Skip to content

Commit df6cadd

Browse files
author
Niklas Kiefer
committed
feat(palette): add keyboard support for palette entries
Closes #536
1 parent feefc13 commit df6cadd

File tree

4 files changed

+132
-44
lines changed

4 files changed

+132
-44
lines changed

packages/form-js-editor/assets/form-js-editor-base.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
--cds-border-strong,
6262
var(--cds-border-strong-01, var(--color-grey-225-10-80))
6363
);
64+
--color-palette-field-focus: var(--cds-border-interactive, var(--color-blue-219-100-53));
6465
--color-palette-field-hover-background: var(--cds-background-hover, var(--color-grey-225-10-90));
6566
--cursor-palette-field: grab;
6667
--palette-width: 250px;
@@ -625,6 +626,8 @@
625626
flex-direction: column;
626627
justify-content: center;
627628
font-size: 11px;
629+
align-items: center;
630+
border: none;
628631
user-select: none;
629632
color: var( --color-palette-field);
630633
background: var(--color-palette-field-background);
@@ -634,6 +637,11 @@
634637
width: 68px;
635638
}
636639

640+
.fjs-palette-container .fjs-palette-field:focus {
641+
outline: none;
642+
border: solid 1px var(--color-palette-field-focus);
643+
}
644+
637645
.fjs-palette-field .fjs-palette-field-icon {
638646
margin: 0 auto;
639647
}

