From 6cd54a9b92f75fc6c7ff60b0bf82bfee8ede125f Mon Sep 17 00:00:00 2001 From: Vinicius Goulart Date: Wed, 18 Sep 2024 10:43:35 +0200 Subject: [PATCH] feat: add implicit keyboard binding Editor is now focussed automatically on hover. chore: update to diagram-js@15 Related to https://github.com/bpmn-io/diagram-js/pull/662 --- package-lock.json | 62 +++++++++++-------- package.json | 2 +- packages/form-js-editor/src/FormEditor.js | 2 +- .../keyboard/FormEditorKeyboardBindings.js | 8 +-- .../form-js-editor/src/render/Renderer.js | 25 ++++++++ .../src/render/components/FormEditor.js | 21 +++++-- .../test/spec/FormEditor.spec.js | 21 ------- .../form-js-viewer/test/spec/Form.spec.js | 3 - 8 files changed, 81 insertions(+), 63 deletions(-) diff --git a/package-lock.json b/package-lock.json index a37a12e06..923935d1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "cross-env": "^7.0.3", "css-loader": "^7.1.2", "del-cli": "^5.1.0", - "diagram-js": "^14.11.3", + "diagram-js": "^15.2.2", "didi": "^10.2.2", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", @@ -2059,16 +2059,18 @@ "resolved": "https://registry.npmjs.org/@bpmn-io/diagram-js-ui/-/diagram-js-ui-0.2.3.tgz", "integrity": "sha512-OGyjZKvGK8tHSZ0l7RfeKhilGoOGtFDcoqSGYkX0uhFlo99OVZ9Jn1K7TJGzcE9BdKwvA5Y5kGqHEhdTxHvFfw==", "dev": true, + "license": "MIT", "dependencies": { "htm": "^3.1.1", "preact": "^10.11.2" } }, "node_modules/@bpmn-io/diagram-js-ui/node_modules/preact": { - "version": "10.20.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.20.1.tgz", - "integrity": "sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw==", + "version": "10.24.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.0.tgz", + "integrity": "sha512-aK8Cf+jkfyuZ0ZZRG9FbYqwmEiGQ4y/PUO4SuTWoyWL244nZZh7bd5h2APd4rSNDYTBNghg1L+5iJN3Skxtbsw==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -7855,10 +7857,11 @@ } }, "node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -9354,9 +9357,9 @@ "license": "MIT" }, "node_modules/diagram-js": { - "version": "14.11.3", - "resolved": "https://registry.npmjs.org/diagram-js/-/diagram-js-14.11.3.tgz", - "integrity": "sha512-Seq9BHAXfzKS60L4v4Gvgvv72wOtvrfJQAyyPm9pntSZDMzjoodPSXnEUPud1G2zVCMGEUUW++s0reEdaWgkXA==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/diagram-js/-/diagram-js-15.2.2.tgz", + "integrity": "sha512-gpe890qN9UL0fLTWGyo7fq9ExBXYbfm1+ccW9UhJs/6Q6ArvfanTKcozd18Fh+EW/42k/FDdEyqm6bWoRbYp+w==", "dev": true, "license": "MIT", "dependencies": { @@ -12046,7 +12049,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/html-escaper": { "version": "2.0.2", @@ -12302,6 +12306,8 @@ }, "node_modules/inherits-browser": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/inherits-browser/-/inherits-browser-0.1.0.tgz", + "integrity": "sha512-CJHHvW3jQ6q7lzsXPpapLdMx5hDpSF3FSh45pwsj6bKxJJ8Nl8v43i5yXnr3BdfOimGHKyniewQtnAIp3vyJJw==", "dev": true, "license": "ISC" }, @@ -16393,6 +16399,7 @@ "resolved": "https://registry.npmjs.org/object-refs/-/object-refs-0.4.0.tgz", "integrity": "sha512-6kJqKWryKZmtte6QYvouas0/EIJKPI1/MMIuRsiBlNuhIMfqYTggzX2F1AJ2+cDs288xyi9GL7FyasHINR98BQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -16965,10 +16972,11 @@ } }, "node_modules/path-intersection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-intersection/-/path-intersection-3.0.0.tgz", - "integrity": "sha512-Rdnfb33F9+qadWe3ZyzDpw3KSXQhsK1MByL44QzSDIQtMAujd0zFx9f+kt4SaQp1JOoXl5pl5K28EoEuAEgarA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/path-intersection/-/path-intersection-3.1.0.tgz", + "integrity": "sha512-3xS3lvv/vuwm5aH2BVvNRvnvwR2Drde7jQClKpCXTYXIMMjcw/EnMhzCgeHwqbCpzi760PEfAkU53vSIlrNr9A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14.20" } @@ -22505,9 +22513,9 @@ }, "dependencies": { "preact": { - "version": "10.20.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.20.1.tgz", - "integrity": "sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw==", + "version": "10.24.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.0.tgz", + "integrity": "sha512-aK8Cf+jkfyuZ0ZZRG9FbYqwmEiGQ4y/PUO4SuTWoyWL244nZZh7bd5h2APd4rSNDYTBNghg1L+5iJN3Skxtbsw==", "dev": true } } @@ -26502,9 +26510,9 @@ } }, "clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "dev": true }, "cmd-shim": { @@ -27504,9 +27512,9 @@ "dev": true }, "diagram-js": { - "version": "14.11.3", - "resolved": "https://registry.npmjs.org/diagram-js/-/diagram-js-14.11.3.tgz", - "integrity": "sha512-Seq9BHAXfzKS60L4v4Gvgvv72wOtvrfJQAyyPm9pntSZDMzjoodPSXnEUPud1G2zVCMGEUUW++s0reEdaWgkXA==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/diagram-js/-/diagram-js-15.2.2.tgz", + "integrity": "sha512-gpe890qN9UL0fLTWGyo7fq9ExBXYbfm1+ccW9UhJs/6Q6ArvfanTKcozd18Fh+EW/42k/FDdEyqm6bWoRbYp+w==", "dev": true, "requires": { "@bpmn-io/diagram-js-ui": "^0.2.3", @@ -29543,6 +29551,8 @@ }, "inherits-browser": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/inherits-browser/-/inherits-browser-0.1.0.tgz", + "integrity": "sha512-CJHHvW3jQ6q7lzsXPpapLdMx5hDpSF3FSh45pwsj6bKxJJ8Nl8v43i5yXnr3BdfOimGHKyniewQtnAIp3vyJJw==", "dev": true }, "ini": { @@ -32736,9 +32746,9 @@ "dev": true }, "path-intersection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-intersection/-/path-intersection-3.0.0.tgz", - "integrity": "sha512-Rdnfb33F9+qadWe3ZyzDpw3KSXQhsK1MByL44QzSDIQtMAujd0zFx9f+kt4SaQp1JOoXl5pl5K28EoEuAEgarA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/path-intersection/-/path-intersection-3.1.0.tgz", + "integrity": "sha512-3xS3lvv/vuwm5aH2BVvNRvnvwR2Drde7jQClKpCXTYXIMMjcw/EnMhzCgeHwqbCpzi760PEfAkU53vSIlrNr9A==", "dev": true }, "path-is-absolute": { diff --git a/package.json b/package.json index 433c0b3e9..8efc56b2a 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "cross-env": "^7.0.3", "css-loader": "^7.1.2", "del-cli": "^5.1.0", - "diagram-js": "^14.11.3", + "diagram-js": "^15.2.2", "didi": "^10.2.2", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", diff --git a/packages/form-js-editor/src/FormEditor.js b/packages/form-js-editor/src/FormEditor.js index 63f46ae45..f70b19884 100644 --- a/packages/form-js-editor/src/FormEditor.js +++ b/packages/form-js-editor/src/FormEditor.js @@ -63,7 +63,7 @@ export class FormEditor { */ this._container = createFormContainer(); - this._container.setAttribute('input-handle-modified-keys', 'z,y'); + this._container.setAttribute('tabindex', '0'); const { container, exporter, injector = this._createInjector(options, this._container), properties = {} } = options; diff --git a/packages/form-js-editor/src/features/keyboard/FormEditorKeyboardBindings.js b/packages/form-js-editor/src/features/keyboard/FormEditorKeyboardBindings.js index 37271f18d..dae50645b 100644 --- a/packages/form-js-editor/src/features/keyboard/FormEditorKeyboardBindings.js +++ b/packages/form-js-editor/src/features/keyboard/FormEditorKeyboardBindings.js @@ -1,6 +1,4 @@ -import { isCmd, isKey, isShift } from 'diagram-js/lib/features/keyboard/KeyboardUtil'; - -import { KEYS_REDO, KEYS_UNDO } from 'diagram-js/lib/features/keyboard/KeyboardBindings'; +import { isUndo, isRedo } from 'diagram-js/lib/features/keyboard/KeyboardUtil'; const LOW_PRIORITY = 500; @@ -25,7 +23,7 @@ export class FormEditorKeyboardBindings { addListener('undo', (context) => { const { keyEvent } = context; - if (isCmd(keyEvent) && !isShift(keyEvent) && isKey(KEYS_UNDO, keyEvent)) { + if (isUndo(keyEvent)) { editorActions.trigger('undo'); return true; @@ -38,7 +36,7 @@ export class FormEditorKeyboardBindings { addListener('redo', (context) => { const { keyEvent } = context; - if (isCmd(keyEvent) && (isKey(KEYS_REDO, keyEvent) || (isKey(KEYS_UNDO, keyEvent) && isShift(keyEvent)))) { + if (isRedo(keyEvent)) { editorActions.trigger('redo'); return true; diff --git a/packages/form-js-editor/src/render/Renderer.js b/packages/form-js-editor/src/render/Renderer.js index d5d0cb045..76687937f 100644 --- a/packages/form-js-editor/src/render/Renderer.js +++ b/packages/form-js-editor/src/render/Renderer.js @@ -22,6 +22,31 @@ export class Renderer { constructor(renderConfig, eventBus, formEditor, injector) { const { container, compact = false } = renderConfig; + eventBus.on('form.init', function () { + // emit so dependent components can hook in + // this is required to register keyboard bindings + eventBus.fire('canvas.init', { + svg: container, + viewport: null, + }); + }); + + // focus container on over if no selection + container.addEventListener('mouseover', function () { + if (document.activeElement === document.body) { + container.focus({ preventScroll: true }); + } + }); + + // ensure we focus the container if the users clicks + // inside; this follows input focus handling closely + container.addEventListener('click', function (event) { + // force focus when clicking container + if (!container.contains(document.activeElement)) { + container.focus({ preventScroll: true }); + } + }); + const App = () => { const [state, setState] = useState(formEditor._getState()); diff --git a/packages/form-js-editor/src/render/components/FormEditor.js b/packages/form-js-editor/src/render/components/FormEditor.js index 271e18e17..3b56919c4 100644 --- a/packages/form-js-editor/src/render/components/FormEditor.js +++ b/packages/form-js-editor/src/render/components/FormEditor.js @@ -121,14 +121,23 @@ function Element(props) { } }, [selection, field]); - function onClick(event) { - event.stopPropagation(); + const onClick = useCallback( + (event) => { + // TODO(nikku): refactor this to use proper DOM delegation + const fieldEl = event.target.closest('[data-id]'); - selection.toggle(field); + if (!fieldEl) { + return; + } - // properly focus on field - ref.current.focus(); - } + const id = fieldEl.dataset.id; + + if (id === field.id) { + selection.toggle(field); + } + }, + [field, selection], + ); const isSelected = selection.isSelected(field); diff --git a/packages/form-js-editor/test/spec/FormEditor.spec.js b/packages/form-js-editor/test/spec/FormEditor.spec.js index 571e892a2..620fa01f2 100644 --- a/packages/form-js-editor/test/spec/FormEditor.spec.js +++ b/packages/form-js-editor/test/spec/FormEditor.spec.js @@ -52,9 +52,6 @@ describe('FormEditor', function () { await bootstrapFormEditor({ container, schema, - keyboard: { - bindTo: document, - }, }); formEditor.on('changed', (event) => { @@ -70,9 +67,6 @@ describe('FormEditor', function () { await bootstrapFormEditor({ container, schema: schemaRows, - keyboard: { - bindTo: document, - }, debugColumns: true, }); @@ -93,9 +87,6 @@ describe('FormEditor', function () { await bootstrapFormEditor({ container, schema, - keyboard: { - bindTo: document, - }, }); // then @@ -111,9 +102,6 @@ describe('FormEditor', function () { await bootstrapFormEditor({ container, schema, - keyboard: { - bindTo: document, - }, }); // when @@ -132,9 +120,6 @@ describe('FormEditor', function () { renderer: { compact: true, }, - keyboard: { - bindTo: document, - }, }); // then @@ -153,9 +138,6 @@ describe('FormEditor', function () { type: 'default', }, debounce: true, - keyboard: { - bindTo: document, - }, }); // then @@ -181,9 +163,6 @@ describe('FormEditor', function () { ], }, debounce: true, - keyboard: { - bindTo: document, - }, }); // then diff --git a/packages/form-js-viewer/test/spec/Form.spec.js b/packages/form-js-viewer/test/spec/Form.spec.js index a6592b1c8..b6b9846de 100644 --- a/packages/form-js-viewer/test/spec/Form.spec.js +++ b/packages/form-js-viewer/test/spec/Form.spec.js @@ -208,9 +208,6 @@ describe('Form', function () { await bootstrapForm({ container, schema, - keyboard: { - bindTo: document, - }, }); // when