Version: getartisanflow/wireflow ^0.1.2-alpha (bundle file: dist/alpineflow.bundle.esm.js)
What's happening
When the user drags from a source handle to draw an edge, three document-level pointer listeners are registered (pointermove, pointerup, pointercancel). The normal-drop cleanup handler Q removes pointermove and pointerup, but not pointercancel. Each subsequent edge-drag adds a fresh triplet on top, leaving an orphan pointercancel listener accumulating on document after each drop.
Code
In vendor/getartisanflow/wireflow/dist/alpineflow.bundle.esm.js around line 3789, the connection-drag pointerup handler:
```js
}, Q = (Y) => {
if (x?.stop(), x = null,
document.removeEventListener("pointermove", R),
document.removeEventListener("pointerup", Q),
// ❌ missing: document.removeEventListener("pointercancel", Q)
L = null, G) {
```
Compare to the full-cleanup function L defined ~20 lines below (line ~3911), which correctly removes all three:
```js
L = () => {
document.removeEventListener("pointermove", R),
document.removeEventListener("pointerup", Q),
document.removeEventListener("pointercancel", Q),
...
}
```
And to the node-drag handler ie near line 4090, which also correctly removes all three on pointerup. So the omission appears to be an oversight specific to the edge-drag handler.
Proposed fix
One line — add pointercancel removal to Q:
```diff
}, Q = (Y) => {
if (x?.stop(), x = null,
document.removeEventListener("pointermove", R),
document.removeEventListener("pointerup", Q),
-
document.removeEventListener(\"pointercancel\", Q),
L = null, G) {
```
Reproduction
- Open any wireflow canvas with the connection feature enabled.
- Drag from a node's source handle to a target handle and drop, repeatedly (10+ times).
- In Chrome DevTools console:
getEventListeners(document).
- You'll see an increasing number of
pointercancel entries — one per successful edge drag — none from prior drags removed.
Impact
- Memory leak: each orphan listener holds the closure (including refs to
_, se, etc.) so they accumulate over a session.
- If a real
pointercancel ever fires later (touch interrupted, OS gesture, modal stealing focus), every accumulated handler runs — most internal state is idempotent via optional chaining, but _._emit('connect-end', { connection: null, ... }) would fire N times in a row.
For typical mouse-driven usage on desktop this isn't catastrophic (drag counts per session are low and pointercancel rarely fires), but on touch devices where pointercancel happens more often, the duplicate connect-end events could be observable downstream.
Environment
- Laravel 12.42.0 / Livewire 3.7.1
- WireFlow integrated via the docs' static-mode pattern + render-hook plugin registration for Filament panels
- Reproduced on Chrome 131 / macOS
Happy to PR the one-line fix if the source repo is open and you'd prefer that path.
Version:
getartisanflow/wireflow ^0.1.2-alpha(bundle file:dist/alpineflow.bundle.esm.js)What's happening
When the user drags from a source handle to draw an edge, three document-level pointer listeners are registered (
pointermove,pointerup,pointercancel). The normal-drop cleanup handlerQremovespointermoveandpointerup, but notpointercancel. Each subsequent edge-drag adds a fresh triplet on top, leaving an orphanpointercancellistener accumulating ondocumentafter each drop.Code
In
vendor/getartisanflow/wireflow/dist/alpineflow.bundle.esm.jsaround line 3789, the connection-drag pointerup handler:```js
}, Q = (Y) => {
if (x?.stop(), x = null,
document.removeEventListener("pointermove", R),
document.removeEventListener("pointerup", Q),
// ❌ missing: document.removeEventListener("pointercancel", Q)
L = null, G) {
```
Compare to the full-cleanup function
Ldefined ~20 lines below (line ~3911), which correctly removes all three:```js
L = () => {
document.removeEventListener("pointermove", R),
document.removeEventListener("pointerup", Q),
document.removeEventListener("pointercancel", Q),
...
}
```
And to the node-drag handler
ienear line 4090, which also correctly removes all three on pointerup. So the omission appears to be an oversight specific to the edge-drag handler.Proposed fix
One line — add
pointercancelremoval toQ:```diff
}, Q = (Y) => {
if (x?.stop(), x = null,
document.removeEventListener("pointermove", R),
document.removeEventListener("pointerup", Q),
```
Reproduction
getEventListeners(document).pointercancelentries — one per successful edge drag — none from prior drags removed.Impact
_,se, etc.) so they accumulate over a session.pointercancelever fires later (touch interrupted, OS gesture, modal stealing focus), every accumulated handler runs — most internal state is idempotent via optional chaining, but_._emit('connect-end', { connection: null, ... })would fire N times in a row.For typical mouse-driven usage on desktop this isn't catastrophic (drag counts per session are low and pointercancel rarely fires), but on touch devices where pointercancel happens more often, the duplicate
connect-endevents could be observable downstream.Environment
Happy to PR the one-line fix if the source repo is open and you'd prefer that path.