@signal9/era-ui 1.10.3 → 1.11.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.
@@ -93,7 +93,36 @@
93
93
  // and are data-pane-control so they never start a pane drag.
94
94
  type ResizeDir = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';
95
95
  const RESIZE_HANDLES: { dir: ResizeDir; cursor: string; place: string }[] = [
96
- // corners: a square just OUTSIDE each corner (diagonal gap filler)
96
+ // edges FIRST so the corners (rendered after) paint on top of them and win
97
+ // the hit test where they overlap — a corner press always resizes
98
+ // diagonally, never along a single axis. n + w stay OUTSIDE only
99
+ // (translate ±100%); s + e straddle (translate ±50%) so the right and
100
+ // bottom also reach ~6px inward.
101
+ {
102
+ dir: 'n',
103
+ cursor: 'cursor-ns-resize',
104
+ place: 'top:0;left:0;right:0;translate:0 -100%;height:calc(var(--era-h-md)/4)'
105
+ },
106
+ {
107
+ dir: 's',
108
+ cursor: 'cursor-ns-resize',
109
+ place: 'bottom:0;left:0;right:0;translate:0 50%;height:calc(var(--era-h-md)/2)'
110
+ },
111
+ {
112
+ dir: 'w',
113
+ cursor: 'cursor-ew-resize',
114
+ place: 'left:0;top:0;bottom:0;translate:-100% 0;width:calc(var(--era-h-md)/4)'
115
+ },
116
+ {
117
+ // Inset the top by the handle-bar height so its inward reach stays clear
118
+ // of the drag bar (keeping the whole bar draggable).
119
+ dir: 'e',
120
+ cursor: 'cursor-ew-resize',
121
+ place:
122
+ 'right:0;top:var(--era-h-sm);bottom:0;translate:50% 0;width:calc(var(--era-h-md)/2)'
123
+ },
124
+ // corners LAST — a square at each corner (diagonal gap filler), painted
125
+ // above the edges so the red grab zone is always on top of the blue.
97
126
  {
98
127
  dir: 'nw',
99
128
  cursor: 'cursor-nwse-resize',
@@ -119,31 +148,6 @@
119
148
  cursor: 'cursor-nwse-resize',
120
149
  place:
121
150
  'bottom:0;right:0;translate:50% 50%;width:calc(var(--era-h-md)/2);height:calc(var(--era-h-md)/2)'
122
- },
123
- // edges. n + w stay OUTSIDE only (translate ±100%); s + e straddle
124
- // (translate ±50%) so the right and bottom also reach ~6px inward.
125
- {
126
- dir: 'n',
127
- cursor: 'cursor-ns-resize',
128
- place: 'top:0;left:0;right:0;translate:0 -100%;height:calc(var(--era-h-md)/4)'
129
- },
130
- {
131
- dir: 's',
132
- cursor: 'cursor-ns-resize',
133
- place: 'bottom:0;left:0;right:0;translate:0 50%;height:calc(var(--era-h-md)/2)'
134
- },
135
- {
136
- dir: 'w',
137
- cursor: 'cursor-ew-resize',
138
- place: 'left:0;top:0;bottom:0;translate:-100% 0;width:calc(var(--era-h-md)/4)'
139
- },
140
- {
141
- // Inset the top by the handle-bar height so its inward reach stays clear
142
- // of the drag bar (keeping the whole bar draggable).
143
- dir: 'e',
144
- cursor: 'cursor-ew-resize',
145
- place:
146
- 'right:0;top:var(--era-h-sm);bottom:0;translate:50% 0;width:calc(var(--era-h-md)/2)'
147
151
  }
148
152
  ];
149
153
 
@@ -6,78 +6,29 @@
6
6
  let { ref = $bindable(null), class: className, ...restProps }: TabsPrimitive.ListProps = $props();
7
7
 
8
8
  // When the pane is too narrow to fit every tab, the strip scrolls (min-w-0 +
9
- // overflow-x-auto — chromeless via scrollbar-none; touch pans natively).
10
- // data-pane-control carves it out of the pane's drag zone so a sideways drag
11
- // scrolls the tabs instead of moving the pane.
9
+ // overflow-x-auto — chromeless via scrollbar-none). The strip stays IN the
10
+ // pane's drag zone (no data-pane-control) so grabbing a tab drags the pane;
11
+ // overflow is scrolled by the mouse wheel (below) or native touch panning
12
+ // (touch-pan-x), not by a pointer drag — a drag moves the pane instead.
12
13
  //