packages/form-js-editor/src/features/palette/components/Palette.js

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import {
1111

1212
import {
1313
CloseIcon,
14-
iconsByType,
1514
SearchIcon
1615
} from '../../../render/components/icons';
1716

17+
import PaletteEntry from './PaletteEntry';
18+
1819
import { formFields } from '@bpmn-io/form-js-viewer';
1920

2021
export const PALETTE_ENTRIES = formFields.filter(({ config: fieldConfig }) => fieldConfig.type !== 'default').map(({ config: fieldConfig }) => {
@@ -120,20 +121,11 @@ export default function Palette(props) {
120121
<span class="fjs-palette-group-title">{ label }</span>
121122
<div class="fjs-palette-fields fjs-drag-container fjs-no-drop">
122123
{
123-
entries.map(({ label, type }) => {
124-
const Icon = iconsByType(type);
125-
124+
entries.map(entry => {
126125
return (
127-
<div
128-
class="fjs-palette-field fjs-drag-copy fjs-no-drop"
129-
data-field-type={ type }
130-
title={ `Create ${getIndefiniteArticle(type)} ${label} element` }
131-
>
132-
{
133-
Icon ? <Icon class="fjs-palette-field-icon" width="36" height="36" viewBox="0 0 54 54" /> : null
134-
}
135-
<span class="fjs-palette-field-text">{ label }</span>
136-
</div>
126+
<PaletteEntry
127+
{ ...entry }
128+
/>
137129
);
138130
})
139131
}
@@ -174,14 +166,4 @@ function groupEntries(entries) {
174166
});
175167

176168
return groups.filter(g => g.entries.length);
177-
}
178-
179-
function getIndefiniteArticle(type) {
180-
if ([
181-
'image'
182-
].includes(type)) {
183-
return 'an';
184-
}
185-
186-
return 'a';
187169
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {
2+
iconsByType
3+
} from '../../../render/components/icons';
4+
5+
import { useService } from '../../../render/hooks';
6+
7+
export default function PaletteEntry(props) {
8+
const {
9+
type,
10+
label
11+
} = props;
12+
13+
const modeling = useService('modeling');
14+
const formEditor = useService('formEditor');
15+
16+
const Icon = iconsByType(type);
17+
18+
const onKeyDown = (event) => {
19+
if (event.code === 'Enter') {
20+
21+
const { fieldType: type } = event.target.dataset;
22+
23+
const { schema } = formEditor._getState();
24+
25+
// add new form field to last position
26+
modeling.addFormField({ type }, schema, schema.components.length);
27+
}
28+
};
29+
30+
return (
31+
<button
32+
class="fjs-palette-field fjs-drag-copy fjs-no-drop"
33+
data-field-type={ type }
34+
title={ `Create ${getIndefiniteArticle(type)} ${label} element` }
35+
onKeyDown={ onKeyDown }
36+
>
37+
{
38+
Icon ? <Icon class="fjs-palette-field-icon" width="36" height="36" viewBox="0 0 54 54" /> : null
39+
}
40+
<span class="fjs-palette-field-text">{ label }</span>
41+
</button>
42+
);
43+
}
44+
45+
46+
// helpers ///////////
47+
48+
function getIndefiniteArticle(type) {
49+
if ([
50+
'image'
51+
].includes(type)) {
52+
return 'an';
53+
}
54+
55+
return 'a';
56+
}

packages/form-js-editor/test/spec/features/palette/Palette.spec.js

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,65 @@ describe('palette', function() {
173173
});
174174

175175

176+
describe('keyboard support', function() {
177+
178+
179+
it('should add entry on ENTER', async function() {
180+
181+
// given
182+
const spy = sinon.spy();
183+
184+
const schema = {
185+
components: []
186+
};
187+
188+
const result = createPalette({
189+
container,
190+
modeling: { addFormField: spy },
191+
formEditor: { _getState: () => ({ schema }) }
192+
});
193+
194+
const entry = result.container.querySelector('[data-field-type="textfield"]');
195+
196+
// when
197+
fireEvent.focus(entry);
198+
fireEvent.keyDown(entry, { key: 'Enter', code: 'Enter' });
199+
200+
// then
201+
expect(spy).to.have.been.calledOnceWith({ type: 'textfield' }, schema, 0);
202+
});
203+
204+
205+
it('should add entry to last position', async function() {
206+
207+
// given
208+
const spy = sinon.spy();
209+
210+
const schema = {
211+
components: [ {
212+
type: 'textfield',
213+
id: 'foo'
214+
} ]
215+
};
216+
217+
const result = createPalette({
218+
container,
219+
modeling: { addFormField: spy },
220+
formEditor: { _getState: () => ({ schema }) }
221+
});
222+
223+
const entry = result.container.querySelector('[data-field-type="textfield"]');
224+
225+
// when
226+
fireEvent.focus(entry);
227+
fireEvent.keyDown(entry, { key: 'Enter', code: 'Enter' });
228+
229+
// then
230+
expect(spy).to.have.been.calledOnceWith({ type: 'textfield' }, schema, 1);
231+
});
232+
});
233+
234+
176235
describe('a11y', function() {
177236

178237
it('should have no violations', async function() {
@@ -183,17 +242,7 @@ describe('palette', function() {
183242
const result = createPalette({ container });
184243

185244
// then
186-
// @Note(pinussilvestrus): the palette entries are currently
187-
// not keyboard accessible, as we need to invest in an overall
188-
// editor keyboard experience
189-
// cf. https://github.com/bpmn-io/form-js/issues/536
190-
await expectNoViolations(result.container, {
191-
rules: {
192-
'scrollable-region-focusable': {
193-
enabled: false
194-
}
195-
}
196-
});
245+
await expectNoViolations(result.container);
197246
});
198247

199248

@@ -210,15 +259,8 @@ describe('palette', function() {
210259
fireEvent.input(search, { target: { value: 'text' } });
211260

212261
// then
213-
await expectNoViolations(result.container, {
214-
rules: {
215-
'scrollable-region-focusable': {
216-
enabled: false
217-
}
218-
}
219-
});
262+
await expectNoViolations(result.container);
220263
});
221-
222264
});
223265

224266
});
@@ -230,7 +272,7 @@ function createPalette(options = {}) {
230272
const { container } = options;
231273

232274
return render(
233-
WithFormEditorContext(<Palette />),
275+
WithFormEditorContext(<Palette />, options),
234276
{
235277
container
236278
}

0 commit comments

Comments
 (0)