From 869008218734a415b4484662616745162e78feac Mon Sep 17 00:00:00 2001 From: Niklas Kiefer Date: Tue, 10 Oct 2023 14:13:02 +0200 Subject: [PATCH] feat(palette): add keyboard support for palette entries Closes #503 --- .../assets/form-js-editor-base.css | 8 ++ .../features/palette/components/Palette.js | 30 ++----- .../palette/components/PaletteEntry.js | 56 +++++++++++++ .../spec/features/palette/Palette.spec.js | 82 ++++++++++++++----- 4 files changed, 132 insertions(+), 44 deletions(-) create mode 100644 packages/form-js-editor/src/features/palette/components/PaletteEntry.js diff --git a/packages/form-js-editor/assets/form-js-editor-base.css b/packages/form-js-editor/assets/form-js-editor-base.css index 6962e9cb2..2c62beaeb 100644 --- a/packages/form-js-editor/assets/form-js-editor-base.css +++ b/packages/form-js-editor/assets/form-js-editor-base.css @@ -61,6 +61,7 @@ --cds-border-strong, var(--cds-border-strong-01, var(--color-grey-225-10-80)) ); + --color-palette-field-focus: var(--cds-border-interactive, var(--color-blue-219-100-53)); --color-palette-field-hover-background: var(--cds-background-hover, var(--color-grey-225-10-90)); --cursor-palette-field: grab; --palette-width: 250px; @@ -625,6 +626,8 @@ flex-direction: column; justify-content: center; font-size: 11px; + align-items: center; + border: none; user-select: none; color: var( --color-palette-field); background: var(--color-palette-field-background); @@ -634,6 +637,11 @@ width: 68px; } +.fjs-palette-container .fjs-palette-field:focus { + outline: none; + border: solid 1px var(--color-palette-field-focus); +} + .fjs-palette-field .fjs-palette-field-icon { margin: 0 auto; } diff --git a/packages/form-js-editor/src/features/palette/components/Palette.js b/packages/form-js-editor/src/features/palette/components/Palette.js index a10dd6afb..e13102a84 100644 --- a/packages/form-js-editor/src/features/palette/components/Palette.js +++ b/packages/form-js-editor/src/features/palette/components/Palette.js @@ -11,10 +11,11 @@ import { import { CloseIcon, - iconsByType, SearchIcon } from '../../../render/components/icons'; +import PaletteEntry from './PaletteEntry'; + import { formFields } from '@bpmn-io/form-js-viewer'; export const PALETTE_ENTRIES = formFields.filter(({ config: fieldConfig }) => fieldConfig.type !== 'default').map(({ config: fieldConfig }) => { @@ -120,20 +121,11 @@ export default function Palette(props) { { label }
{ - entries.map(({ label, type }) => { - const Icon = iconsByType(type); - + entries.map(entry => { return ( -
- { - Icon ? : null - } - { label } -
+ ); }) } @@ -174,14 +166,4 @@ function groupEntries(entries) { }); return groups.filter(g => g.entries.length); -} - -function getIndefiniteArticle(type) { - if ([ - 'image' - ].includes(type)) { - return 'an'; - } - - return 'a'; } \ No newline at end of file diff --git a/packages/form-js-editor/src/features/palette/components/PaletteEntry.js b/packages/form-js-editor/src/features/palette/components/PaletteEntry.js new file mode 100644 index 000000000..1a284e97a --- /dev/null +++ b/packages/form-js-editor/src/features/palette/components/PaletteEntry.js @@ -0,0 +1,56 @@ +import { + iconsByType +} from '../../../render/components/icons'; + +import { useService } from '../../../render/hooks'; + +export default function PaletteEntry(props) { + const { + type, + label + } = props; + + const modeling = useService('modeling'); + const formEditor = useService('formEditor'); + + const Icon = iconsByType(type); + + const onKeyDown = (event) => { + if (event.code === 'Enter') { + + const { fieldType: type } = event.target.dataset; + + const { schema } = formEditor._getState(); + + // add new form field to last position + modeling.addFormField({ type }, schema, schema.components.length); + } + }; + + return ( + + ); +} + + +// helpers /////////// + +function getIndefiniteArticle(type) { + if ([ + 'image' + ].includes(type)) { + return 'an'; + } + + return 'a'; +} \ No newline at end of file diff --git a/packages/form-js-editor/test/spec/features/palette/Palette.spec.js b/packages/form-js-editor/test/spec/features/palette/Palette.spec.js index ee35c3390..e3ceb630c 100644 --- a/packages/form-js-editor/test/spec/features/palette/Palette.spec.js +++ b/packages/form-js-editor/test/spec/features/palette/Palette.spec.js @@ -173,6 +173,65 @@ describe('palette', function() { }); + describe('keyboard support', function() { + + + it('should add entry on ENTER', async function() { + + // given + const spy = sinon.spy(); + + const schema = { + components: [] + }; + + const result = createPalette({ + container, + modeling: { addFormField: spy }, + formEditor: { _getState: () => ({ schema }) } + }); + + const entry = result.container.querySelector('[data-field-type="textfield"]'); + + // when + fireEvent.focus(entry); + fireEvent.keyDown(entry, { key: 'Enter', code: 'Enter' }); + + // then + expect(spy).to.have.been.calledOnceWith({ type: 'textfield' }, schema, 0); + }); + + + it('should add entry to last position', async function() { + + // given + const spy = sinon.spy(); + + const schema = { + components: [ { + type: 'textfield', + id: 'foo' + } ] + }; + + const result = createPalette({ + container, + modeling: { addFormField: spy }, + formEditor: { _getState: () => ({ schema }) } + }); + + const entry = result.container.querySelector('[data-field-type="textfield"]'); + + // when + fireEvent.focus(entry); + fireEvent.keyDown(entry, { key: 'Enter', code: 'Enter' }); + + // then + expect(spy).to.have.been.calledOnceWith({ type: 'textfield' }, schema, 1); + }); + }); + + describe('a11y', function() { it('should have no violations', async function() { @@ -183,17 +242,7 @@ describe('palette', function() { const result = createPalette({ container }); // then - // @Note(pinussilvestrus): the palette entries are currently - // not keyboard accessible, as we need to invest in an overall - // editor keyboard experience - // cf. https://github.com/bpmn-io/form-js/issues/536 - await expectNoViolations(result.container, { - rules: { - 'scrollable-region-focusable': { - enabled: false - } - } - }); + await expectNoViolations(result.container); }); @@ -210,15 +259,8 @@ describe('palette', function() { fireEvent.input(search, { target: { value: 'text' } }); // then - await expectNoViolations(result.container, { - rules: { - 'scrollable-region-focusable': { - enabled: false - } - } - }); + await expectNoViolations(result.container); }); - }); }); @@ -230,7 +272,7 @@ function createPalette(options = {}) { const { container } = options; return render( - WithFormEditorContext(), + WithFormEditorContext(, options), { container }