@signal9/era-ui 1.10.4 → 1.11.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.
|
@@ -84,13 +84,14 @@
|
|
|
84
84
|
// TEMP: show the grab zones while developing. Flip to false to hide them.
|
|
85
85
|
const SHOW_RESIZE_ZONES = true;
|
|
86
86
|
|
|
87
|
-
// Resize handles
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
87
|
+
// Resize handles. Every strip protrudes xs-inset-sm (= sp/2, the same inset
|
|
88
|
+
// that gaps the close button from the pane edge) OUTSIDE the pane. n + w are
|
|
89
|
+
// that thin outward-only. s + e straddle (translate ±50%, double thickness)
|
|
90
|
+
// so they also reach xs-inset-sm INWARD — on the right, filling the close-
|
|
91
|
+
// button gap up to the button's edge. Corners are h-md/2 squares filling the
|
|
92
|
+
// four diagonal gaps. `dir` is any of n/s/e/w; a 2-letter dir is a corner.
|
|
93
|
+
// Handles live in a layer aligned to the pane's border box (markup) and are
|
|
94
|
+
// data-pane-control so they never start a pane drag.
|
|
94
95
|
type ResizeDir = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';
|
|
95
96
|
const RESIZE_HANDLES: { dir: ResizeDir; cursor: string; place: string }[] = [
|
|
96
97
|
// edges FIRST so the corners (rendered after) paint on top of them and win
|
|
@@ -101,25 +102,25 @@
|
|
|
101
102
|
{
|
|
102
103
|
dir: 'n',
|
|
103
104
|
cursor: 'cursor-ns-resize',
|
|
104
|
-
place: 'top:0;left:0;right:0;translate:0 -100%;height:
|
|
105
|
+
place: 'top:0;left:0;right:0;translate:0 -100%;height:var(--era-xs-inset-sm)'
|
|
105
106
|
},
|
|
106
107
|
{
|
|
107
108
|
dir: 's',
|
|
108
109
|
cursor: 'cursor-ns-resize',
|
|
109
|
-
place: 'bottom:0;left:0;right:0;translate:0 50%;height:calc(var(--era-
|
|
110
|
+
place: 'bottom:0;left:0;right:0;translate:0 50%;height:calc(var(--era-xs-inset-sm)*2)'
|
|
110
111
|
},
|
|
111
112
|
{
|
|
112
113
|
dir: 'w',
|
|
113
114
|
cursor: 'cursor-ew-resize',
|
|
114
|
-
place: 'left:0;top:0;bottom:0;translate:-100% 0;width:
|
|
115
|
+
place: 'left:0;top:0;bottom:0;translate:-100% 0;width:var(--era-xs-inset-sm)'
|
|
115
116
|
},
|
|
116
117
|
{
|
|
117
|
-
//
|
|
118
|
-
//
|
|
118
|
+
// Rises to top:0 (no inset) so its inward half overlays the close-button
|
|
119
|
+
// row — reaching exactly the button's edge, filling the gap on the left
|
|
120
|
+
// of the pane edge without covering the button.
|
|
119
121
|
dir: 'e',
|
|
120
122
|
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
|
+
place: 'right:0;top:0;bottom:0;translate:50% 0;width:calc(var(--era-xs-inset-sm)*2)'
|
|
123
124
|
},
|
|
124
125
|
// corners LAST — a square at each corner (diagonal gap filler), painted
|
|
125
126
|
// above the edges so the red grab zone is always on top of the blue.
|
|
@@ -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
|
|
10
|
-
// data-pane-control
|
|
11
|
-
//
|
|
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
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
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.
|
|
15
|
-
drags
|
|
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
|