@signal9/era-ui 1.7.1 → 1.7.3

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.
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLButtonAttributes } from 'svelte/elements';
3
3
  import { Button } from '../button';
4
+ import { cn } from '../../utils/index.js';
4
5
  import X from '@lucide/svelte/icons/x';
5
6
 
6
7
  let {
@@ -17,13 +18,16 @@
17
18
  corner). A clean click still closes — neodrag only starts a drag on pointer
18
19
  movement, and it suppresses the trailing click after an actual drag, so a
19
20
  stationary click fires immediately with no delay. -->
21
+ <!-- Sized to h-xxs (matching the tab pills' highlight height) rather than the
22
+ icon default (h-xs), so the close hover box and the active-tab highlight are
23
+ the same size in the handle bar; aspect-square keeps it a square. -->
20
24
  <Button
21
25
  bind:ref
22
26
  icon
23
27
  size="icon"
24
28
  tone="destructive"
25
29
  aria-label="Close"
26
- class={className}
30
+ class={cn('h-(--era-h-xxs) min-w-(--era-h-xxs)', className)}
27
31
  {...restProps}
28
32
  >
29
33
  <X class="size-(--era-h-xs)" />
@@ -65,13 +65,21 @@
65
65
 
66
66
  let resizing = $state(false);
67
67
 
68
- // neodrag's `controls` plugin caches the handle's allow-zone at setup and
69
- // only refreshes it mid-drag. After a resize the widened handle bar would
70
- // keep the old (narrow) zone, so its new area wouldn't initiate a drag until
71
- // the next one. Bumping this on resize-end re-creates the draggable
72
- // attachment below, re-running controls' setup so the zone matches the
73
- // current width the whole bar stays a handle at any size.
74
- let dragGeneration = $state(0);
68
+ // The handle bar is the drag zone. neodrag's `controls` plugin measures that
69
+ // zone once at setup and only refreshes it mid-drag, so after a resize the
70
+ // widened bar keeps the old (narrow) zone and its new area won't start a drag.
71
+ // Recompute it by dropping controls while resizing and re-adding on release: a
72
+ // Compartment null→plugin swap re-runs the plugin's setup, re-measuring the
73
+ // handle at its new width without re-initializing the whole draggable. You
74
+ // can't drag mid-resize anyway (the grip owns the pointer).
75
+ const controlsComp = Compartment.of(() =>
76
+ resizing
77
+ ? null
78
+ : controls({
79
+ allow: ControlFrom.selector('[data-pane-handle]'),
80
+ block: ControlFrom.selector('[data-pane-control]')
81
+ })
82
+ );
75
83
 
76
84
  // Corner-grip resize. The grip sits outside the handle allow-zone and is
77
85
  // marked data-pane-control, so a drag can never start from it. Pointer
@@ -96,50 +104,22 @@
96
104
  };
97
105
  };
98
106
  const onUp = (ev: PointerEvent) => {
107
+ // Clearing `resizing` re-adds controls (see controlsComp), re-measuring
108
+ // the handle zone at the new size.
99
109
  resizing = false;
100
110
  grip.releasePointerCapture(ev.pointerId);
101
111
  grip.removeEventListener('pointermove', onMove);
102
112
  grip.removeEventListener('pointerup', onUp);
103
- // Recompute the handle drag-zone at the new size (see dragGeneration).
104
- dragGeneration += 1;
105
113
  };
106
114
  grip.addEventListener('pointermove', onMove);
107
115
  grip.addEventListener('pointerup', onUp);
108
116
  }
109
-
110
- // Reading dragGeneration ties this attachment's identity to it, so a resize
111
- // re-creates the draggable and re-runs controls' setup (fresh handle zone).
112
- // Position/disabled stay reactive without re-init via their Compartments.
113
- const paneDraggable = $derived.by(() => {
114
- void dragGeneration;
115
- return draggable(() => [
116
- // The handle bar initiates a drag; interactive controls inside it
117
- // (close button, tab triggers) are carved back out so they click, not drag.
118
- controls({
119
- allow: ControlFrom.selector('[data-pane-handle]'),
120
- block: ControlFrom.selector('[data-pane-control]')
121
- }),
122
- // Exposes data-neodrag-state="idle | dragging" (and a drag count) on
123
- // the root — the hover-highlight and layer-promotion hooks hang off it.
124
- stateMarker(),
125
- events({
126
- onDragStart,
127
- onDrag: (data) => {
128
- sync(data);
129
- onDrag?.(data);
130
- },
131
- onDragEnd: (data) => {
132
- sync(data);
133
- onDragEnd?.(data);
134
- }
135
- }),
136
- positionComp,
137
- disabledComp,
138
- ...(typeof plugins === 'function' ? plugins() : plugins)
139
- ]);
140
- });
141
117
  </script>
142
118
 
119
+ <!-- Size via style: DIRECTIVES, not a style="…" string. neodrag positions the
120
+ pane by writing element.style.transform imperatively; a whole-attribute
121
+ style binding would clobber that transform on every size change, snapping
122
+ the pane to 0,0 mid-resize. Directives set individual properties only. -->
143
123
  <div
144
124
  bind:this={ref}
145
125
  class={cn(
@@ -154,9 +134,31 @@
154
134
  'data-[neodrag-state=dragging]:will-change-transform',
155
135
  className
156
136
  )}
157
- style={size ? `width:${size.width}px;height:${size.height}px` : undefined}
158
137
  {...restProps}
159
- {@attach paneDraggable}
138
+ style:width={size ? `${size.width}px` : undefined}
139
+ style:height={size ? `${size.height}px` : undefined}
140
+ {@attach draggable(() => [
141
+ // controlsComp restricts drags to the handle bar (and carves interactive
142
+ // controls back out); it re-measures the handle after a resize. See above.
143
+ controlsComp,
144
+ // Exposes data-neodrag-state="idle | dragging" on the root — the drag-only
145
+ // layer-promotion hook hangs off it.
146
+ stateMarker(),
147
+ events({
148
+ onDragStart,
149
+ onDrag: (data) => {
150
+ sync(data);
151
+ onDrag?.(data);
152
+ },
153
+ onDragEnd: (data) => {
154
+ sync(data);
155
+ onDragEnd?.(data);
156
+ }
157
+ }),
158
+ positionComp,
159
+ disabledComp,
160
+ ...(typeof plugins === 'function' ? plugins() : plugins)
161
+ ])}
160
162
  >
161
163
  {@render children?.()}
162
164
  {#if resizable}
@@ -17,7 +17,7 @@
17
17
  bind:ref
18
18
  data-pane-control
19
19
  class={cn(
20
- 'flex cursor-pointer items-center gap-(--era-gap) rounded-(--era-rd-xs) px-(--era-inset-sm) py-0.5 text-body leading-none text-muted outline-none select-none hover:text-fg focus-visible:bg-(--era-highlight) data-[state=active]:bg-hover data-[state=active]:text-bright',
20
+ 'flex h-(--era-h-xxs) cursor-pointer items-center gap-(--era-gap) rounded-(--era-rd-xs) px-(--era-inset-sm) text-body leading-none text-muted outline-none select-none hover:text-fg focus-visible:bg-(--era-highlight) data-[state=active]:bg-hover data-[state=active]:text-bright',
21
21
  className
22
22
  )}
23
23
  {...restProps}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",