@signal9/era-ui 1.10.4 → 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.
|
@@ -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
|