Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make canvas selectable / keyboard binding implicit #662

Open
wants to merge 24 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c04b003
feat: make SVG selectable
philippfromme Aug 11, 2022
135b599
feat(keyboard): remove explicit binding
nikku Aug 15, 2022
38dbb69
feat: focus on hover if no document selection
nikku Aug 16, 2022
8d6a153
chore(CHANGELOG): update changelog
nikku Aug 15, 2022
144763d
fix(core): prevent scrolling on canvas focus
nikku Aug 17, 2022
e957159
chore: safety console warning
jarekdanielak Aug 29, 2024
7c2cf7e
chore: alpha build warning
jarekdanielak Aug 30, 2024
73230ae
15.0.0-alpha-keyboard
jarekdanielak Sep 10, 2024
07add7f
chore: clean up
jarekdanielak Sep 19, 2024
b439ec6
chore: fix tests
jarekdanielak Sep 19, 2024
bbfcf5a
chore: remove canvas outline
jarekdanielak Oct 17, 2024
92a8734
chore: focus canvas after popup menu is closed
jarekdanielak Oct 17, 2024
7997db8
chore: only refocus on menu close if back to document.body; clean up
jarekdanielak Oct 17, 2024
58de300
chore: update and fix linting
jarekdanielak Oct 17, 2024
8bd695c
chore: add focus related events and property
jarekdanielak Oct 18, 2024
ffc7b39
chore: change to general event; add isFocused API
jarekdanielak Oct 18, 2024
1e1b70e
chore: split focus and restoreFocus into two API functions
jarekdanielak Oct 31, 2024
912b0b9
chore: PhantomJS clean up
jarekdanielak Oct 31, 2024
5a370b3
chore: remove unused var
jarekdanielak Oct 31, 2024
94efc6e
chore: remove outdated changelog entry
jarekdanielak Oct 31, 2024
932bd47
chore: fix test and dep
jarekdanielak Oct 31, 2024
186d38a
chore: revert seemingly random change
jarekdanielak Nov 1, 2024
36ae7e7
Merge branch 'develop' into keyboard-bind
jarekdanielak Nov 1, 2024
18db092
chore: update package-lock
jarekdanielak Nov 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ All notable changes to [diagram-js](https://github.com/bpmn-io/diagram-js) are d

## Unreleased

_**Note:** Yet to be released changes appear here._
* `FEAT`: only copy if selected elements ([#660](https://github.com/bpmn-io/diagram-js/pull/660))
* `FEAT`: make canvas browser selectable ([#659](https://github.com/bpmn-io/diagram-js/pull/659))
* `FEAT`: trigger keyboard bindings on browser selection only ([#661](https://github.com/bpmn-io/diagram-js/issues/661))

### Breaking Changes

* Keyboard binding target can no longer be chosen. Configure keyboard binding via the `keyboard.bind` configuration and rely on keybindings to work if the canvas has browser focus. ([#661](https://github.com/bpmn-io/diagram-js/issues/661))

## 14.11.3

Expand Down
20 changes: 12 additions & 8 deletions assets/diagram-js.css
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@
--tooltip-error-color: var(--color-red-360-100-45);
}

/**
* SVG styles
*/

.djs-container svg.drop-not-ok {
background: var(--shape-drop-not-allowed-fill-color) !important;
}

.djs-container svg.new-parent {
background: var(--shape-drop-allowed-fill-color) !important;
}

/**
* outline styles
*/
Expand Down Expand Up @@ -147,14 +159,6 @@
fill: var(--shape-drop-allowed-fill-color) !important;
}

svg.drop-not-ok {
background: var(--shape-drop-not-allowed-fill-color) !important;
}

svg.new-parent {
background: var(--shape-drop-allowed-fill-color) !important;
}


/* Override move cursor during drop and connect */
.drop-not-ok,
Expand Down
56 changes: 53 additions & 3 deletions lib/core/Canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
} from 'min-dash';

import {
assignStyle
assignStyle,
attr as domAttr
} from 'min-dom';

import {
Expand Down Expand Up @@ -189,6 +190,11 @@
*/
this._rootElement = null;

/**
* @type {boolean}
*/
this._focused = false;
nikku marked this conversation as resolved.
Show resolved Hide resolved

this._init(config || {});
}

Expand All @@ -215,14 +221,33 @@
* @param {CanvasConfig} config
*/
Canvas.prototype._init = function(config) {

const eventBus = this._eventBus;

// html container
const container = this._container = createContainer(config);

const svg = this._svg = svgCreate('svg');
svgAttr(svg, { width: '100%', height: '100%' });

svgAttr(svg, {
width: '100%',
height: '100%'
});

domAttr(svg, 'tabindex', 0);

eventBus.on('element.hover', () => {
this.restoreFocus();
});

svg.addEventListener('focusin', () => {
this._focused = true;
eventBus.fire('canvas.focus.changed', { focused: true });
});

svg.addEventListener('focusout', () => {
this._focused = false;
eventBus.fire('canvas.focus.changed', { focused: false });
});

svgAppend(container, svg);

Expand Down Expand Up @@ -314,6 +339,31 @@
delete this._cachedViewbox;
};

/**
* Sets focus on the canvas SVG element.
*/
Canvas.prototype.focus = function() {
jarekdanielak marked this conversation as resolved.
Show resolved Hide resolved
this._svg.focus({ preventScroll: true });
};

/**
* Sets focus on the canvas SVG element if `document.body` is currently focused.
*/
Canvas.prototype.restoreFocus = function() {
if (document.activeElement === document.body) {
this.focus();
}
};

/**
* Returns true if the canvas is focused.
*
* @return {boolean}
*/
Canvas.prototype.isFocused = function() {
return this._focused;

Check warning on line 364 in lib/core/Canvas.js

View check run for this annotation

Codecov / codecov/patch

lib/core/Canvas.js#L364

Added line #L364 was not covered by tests
};

/**
* Returns the default layer on which
* all elements are drawn.
Expand Down
84 changes: 26 additions & 58 deletions lib/features/keyboard/Keyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import {
} from 'min-dash';

import {
closest as domClosest,
event as domEvent,
matches as domMatches
event as domEvent
} from 'min-dom';

import {
Expand All @@ -24,10 +22,11 @@ import {
var KEYDOWN_EVENT = 'keyboard.keydown',
KEYUP_EVENT = 'keyboard.keyup';

var HANDLE_MODIFIER_ATTRIBUTE = 'input-handle-modified-keys';

var DEFAULT_PRIORITY = 1000;

var compatMessage = 'Explicit binding of keyboard to an element got removed. For more information, see https://github.com/bpmn-io/diagram-js/issues/661';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var compatMessage = 'Explicit binding of keyboard to an element got removed. For more information, see https://github.com/bpmn-io/diagram-js/issues/661';
var compatMessage = 'Keyboard binding is now implicit; explicit binding to an element got removed. For more information, see https://github.com/bpmn-io/diagram-js/issues/661';



/**
* A keyboard abstraction that may be activated and
* deactivated by users at will, consuming global key events
Expand All @@ -46,8 +45,8 @@ var DEFAULT_PRIORITY = 1000;
*
* All events contain one field which is node.
*
* A default binding for the keyboard may be specified via the
* `keyboard.bindTo` configuration option.
* Specify the initial keyboard binding state via the
* `keyboard.bind=true|false` configuration option.
*
* @param {Object} config
* @param {EventTarget} [config.bindTo]
Expand All @@ -56,7 +55,8 @@ var DEFAULT_PRIORITY = 1000;
export default function Keyboard(config, eventBus) {
var self = this;

this._config = config || {};
this._config = config = config || {};

this._eventBus = eventBus;

this._keydownHandler = this._keydownHandler.bind(this);
Expand All @@ -69,19 +69,22 @@ export default function Keyboard(config, eventBus) {
self.unbind();
});

eventBus.on('diagram.init', function() {
self._fire('init');
});
if (config.bindTo) {
console.error('unsupported configuration <keyboard.bindTo>', new Error(compatMessage));
}

var bind = config && config.bind !== false;

eventBus.on('canvas.init', function(event) {
self._target = event.svg;

eventBus.on('attach', function() {
if (config && config.bindTo) {
self.bind(config.bindTo);
if (bind) {
self.bind();
}
});

eventBus.on('detach', function() {
self.unbind();
self._fire('init');
});

}

Keyboard.$inject = [
Expand Down Expand Up @@ -116,34 +119,7 @@ Keyboard.prototype._keyHandler = function(event, type) {
};

Keyboard.prototype._isEventIgnored = function(event) {
if (event.defaultPrevented) {
return true;
}

return (
isInput(event.target) || (
isButton(event.target) && isKey([ ' ', 'Enter' ], event)
)
) && this._isModifiedKeyIgnored(event);
};

Keyboard.prototype._isModifiedKeyIgnored = function(event) {
if (!isCmd(event)) {
return true;
}

var allowedModifiers = this._getAllowedModifiers(event.target);
return allowedModifiers.indexOf(event.key) === -1;
};

Keyboard.prototype._getAllowedModifiers = function(element) {
var modifierContainer = domClosest(element, '[' + HANDLE_MODIFIER_ATTRIBUTE + ']', true);

if (!modifierContainer || (this._node && !this._node.contains(modifierContainer))) {
return [];
}

return modifierContainer.getAttribute(HANDLE_MODIFIER_ATTRIBUTE).split(',');
return false;
};

/**
Expand All @@ -153,10 +129,14 @@ Keyboard.prototype._getAllowedModifiers = function(element) {
*/
Keyboard.prototype.bind = function(node) {

if (node) {
console.error('unsupported argument <node>', new Error(compatMessage));
}

// make sure that the keyboard is only bound once to the DOM
this.unbind();

this._node = node;
node = this._node = this._target;

// bind key events
domEvent.bind(node, 'keydown', this._keydownHandler);
Expand Down Expand Up @@ -226,15 +206,3 @@ Keyboard.prototype.hasModifier = hasModifier;
Keyboard.prototype.isCmd = isCmd;
Keyboard.prototype.isShift = isShift;
Keyboard.prototype.isKey = isKey;



// helpers ///////

function isInput(target) {
return target && (domMatches(target, 'input, textarea') || target.contentEditable === 'true');
}

function isButton(target) {
return target && domMatches(target, 'button, input[type=submit], input[type=button], a[href], [aria-role=button]');
}
2 changes: 2 additions & 0 deletions lib/features/popup-menu/PopupMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ PopupMenu.prototype.close = function() {

this.reset();

this._canvas.restoreFocus();

this._current = null;
};

Expand Down
8 changes: 3 additions & 5 deletions lib/features/preview-support/PreviewSupport.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,10 @@ PreviewSupport.prototype._cloneMarkers = function(gfx, className = 'djs-dragger'

if (gfx.childNodes) {

// TODO: use forEach once we drop PhantomJS
for (var i = 0; i < gfx.childNodes.length; i++) {
gfx.childNodes.forEach((childNode) => {
self._cloneMarkers(childNode, className, rootGfx);
});

// recursively clone markers of child nodes
self._cloneMarkers(gfx.childNodes[ i ], className, rootGfx);
}
}

if (!canHaveMarker(gfx)) {
Expand Down
Loading