@bpmn-io/diagram-js-canvas-lock 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # diagram-js-canvas-lock
2
+
3
+ [![CI](https://github.com/bpmn-io/diagram-js-canvas-lock/actions/workflows/CI.yml/badge.svg)](https://github.com/bpmn-io/diagram-js-canvas-lock/actions/workflows/CI.yml)
4
+
5
+ This module extends [diagram-js](https://github.com/bpmn-io/diagram-js)-based editors with a canvas lock feature.
6
+
7
+ ## Features
8
+
9
+ * lock and unlock canvas programmatically
10
+
11
+ ## Installation
12
+
13
+ Install via npm:
14
+
15
+ ```sh
16
+ npm install @bpmn-io/diagram-js-canvas-lock
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Use as an extension for [bpmn-js](https://github.com/bpmn-io/bpmn-js):
22
+
23
+ ```javascript
24
+ import CanvasLockModule from '@bpmn-io/diagram-js-canvas-lock';
25
+
26
+ const modeler = new BpmnModeler({
27
+ additionalModules: [
28
+ CanvasLockModule
29
+ ]
30
+ });
31
+ ```
32
+
33
+ Lock and unlock the canvas through the `canvasLock` service:
34
+
35
+ ```javascript
36
+ const canvasLock = modeler.get('canvasLock');
37
+
38
+ canvasLock.lock();
39
+
40
+ canvasLock.isLocked(); // true
41
+
42
+ canvasLock.unlock();
43
+ ```
44
+
45
+ ## Integrating with diagram-js-canvas-lock
46
+
47
+ By design, this module has no knowledge of the other diagram-js plugins running
48
+ alongside it. Instead it exposes a small, stable contract that any plugin can
49
+ integrate with. If your plugin adds interactive UI (pads, overlays, menus, ...),
50
+ respect the lock through one or more of the following.
51
+
52
+ ### 1. Query the lock state (pull)
53
+
54
+ Guard your own entry points with `canvasLock.isLocked()`:
55
+
56
+ ```javascript
57
+ MyPad.prototype.canOpen = function(target) {
58
+ if (this._canvasLock.isLocked()) {
59
+ return false;
60
+ }
61
+
62
+ // ...
63
+ };
64
+ ```
65
+
66
+ `canvasLock` is an optional dependency – resolve it via the injector so your
67
+ plugin keeps working in editors without canvas lock:
68
+
69
+ ```javascript
70
+ function MyPad(injector) {
71
+ this._canvasLock = injector.get('canvasLock', false);
72
+ }
73
+
74
+ MyPad.prototype.isLocked = function() {
75
+ return this._canvasLock && this._canvasLock.isLocked();
76
+ };
77
+
78
+ MyPad.$inject = [ 'injector' ];
79
+ ```
80
+
81
+ ### 2. React to lock changes (push)
82
+
83
+ Subscribe to the `canvasLock.changed` event to tear down or restore UI when the
84
+ lock is toggled:
85
+
86
+ ```javascript
87
+ eventBus.on('canvasLock.changed', function(event) {
88
+ if (event.locked) {
89
+ myPad.close();
90
+ }
91
+ });
92
+ ```
93
+
94
+ ### 3. Make an interaction vetoable (`*.allowed` convention)
95
+
96
+ Following the diagram-js convention, fire an `<x>.allowed` event before opening
97
+ and bail if any listener returns `false`. Canvas lock (and other features) can
98
+ then veto the interaction without knowing about your plugin:
99
+
100
+ ```javascript
101
+ MyPad.prototype.open = function(target) {
102
+ if (this._eventBus.fire('myPad.open.allowed', { target: target }) === false) {
103
+ return;
104
+ }
105
+
106
+ // ...
107
+ };
108
+ ```
109
+
110
+ ### 4. Style locked state (CSS hook)
111
+
112
+ While locked, the canvas container carries the `djs-canvas-locked` class. Use it
113
+ to hide or dim plugin UI purely visually:
114
+
115
+ ```css
116
+ .djs-canvas-locked .my-pad {
117
+ display: none;
118
+ }
119
+ ```
120
+
121
+ ### Public contract summary
122
+
123
+ | Surface | Type | Description |
124
+ | --- | --- | --- |
125
+ | `canvasLock.lock()` / `unlock()` | API | Toggle the lock. |
126
+ | `canvasLock.isLocked()` | API | Query the current lock state. |
127
+ | `canvasLock.changed` | Event | Fired with `{ locked }` whenever the lock toggles. |
128
+ | `djs-canvas-locked` | CSS class | Added to the canvas container while locked. |
129
+
130
+ ## Development
131
+
132
+ ```sh
133
+ npm install
134
+
135
+ npm start
136
+ ```
137
+
138
+
139
+ ## License
140
+
141
+ MIT
@@ -0,0 +1,31 @@
1
+ .djs-canvas-locked {
2
+ user-select: none;
3
+ }
4
+
5
+ .djs-canvas-locked .djs-connection.selected .djs-outline {
6
+ display: initial;
7
+ }
8
+
9
+ .djs-canvas-locked .djs-palette .entry {
10
+ opacity: 0.4;
11
+ pointer-events: none;
12
+ }
13
+
14
+ .djs-canvas-locked .djs-palette .djs-palette-toggle {
15
+ pointer-events: none;
16
+ }
17
+
18
+ .djs-canvas-locked .djs-element .djs-hit-stroke,
19
+ .djs-canvas-locked .djs-element .djs-hit-click-stroke,
20
+ .djs-canvas-locked .djs-element .djs-hit-all {
21
+ cursor: unset;
22
+ }
23
+
24
+ .djs-canvas-locked .djs-resizer {
25
+ display: none;
26
+ }
27
+
28
+ .djs-canvas-locked .djs-segment-dragger,
29
+ .djs-canvas-locked .djs-bendpoint {
30
+ display: none !important;
31
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
3
+ * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
4
+ */
5
+
6
+ /**
7
+ * A service that allows to temporarily lock user interactions
8
+ * while still allowing programmatic changes via modeling APIs.
9
+ *
10
+ * Pan/zoom (navigation) remains enabled even while locked.
11
+ *
12
+ * @param {EventBus} eventBus
13
+ * @param {Canvas} canvas
14
+ */
15
+ export default function CanvasLock(eventBus, canvas) {
16
+ this._eventBus = eventBus;
17
+ this._canvas = canvas;
18
+ this._locked = false;
19
+ }
20
+
21
+ CanvasLock.$inject = [ 'eventBus', 'canvas' ];
22
+
23
+ /**
24
+ * Lock user interactions.
25
+ */
26
+ CanvasLock.prototype.lock = function() {
27
+ if (this._locked) {
28
+ return;
29
+ }
30
+
31
+ this._locked = true;
32
+
33
+ var container = this._canvas.getContainer();
34
+ container.classList.add('djs-canvas-locked');
35
+
36
+ this._eventBus.fire('canvasLock.changed', { locked: true });
37
+ };
38
+
39
+ /**
40
+ * Unlock user interactions.
41
+ */
42
+ CanvasLock.prototype.unlock = function() {
43
+ if (!this._locked) {
44
+ return;
45
+ }
46
+
47
+ this._locked = false;
48
+
49
+ var container = this._canvas.getContainer();
50
+ container.classList.remove('djs-canvas-locked');
51
+
52
+ this._eventBus.fire('canvasLock.changed', { locked: false });
53
+ };
54
+
55
+ /**
56
+ * Check whether interactions are currently locked.
57
+ *
58
+ * @return {boolean}
59
+ */
60
+ CanvasLock.prototype.isLocked = function() {
61
+ return this._locked;
62
+ };
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
3
+ * @typedef {import('./CanvasLock').default} CanvasLock
4
+ * @typedef {import('didi').Injector} Injector
5
+ */
6
+
7
+ var VERY_HIGH_PRIORITY = 10000;
8
+
9
+ /**
10
+ * Blocked interaction events.
11
+ *
12
+ * These are events that initiate user-driven interactions
13
+ * but should be suppressed when the canvas lock is active.
14
+ */
15
+ var BLOCKED_EVENTS = [
16
+
17
+ // block any user-initiated drag, including move, create, connect, etc.
18
+ 'drag.init',
19
+
20
+ // block any user-initiated open of context pad or popup menu
21
+ 'contextPad.open.allowed',
22
+ 'popupMenu.open.allowed',
23
+
24
+ // block direct editing activation
25
+ 'directEditing.activate.allowed'
26
+ ];
27
+
28
+ /**
29
+ * Editor actions that are allowed while locked
30
+ * because they only affect navigation, not diagram content.
31
+ */
32
+ var ALLOWED_EDITOR_ACTIONS = [
33
+ 'stepZoom',
34
+ 'zoom',
35
+ 'moveCanvas'
36
+ ];
37
+
38
+
39
+ /**
40
+ * A behavior that blocks user-initiated interaction events
41
+ * when the canvas lock is active.
42
+ *
43
+ * Navigation events (canvas.move, canvas.zoom) are explicitly
44
+ * not blocked to allow pan/zoom while locked. Keyboard-triggered
45
+ * navigation editor actions (stepZoom, zoom, moveCanvas) are also
46
+ * allowed through.
47
+ *
48
+ * @param {EventBus} eventBus
49
+ * @param {CanvasLock} canvasLock
50
+ * @param {Injector} injector
51
+ */
52
+ export default function CanvasLockBehavior(eventBus, canvasLock, injector) {
53
+
54
+ BLOCKED_EVENTS.forEach(function(event) {
55
+ eventBus.on(event, VERY_HIGH_PRIORITY, function(e) {
56
+ if (canvasLock.isLocked()) {
57
+ return false;
58
+ }
59
+ });
60
+ });
61
+
62
+ // block non-navigation editor actions when locked
63
+ eventBus.on('editorActions.allowed', VERY_HIGH_PRIORITY, function(event) {
64
+ if (canvasLock.isLocked() && !ALLOWED_EDITOR_ACTIONS.includes(event.action)) {
65
+ return false;
66
+ }
67
+ });
68
+
69
+ // close open overlays when locking; restore context pad on unlock
70
+ eventBus.on('canvasLock.changed', function(event) {
71
+ var contextPad = injector.get('contextPad', false);
72
+ var directEditing = injector.get('directEditing', false);
73
+ var dragging = injector.get('dragging', false);
74
+ var popupMenu = injector.get('popupMenu', false);
75
+
76
+ if (event.locked) {
77
+ if (contextPad && contextPad.isOpen()) {
78
+ contextPad.close();
79
+ }
80
+
81
+ if (directEditing && directEditing.isActive()) {
82
+ directEditing.cancel();
83
+ }
84
+
85
+ if (dragging && dragging.context()) {
86
+ dragging.cancel();
87
+ }
88
+
89
+ if (popupMenu && popupMenu.isOpen()) {
90
+ popupMenu.close();
91
+ }
92
+ } else {
93
+
94
+ // restore context pad for current selection
95
+ var selection = injector.get('selection', false);
96
+
97
+ if (contextPad && selection) {
98
+ var selectedElements = selection.get();
99
+
100
+ if (selectedElements.length) {
101
+ contextPad.open(selectedElements.length === 1 ? selectedElements[0] : selectedElements);
102
+ }
103
+ }
104
+ }
105
+ });
106
+ }
107
+
108
+ CanvasLockBehavior.$inject = [ 'eventBus', 'canvasLock', 'injector' ];
package/lib/index.js ADDED
@@ -0,0 +1,11 @@
1
+ import CanvasLock from './CanvasLock.js';
2
+ import CanvasLockBehavior from './CanvasLockBehavior.js';
3
+
4
+ /**
5
+ * @type { import('didi').ModuleDeclaration }
6
+ */
7
+ export default {
8
+ __init__: [ 'canvasLock', 'canvasLockBehavior' ],
9
+ canvasLock: [ 'type', CanvasLock ],
10
+ canvasLockBehavior: [ 'type', CanvasLockBehavior ]
11
+ };
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@bpmn-io/diagram-js-canvas-lock",
3
+ "version": "0.1.0",
4
+ "description": "A diagram-js plugin for locking the canvas",
5
+ "main": "lib/index.js",
6
+ "module": "lib/index.js",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": "./lib/index.js",
10
+ "./lib/*": "./lib/*",
11
+ "./assets/*": "./assets/*",
12
+ "./package.json": "./package.json"
13
+ },
14
+ "scripts": {
15
+ "all": "run-s lint test",
16
+ "test": "karma start karma.config.cjs",
17
+ "lint": "eslint .",
18
+ "dev": "npm test -- --auto-watch --no-single-run",
19
+ "start": "cross-env SINGLE_START=modeler npm run dev"
20
+ },
21
+ "keywords": [
22
+ "diagram-js",
23
+ "canvas",
24
+ "lock"
25
+ ],
26
+ "author": "Philipp Fromme <philipp.fromme@camunda.com>",
27
+ "license": "MIT",
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "files": [
32
+ "assets",
33
+ "lib"
34
+ ],
35
+ "peerDependencies": {
36
+ "diagram-js": ">= 15.16.0",
37
+ "diagram-js-direct-editing": ">= 3.4.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "diagram-js-direct-editing": {
41
+ "optional": true
42
+ }
43
+ },
44
+ "devDependencies": {
45
+ "babel-loader": "^10.1.1",
46
+ "babel-plugin-istanbul": "^8.0.0",
47
+ "bpmn-js": "^18.19.0",
48
+ "chai": "^6.2.2",
49
+ "cross-env": "^10.1.0",
50
+ "eslint": "^9.39.4",
51
+ "eslint-plugin-bpmn-io": "^2.2.0",
52
+ "karma": "^6.4.4",
53
+ "karma-chrome-launcher-2": "^3.3.0",
54
+ "karma-coverage": "^2.2.1",
55
+ "karma-debug-launcher": "^0.0.5",
56
+ "karma-env-preprocessor": "^0.1.1",
57
+ "karma-mocha": "^2.0.1",
58
+ "karma-tldr-reporter": "^1.0.0",
59
+ "karma-webpack": "^5.0.1",
60
+ "mocha": "^11.7.5",
61
+ "mocha-test-container-support": "^0.2.0",
62
+ "npm-run-all2": "^9.0.2",
63
+ "sinon": "^22.0.0",
64
+ "sinon-chai": "^4.0.1",
65
+ "webpack": "^5.106.2"
66
+ }
67
+ }