13
- // Mouse drag-to-scroll: grab anywhere in the strip and slide. A small
14
- // threshold tells a scroll-drag from a tab click; once we've dragged, the
15
- // trailing click is swallowed (capture phase) so a tab isn't toggled. Touch
16
- // and pen fall through to native panning. A wheel handler (below) maps the
17
- // vertical mouse wheel onto horizontal scroll while hovering the strip.
18
- let startX = 0;
19
- let startScroll = 0;
20
- let dragging = false;
21
- let dragged = false;
22
-
23
- function onpointerdown(e: PointerEvent) {
24
- if (!ref || e.pointerType !== 'mouse' || e.button !== 0) return;
25
- dragging = true;
26
- dragged = false;
27
- startX = e.clientX;
28
- startScroll = ref.scrollLeft;
29
- }
30
-
31
- function onpointermove(e: PointerEvent) {
32
- if (!dragging || !ref) return;
33
- const dx = e.clientX - startX;
34
- if (!dragged && Math.abs(dx) > 3) {
35
- dragged = true;
36
- ref.setPointerCapture(e.pointerId);
37
- }
38
- if (dragged) ref.scrollLeft = startScroll - dx;
39
- }
40
-
41
- function endDrag(e: PointerEvent) {
42
- if (!dragging) return;
43
- dragging = false;
44
- if (ref?.hasPointerCapture(e.pointerId)) ref.releasePointerCapture(e.pointerId);
45
- }
46
-
47
- // Two listeners that need non-default registration (capture / non-passive),
48
- // so they're added imperatively rather than as element props:
49
- // - click (capture): swallow the click that follows a scroll-drag so the
50
- // tab under the cursor isn't selected — beats the trigger's own handler.
51
- // - wheel (non-passive): translate a vertical mouse wheel into horizontal
52
- // scroll while hovering the strip, and only consume the event when the
53
- // strip can actually move that way (otherwise the page scrolls as usual).
14
+ // The wheel listener needs non-passive registration (it calls preventDefault),
15
+ // so it's added imperatively rather than as an element prop: it translates a
16
+ // vertical mouse wheel into horizontal scroll while hovering the strip, and
17
+ // keeps the wheel captured even at an end so the page never takes over until
18
+ // the cursor leaves.
54
19
  $effect(() => {
55
20
  const node = ref;
56
21
  if (!node) return;
57
- const swallow = (e: MouseEvent) => {
58
- if (dragged) {
59
- e.stopPropagation();
60
- e.preventDefault();
61
- dragged = false;
62
- }
63
- };
64
22
  const onWheel = (e: WheelEvent) => {
65
23
  const max = node.scrollWidth - node.clientWidth;
66
24
  if (max <= 0) return; // no overflow — let the page scroll
67
25
  const delta = Math.abs(e.deltaY) >= Math.abs(e.deltaX) ? e.deltaY : e.deltaX;
68
26
  if (delta === 0) return;
69
27
  node.scrollLeft = Math.max(0, Math.min(max, node.scrollLeft + delta));
70
- // Keep the wheel captured on the strip while it's hovered — even once
71
- // an end is reached — so the page never takes over until the cursor
72
- // leaves. (No edge bubbling.)
73
28
  e.preventDefault();
74
29
  };
75
- node.addEventListener('click', swallow, true);
76
30
  node.addEventListener('wheel', onWheel, { passive: false });
77
- return () => {
78
- node.removeEventListener('click', swallow, true);
79
- node.removeEventListener('wheel', onWheel);
80
- };
31
+ return () => node.removeEventListener('wheel', onWheel);
81
32
  });
82
33
  </script>
83
34
 
@@ -86,11 +37,6 @@
86
37
  descenders (g/y/p) room inside the clip box (which is the padding edge). -->
87
38
  <Tabs.List
88
39
  bind:ref
89
- data-pane-control
90
- {onpointerdown}
91
- {onpointermove}
92
- onpointerup={endDrag}
93
- onpointercancel={endDrag}
94
40
  class={cn(
95
41
  'scrollbar-none flex min-w-0 touch-pan-x items-center gap-(--era-xs-inset-sm) overflow-x-auto py-0.5',
96
42
  className
@@ -11,11 +11,12 @@
11
11
  </script>
12
12
 
13
13
  <!-- A tab trigger (era-ui Tabs.Trigger) styled as a text pill for the handle
14
- Bar. data-pane-control carves it out of the drag zone so it clicks, not
15
- drags. Active state paints with the hover token. -->
14
+ Bar. It stays IN the drag zone (no data-pane-control) so grabbing a tab
15
+ drags the pane, while a clean click still selects it — neodrag starts a
16
+ drag only on movement and suppresses the trailing click after a real one.
17
+ Active state paints with the hover token. -->
16
18
  <Tabs.Trigger
17
19
  bind:ref
18
- data-pane-control
19
20
  class={cn(
20
21
  'flex cursor-pointer items-center gap-(--era-gap) rounded-(--era-rd-xs) px-(--era-inset-sm) py-px 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
22
  className
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "1.10.3",
3
+ "version": "1.11.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",