@signal9/era-ui 1.14.0 → 1.14.1

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.
@@ -3,8 +3,6 @@
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
4
  import {
5
5
  draggable,
6
- controls,
7
- ControlFrom,
8
6
  disabled as disabledPlugin,
9
7
  events,
10
8
  position as positionPlugin,
@@ -65,11 +63,31 @@
65
63
 
66
64
  let resizing = $state(false);
67
65
 
68
- // Ctrl-drag: while Ctrl is held the WHOLE pane becomes a drag handle, not just
69
- // the bar. Track the modifier globally (keydown/keyup) so the controls zone
70
- // below is already widened by the time a pointerdown lands; blur resets it so a
71
- // Ctrl released off-window doesn't stick. Assigning the same boolean is a no-op
72
- // in Svelte, so key auto-repeat doesn't thrash the Compartment.
66
+ // What may start a drag, decided PER POINTERDOWN from the real event no
67
+ // cached zones, no reactive state, nothing to fall out of sync. neodrag's
68
+ // `shouldStart` hook runs on the initiating pointer event; returning false
69
+ // vetoes the drag. Because this is a pure function of that event, it's correct
70
+ // after resizes, re-renders, or anything else — the ctrl-drag can't break.
71
+ // • Ctrl held → the WHOLE pane is a handle (read live off the event).
72
+ // • otherwise → only the handle bar, and never a resize handle / control
73
+ // (those carry data-pane-control).
74
+ const dragGate: Plugin = {
75
+ name: 'era:pane-drag-gate',
76
+ shouldStart(_ctx, _state, event) {
77
+ const e = event as PointerEvent;
78
+ const target = e.target as Element | null;
79
+ if (!target) return false;
80
+ if (e.ctrlKey) return true;
81
+ if (target.closest('[data-pane-control]')) return false;
82
+ return target.closest('[data-pane-handle]') != null;
83
+ }
84
+ };
85
+
86
+ // Ctrl held is ALSO tracked reactively — but only for the visual affordances
87
+ // (grab cursor, no text selection, inert tabs), never for the drag itself. If
88
+ // this lags the keypress, the drag still works (the gate reads the event); the
89
+ // styling just catches up a frame later. blur resets it so a Ctrl released
90
+ // off-window doesn't stick.
73
91
  let ctrlHeld = $state(false);
74
92
  $effect(() => {
75
93
  const sync = (e: KeyboardEvent) => (ctrlHeld = e.ctrlKey);
@@ -84,31 +102,6 @@
84
102
  };
85
103
  });
86
104
 
87
- // The handle bar is the drag zone. neodrag's `controls` plugin measures that
88
- // zone once at setup and only refreshes it mid-drag, so after a resize the
89
- // widened bar keeps the old (narrow) zone and its new area won't start a drag.
90
- // Recompute it by dropping controls (→ null) and re-adding: a Compartment
91
- // null→plugin swap re-runs the plugin's setup, re-measuring the handle. Two
92
- // states drop it to null:
93
- // • resizing — the grip owns the pointer, you can't drag anyway.
94
- // • Ctrl held — no allow/block restriction, so the WHOLE pane is a drag handle
95
- // (ctrl-drag). Resize handles keep resizing because their own pointerdown
96
- // stops propagation before neodrag sees it.
97
- // Read BOTH flags into locals first: `resizing || ctrlHeld` would short-circuit
98
- // while resizing and drop the ctrlHeld reactive dependency, so a resize could
99
- // leave ctrl-drag un-tracked until something else re-ran this. Assigning both
100
- // forces the compartment to always depend on both.
101
- const controlsComp = Compartment.of(() => {
102
- const isResizing = resizing;
103
- const isCtrl = ctrlHeld;
104
- return isResizing || isCtrl
105
- ? null
106
- : controls({
107
- allow: ControlFrom.selector('[data-pane-handle]'),
108
- block: ControlFrom.selector('[data-pane-control]')
109
- });
110
- });
111
-
112
105
  // Dev aid: tint the grab zones so you can see them while tuning geometry. Off
113
106
  // in production — flip to true when adjusting the handle placement above.
114
107
  const SHOW_RESIZE_ZONES = false;
@@ -252,8 +245,8 @@
252
245
  ref!.style.marginLeft = '';
253
246
  ref!.style.marginTop = '';
254
247
  }
255
- // Clearing `resizing` re-adds controls (see controlsComp), re-measuring
256
- // the handle zone at the new size.
248
+ // `resizing` now only suppresses the ctrl grab-cursor while the grip is
249
+ // active (the drag gate reads the DOM live, so nothing to re-measure).
257
250
  resizing = false;
258
251
  handle.releasePointerCapture(ev.pointerId);
259
252
  handle.removeEventListener('pointermove', onMove);
@@ -301,9 +294,9 @@
301
294
  style:width={size ? `${size.width}px` : undefined}
302
295
  style:height={size ? `${size.height}px` : undefined}
303
296
  {@attach draggable(() => [
304
- // controlsComp restricts drags to the handle bar (and carves interactive
305
- // controls back out); it re-measures the handle after a resize. See above.
306
- controlsComp,
297
+ // Gates which pointerdown starts a drag: handle bar only, or the whole pane
298
+ // while Ctrl is held. Stateless decided live off the event. See above.
299
+ dragGate,
307
300
  // Exposes data-neodrag-state="idle | dragging" on the root — the drag-only
308
301
  // layer-promotion hook hangs off it.
309
302
  stateMarker(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "1.14.0",
3
+ "version": "1.14.1",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",