From 953f2f0fa05c140e1e54235f6d52293d42fd3913 Mon Sep 17 00:00:00 2001 From: Roman Bruckner Date: Mon, 18 May 2026 17:49:34 +0200 Subject: [PATCH 1/4] feat(joint-core): add `Paper#setDragging` / `Paper#isDragging` API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New public methods on `dia.Paper` for marking and reading the active-drag state on an event: - `paper.setDragging(evt, value = true)` — stash the drag flag on the event's per-paper `eventData` bag (defaults to `true`). - `paper.isDragging(evt)` — boolean read of the same flag. Joint-core itself sets the flag from every action-confirmed drag-start branch: - `ElementView.dragStart` — element move (after `can('elementMove')`). - `LinkView.dragStart` — link move (after `can('linkMove')`). - `LinkView.dragLabelStart` — label move (inside `can('labelMove')` block). - `LinkView.dragArrowheadStart` — arrowhead move (after `can('arrowheadMove')`). - `CellView.dragLinkStart` — magnet→link transition (when the link is actually created, which may be deferred under `magnetThreshold: 'onleave'`). External consumers (plugins / hosts) can now ask "is a drag actually happening?" without inspecting view-level eventData internals. Useful for capture-on-drag, custom cursors, drag-aware UI overlays, etc. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/joint-core/src/dia/CellView.mjs | 1 + packages/joint-core/src/dia/ElementView.mjs | 1 + packages/joint-core/src/dia/LinkView.mjs | 6 ++++++ packages/joint-core/src/dia/Paper.mjs | 12 ++++++++++++ packages/joint-core/types/dia.d.ts | 4 ++++ 5 files changed, 24 insertions(+) diff --git a/packages/joint-core/src/dia/CellView.mjs b/packages/joint-core/src/dia/CellView.mjs index 5caca36480..001cedb25b 100644 --- a/packages/joint-core/src/dia/CellView.mjs +++ b/packages/joint-core/src/dia/CellView.mjs @@ -498,6 +498,7 @@ export const CellView = View.extend({ linkView.notifyPointerdown(evt, x, y); linkView.eventData(evt, linkView.startArrowheadMove('target', { whenNotAllowed: 'remove' })); this.eventData(evt, { linkView }); + this.paper.setDragging(evt); }, dragLink: function(evt, x, y) { diff --git a/packages/joint-core/src/dia/ElementView.mjs b/packages/joint-core/src/dia/ElementView.mjs index 96988bb857..e5f18adc0c 100644 --- a/packages/joint-core/src/dia/ElementView.mjs +++ b/packages/joint-core/src/dia/ElementView.mjs @@ -747,6 +747,7 @@ export const ElementView = CellView.extend({ var view = this.getDelegatedView(); if (!view || !view.can('elementMove')) return; + this.paper.setDragging(evt); this.eventData(evt, { action: DragActions.MOVE, delegatedView: view diff --git a/packages/joint-core/src/dia/LinkView.mjs b/packages/joint-core/src/dia/LinkView.mjs index f23f8236fa..05c2a57a94 100644 --- a/packages/joint-core/src/dia/LinkView.mjs +++ b/packages/joint-core/src/dia/LinkView.mjs @@ -1597,6 +1597,8 @@ export const LinkView = CellView.extend({ if (this.isDefaultInteractionPrevented(evt)) return; + this.paper.setDragging(evt); + var labelNode = evt.currentTarget; var labelIdx = parseInt(labelNode.getAttribute('label-idx'), 10); @@ -1637,6 +1639,8 @@ export const LinkView = CellView.extend({ if (!this.can('arrowheadMove')) return; + this.paper.setDragging(evt); + var arrowheadNode = evt.target; var arrowheadType = arrowheadNode.getAttribute('end'); var data = this.startArrowheadMove(arrowheadType, { ignoreBackwardsCompatibility: true }); @@ -1650,6 +1654,8 @@ export const LinkView = CellView.extend({ if (!this.can('linkMove')) return; + this.paper.setDragging(evt); + this.eventData(evt, { action: 'move', dx: x, diff --git a/packages/joint-core/src/dia/Paper.mjs b/packages/joint-core/src/dia/Paper.mjs index affdcd98ad..651ff64cac 100644 --- a/packages/joint-core/src/dia/Paper.mjs +++ b/packages/joint-core/src/dia/Paper.mjs @@ -3807,6 +3807,18 @@ export const Paper = View.extend({ this.delegateDocumentEvents(null, data); }, + // Mark `evt` as belonging to an active drag. Called from each + // action-confirmed drag-start branch (element, link, label, arrowhead, + // magnet→link). External consumers read via `isDragging(evt)`. + setDragging: function(evt, value = true) { + this.eventData(evt, { isDragging: !!value }); + }, + + // Returns true when a drag has been confirmed for `evt` (see `setDragging`). + isDragging: function(evt) { + return !!this.eventData(evt).isDragging; + }, + // Guard the specified event. If the event should be ignored, guard returns `true`. // Otherwise, it returns `false`. guard: function(evt, view) { diff --git a/packages/joint-core/types/dia.d.ts b/packages/joint-core/types/dia.d.ts index 65884994cb..030224eafa 100644 --- a/packages/joint-core/types/dia.d.ts +++ b/packages/joint-core/types/dia.d.ts @@ -2040,6 +2040,10 @@ export class Paper extends mvc.View { isDefined(defId: string): boolean; + setDragging(evt: dia.Event, value?: boolean): void; + + isDragging(evt: dia.Event): boolean; + getComputedSize(): Size; getArea(): g.Rect; From b9b1b98d5a20d7792e8cf87621a7e5130cb72122 Mon Sep 17 00:00:00 2001 From: Roman Bruckner Date: Mon, 18 May 2026 17:55:47 +0200 Subject: [PATCH 2/4] refactor(joint-core): drop `value` param from `Paper#setDragging` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Always sets to true. The optional value parameter was unused code for a hypothetical reset use case. YAGNI — easy to add later if needed. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/joint-core/src/dia/Paper.mjs | 4 ++-- packages/joint-core/types/dia.d.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/joint-core/src/dia/Paper.mjs b/packages/joint-core/src/dia/Paper.mjs index 651ff64cac..e56898d7b7 100644 --- a/packages/joint-core/src/dia/Paper.mjs +++ b/packages/joint-core/src/dia/Paper.mjs @@ -3810,8 +3810,8 @@ export const Paper = View.extend({ // Mark `evt` as belonging to an active drag. Called from each // action-confirmed drag-start branch (element, link, label, arrowhead, // magnet→link). External consumers read via `isDragging(evt)`. - setDragging: function(evt, value = true) { - this.eventData(evt, { isDragging: !!value }); + setDragging: function(evt) { + this.eventData(evt, { isDragging: true }); }, // Returns true when a drag has been confirmed for `evt` (see `setDragging`). diff --git a/packages/joint-core/types/dia.d.ts b/packages/joint-core/types/dia.d.ts index 030224eafa..0eb5a240a0 100644 --- a/packages/joint-core/types/dia.d.ts +++ b/packages/joint-core/types/dia.d.ts @@ -2040,7 +2040,7 @@ export class Paper extends mvc.View { isDefined(defId: string): boolean; - setDragging(evt: dia.Event, value?: boolean): void; + setDragging(evt: dia.Event): void; isDragging(evt: dia.Event): boolean; From 828caba0ff5903caaf182b324b435b81e7188aa3 Mon Sep 17 00:00:00 2001 From: Roman Bruckner Date: Mon, 18 May 2026 18:05:08 +0200 Subject: [PATCH 3/4] test(joint-core): cover `Paper#setDragging` / `Paper#isDragging` Adds a QUnit module that verifies: - direct API round-trip (set then read); - `isDragging` defaults to false on fresh events; - `ElementView.dragStart` flips the flag after `can('elementMove')` passes; - `isDragging` stays false when `elementMove` is denied; - `LinkView.dragStart` flips the flag after `can('linkMove')`. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/joint-core/test/jointjs/paper.js | 61 +++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/packages/joint-core/test/jointjs/paper.js b/packages/joint-core/test/jointjs/paper.js index d35166fe7e..8e6d4d68be 100644 --- a/packages/joint-core/test/jointjs/paper.js +++ b/packages/joint-core/test/jointjs/paper.js @@ -256,6 +256,67 @@ QUnit.module('paper', function(hooks) { 'Paper area returns correct results for scaled and translated viewport.'); }); + QUnit.module('paper.setDragging() / paper.isDragging()', function() { + + QUnit.test('round-trip via the public API', function(assert) { + const data = {}; + const evt = { target: this.paper.el, type: 'mousedown', data: data }; + assert.notOk(this.paper.isDragging(evt), 'false on a fresh event'); + this.paper.setDragging(evt); + assert.ok(this.paper.isDragging(evt), 'true after setDragging'); + }); + + QUnit.test('isDragging is false until the drag-start gate passes', function(assert) { + const data = {}; + const evt = { target: this.paper.el, type: 'mousedown', data: data }; + // No drag-start has been called; data bag exists but lacks the flag. + assert.notOk(this.paper.isDragging(evt)); + // A different event keeps a clean bag. + const data2 = {}; + const evt2 = { target: this.paper.el, type: 'mousedown', data: data2 }; + assert.notOk(this.paper.isDragging(evt2)); + }); + + QUnit.test('ElementView.dragStart flips isDragging after can(\'elementMove\')', function(assert) { + const el = new joint.shapes.standard.Rectangle(); + el.resize(100, 100); + el.addTo(this.graph); + const view = el.findView(this.paper); + const data = {}; + const evt = { target: view.el, type: 'mousedown', data: data }; + assert.notOk(this.paper.isDragging(evt)); + view.pointerdown(evt, 50, 50); + assert.ok(this.paper.isDragging(evt), 'set after element pointerdown'); + }); + + QUnit.test('isDragging stays false when elementMove is denied', function(assert) { + this.paper.options.interactive = { elementMove: false }; + const el = new joint.shapes.standard.Rectangle(); + el.resize(100, 100); + el.addTo(this.graph); + const view = el.findView(this.paper); + const data = {}; + const evt = { target: view.el, type: 'mousedown', data: data }; + view.pointerdown(evt, 50, 50); + assert.notOk(this.paper.isDragging(evt), 'not set when can(elementMove) returns false'); + }); + + QUnit.test('LinkView.dragStart flips isDragging after can(\'linkMove\')', function(assert) { + const source = new joint.shapes.standard.Rectangle().resize(40, 40).position(0, 0).addTo(this.graph); + const target = new joint.shapes.standard.Rectangle().resize(40, 40).position(200, 200).addTo(this.graph); + const link = new joint.shapes.standard.Link({ + source: { id: source.id }, + target: { id: target.id } + }).addTo(this.graph); + const linkView = link.findView(this.paper); + const data = {}; + const evt = { target: linkView.el, type: 'mousedown', data: data }; + assert.notOk(this.paper.isDragging(evt)); + linkView.pointerdown(evt, 100, 100); + assert.ok(this.paper.isDragging(evt), 'set after link pointerdown'); + }); + }); + QUnit.module('paper.getRestrictedArea()', function() { QUnit.test('function', function(assert) { From 7eab2106383b9ebda28533e5412a80a63ce7ae78 Mon Sep 17 00:00:00 2001 From: Roman Bruckner Date: Mon, 18 May 2026 18:07:40 +0200 Subject: [PATCH 4/4] fix(joint-core): use bare `Event` type in dia.d.ts setDragging/isDragging signatures `dia.Event` was unresolved inside dia.d.ts itself; the file declares `export type Event = mvc.TriggeredEvent` and other methods reference `Event` directly. Match the convention. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/joint-core/types/dia.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/joint-core/types/dia.d.ts b/packages/joint-core/types/dia.d.ts index 0eb5a240a0..74df8a680b 100644 --- a/packages/joint-core/types/dia.d.ts +++ b/packages/joint-core/types/dia.d.ts @@ -2040,9 +2040,9 @@ export class Paper extends mvc.View { isDefined(defId: string): boolean; - setDragging(evt: dia.Event): void; + setDragging(evt: Event): void; - isDragging(evt: dia.Event): boolean; + isDragging(evt: Event): boolean; getComputedSize(): Size;