@delightstack/components 0.1.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.
- package/LICENSE +21 -0
- package/README.md +136 -0
- package/SKILL.md +149 -0
- package/bin/agents.js +63 -0
- package/dist/actions/Alert.svelte +202 -0
- package/dist/actions/Alert.svelte.d.ts +36 -0
- package/dist/actions/Alert.svelte.d.ts.map +1 -0
- package/dist/actions/Button.svelte +1450 -0
- package/dist/actions/Button.svelte.d.ts +56 -0
- package/dist/actions/Button.svelte.d.ts.map +1 -0
- package/dist/actions/ButtonGroup.svelte +111 -0
- package/dist/actions/ButtonGroup.svelte.d.ts +41 -0
- package/dist/actions/ButtonGroup.svelte.d.ts.map +1 -0
- package/dist/actions/CommandPalette.svelte +939 -0
- package/dist/actions/CommandPalette.svelte.d.ts +37 -0
- package/dist/actions/CommandPalette.svelte.d.ts.map +1 -0
- package/dist/actions/ContextMenu.svelte +138 -0
- package/dist/actions/ContextMenu.svelte.d.ts +54 -0
- package/dist/actions/ContextMenu.svelte.d.ts.map +1 -0
- package/dist/actions/Modal.svelte +474 -0
- package/dist/actions/Modal.svelte.d.ts +28 -0
- package/dist/actions/Modal.svelte.d.ts.map +1 -0
- package/dist/actions/Popover.svelte +1214 -0
- package/dist/actions/Popover.svelte.d.ts +31 -0
- package/dist/actions/Popover.svelte.d.ts.map +1 -0
- package/dist/actions/Portal.svelte +80 -0
- package/dist/actions/Portal.svelte.d.ts +17 -0
- package/dist/actions/Portal.svelte.d.ts.map +1 -0
- package/dist/actions/ThemeToggle.svelte +345 -0
- package/dist/actions/ThemeToggle.svelte.d.ts +15 -0
- package/dist/actions/ThemeToggle.svelte.d.ts.map +1 -0
- package/dist/actions/index.d.ts +13 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +10 -0
- package/dist/actions/scrollbar.d.ts +48 -0
- package/dist/actions/scrollbar.d.ts.map +1 -0
- package/dist/actions/scrollbar.js +404 -0
- package/dist/display/Accordion.svelte +586 -0
- package/dist/display/Accordion.svelte.d.ts +41 -0
- package/dist/display/Accordion.svelte.d.ts.map +1 -0
- package/dist/display/Avatar.svelte +527 -0
- package/dist/display/Avatar.svelte.d.ts +22 -0
- package/dist/display/Avatar.svelte.d.ts.map +1 -0
- package/dist/display/AvatarGroup.svelte +298 -0
- package/dist/display/AvatarGroup.svelte.d.ts +31 -0
- package/dist/display/AvatarGroup.svelte.d.ts.map +1 -0
- package/dist/display/Calendar.svelte +1366 -0
- package/dist/display/Calendar.svelte.d.ts +58 -0
- package/dist/display/Calendar.svelte.d.ts.map +1 -0
- package/dist/display/Chart.svelte +1426 -0
- package/dist/display/Chart.svelte.d.ts +35 -0
- package/dist/display/Chart.svelte.d.ts.map +1 -0
- package/dist/display/Code.svelte +780 -0
- package/dist/display/Code.svelte.d.ts +19 -0
- package/dist/display/Code.svelte.d.ts.map +1 -0
- package/dist/display/Comparison.svelte +686 -0
- package/dist/display/Comparison.svelte.d.ts +22 -0
- package/dist/display/Comparison.svelte.d.ts.map +1 -0
- package/dist/display/Counter.svelte +285 -0
- package/dist/display/Counter.svelte.d.ts +21 -0
- package/dist/display/Counter.svelte.d.ts.map +1 -0
- package/dist/display/Expand.svelte +48 -0
- package/dist/display/Expand.svelte.d.ts +9 -0
- package/dist/display/Expand.svelte.d.ts.map +1 -0
- package/dist/display/List.svelte +294 -0
- package/dist/display/List.svelte.d.ts +40 -0
- package/dist/display/List.svelte.d.ts.map +1 -0
- package/dist/display/ListContextReset.svelte +19 -0
- package/dist/display/ListContextReset.svelte.d.ts +7 -0
- package/dist/display/ListContextReset.svelte.d.ts.map +1 -0
- package/dist/display/ListItem.svelte +834 -0
- package/dist/display/ListItem.svelte.d.ts +22 -0
- package/dist/display/ListItem.svelte.d.ts.map +1 -0
- package/dist/display/QR.svelte +1193 -0
- package/dist/display/QR.svelte.d.ts +23 -0
- package/dist/display/QR.svelte.d.ts.map +1 -0
- package/dist/display/SplitPane.svelte +744 -0
- package/dist/display/SplitPane.svelte.d.ts +25 -0
- package/dist/display/SplitPane.svelte.d.ts.map +1 -0
- package/dist/display/Stat.svelte +439 -0
- package/dist/display/Stat.svelte.d.ts +24 -0
- package/dist/display/Stat.svelte.d.ts.map +1 -0
- package/dist/display/Table.svelte +4654 -0
- package/dist/display/Table.svelte.d.ts +249 -0
- package/dist/display/Table.svelte.d.ts.map +1 -0
- package/dist/display/TableCellEditor.svelte +935 -0
- package/dist/display/TableCellEditor.svelte.d.ts +58 -0
- package/dist/display/TableCellEditor.svelte.d.ts.map +1 -0
- package/dist/display/Timeline.svelte +1258 -0
- package/dist/display/Timeline.svelte.d.ts +43 -0
- package/dist/display/Timeline.svelte.d.ts.map +1 -0
- package/dist/display/Tree.svelte +1740 -0
- package/dist/display/Tree.svelte.d.ts +74 -0
- package/dist/display/Tree.svelte.d.ts.map +1 -0
- package/dist/display/Typewriter.svelte +338 -0
- package/dist/display/Typewriter.svelte.d.ts +22 -0
- package/dist/display/Typewriter.svelte.d.ts.map +1 -0
- package/dist/display/index.d.ts +24 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +18 -0
- package/dist/feedback/Callout.svelte +529 -0
- package/dist/feedback/Callout.svelte.d.ts +24 -0
- package/dist/feedback/Callout.svelte.d.ts.map +1 -0
- package/dist/feedback/Confetti.svelte +631 -0
- package/dist/feedback/Confetti.svelte.d.ts +90 -0
- package/dist/feedback/Confetti.svelte.d.ts.map +1 -0
- package/dist/feedback/Progress.svelte +382 -0
- package/dist/feedback/Progress.svelte.d.ts +25 -0
- package/dist/feedback/Progress.svelte.d.ts.map +1 -0
- package/dist/feedback/Toast.svelte +967 -0
- package/dist/feedback/Toast.svelte.d.ts +54 -0
- package/dist/feedback/Toast.svelte.d.ts.map +1 -0
- package/dist/feedback/index.d.ts +7 -0
- package/dist/feedback/index.d.ts.map +1 -0
- package/dist/feedback/index.js +4 -0
- package/dist/form/Checkbox.svelte +449 -0
- package/dist/form/Checkbox.svelte.d.ts +27 -0
- package/dist/form/Checkbox.svelte.d.ts.map +1 -0
- package/dist/form/Fieldset.svelte +410 -0
- package/dist/form/Fieldset.svelte.d.ts +22 -0
- package/dist/form/Fieldset.svelte.d.ts.map +1 -0
- package/dist/form/FileUpload.svelte +934 -0
- package/dist/form/FileUpload.svelte.d.ts +41 -0
- package/dist/form/FileUpload.svelte.d.ts.map +1 -0
- package/dist/form/Form.svelte +530 -0
- package/dist/form/Form.svelte.d.ts +120 -0
- package/dist/form/Form.svelte.d.ts.map +1 -0
- package/dist/form/Input.svelte +2858 -0
- package/dist/form/Input.svelte.d.ts +66 -0
- package/dist/form/Input.svelte.d.ts.map +1 -0
- package/dist/form/Radio.svelte +507 -0
- package/dist/form/Radio.svelte.d.ts +39 -0
- package/dist/form/Radio.svelte.d.ts.map +1 -0
- package/dist/form/Range.svelte +912 -0
- package/dist/form/Range.svelte.d.ts +33 -0
- package/dist/form/Range.svelte.d.ts.map +1 -0
- package/dist/form/Rating.svelte +429 -0
- package/dist/form/Rating.svelte.d.ts +28 -0
- package/dist/form/Rating.svelte.d.ts.map +1 -0
- package/dist/form/Select.svelte +1933 -0
- package/dist/form/Select.svelte.d.ts +54 -0
- package/dist/form/Select.svelte.d.ts.map +1 -0
- package/dist/form/Toggle.svelte +645 -0
- package/dist/form/Toggle.svelte.d.ts +50 -0
- package/dist/form/Toggle.svelte.d.ts.map +1 -0
- package/dist/form/index.d.ts +15 -0
- package/dist/form/index.d.ts.map +1 -0
- package/dist/form/index.js +10 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/layout/README.md +172 -0
- package/dist/media/Carousel.svelte +2424 -0
- package/dist/media/Carousel.svelte.d.ts +47 -0
- package/dist/media/Carousel.svelte.d.ts.map +1 -0
- package/dist/media/Gallery.svelte +2881 -0
- package/dist/media/Gallery.svelte.d.ts +82 -0
- package/dist/media/Gallery.svelte.d.ts.map +1 -0
- package/dist/media/Image.svelte +389 -0
- package/dist/media/Image.svelte.d.ts +33 -0
- package/dist/media/Image.svelte.d.ts.map +1 -0
- package/dist/media/PDF.svelte +1793 -0
- package/dist/media/PDF.svelte.d.ts +44 -0
- package/dist/media/PDF.svelte.d.ts.map +1 -0
- package/dist/media/Panorama.svelte +1391 -0
- package/dist/media/Panorama.svelte.d.ts +47 -0
- package/dist/media/Panorama.svelte.d.ts.map +1 -0
- package/dist/media/Video.svelte +2501 -0
- package/dist/media/Video.svelte.d.ts +58 -0
- package/dist/media/Video.svelte.d.ts.map +1 -0
- package/dist/media/carousel.d.ts +211 -0
- package/dist/media/carousel.d.ts.map +1 -0
- package/dist/media/carousel.js +408 -0
- package/dist/media/index.d.ts +11 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/index.js +5 -0
- package/dist/navigation/BottomSheet.svelte +636 -0
- package/dist/navigation/BottomSheet.svelte.d.ts +27 -0
- package/dist/navigation/BottomSheet.svelte.d.ts.map +1 -0
- package/dist/navigation/Breadcrumbs.svelte +611 -0
- package/dist/navigation/Breadcrumbs.svelte.d.ts +28 -0
- package/dist/navigation/Breadcrumbs.svelte.d.ts.map +1 -0
- package/dist/navigation/Pagination.svelte +641 -0
- package/dist/navigation/Pagination.svelte.d.ts +27 -0
- package/dist/navigation/Pagination.svelte.d.ts.map +1 -0
- package/dist/navigation/Steps.svelte +965 -0
- package/dist/navigation/Steps.svelte.d.ts +43 -0
- package/dist/navigation/Steps.svelte.d.ts.map +1 -0
- package/dist/navigation/Tabs.svelte +698 -0
- package/dist/navigation/Tabs.svelte.d.ts +41 -0
- package/dist/navigation/Tabs.svelte.d.ts.map +1 -0
- package/dist/navigation/index.d.ts +8 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +5 -0
- package/package.json +139 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/** Overflow values that make an element scrollable */
|
|
2
|
+
const SCROLLABLE = /auto|scroll|overlay/;
|
|
3
|
+
/** Minimum thumb length (px), so the thumb stays grabbable in huge documents */
|
|
4
|
+
const MIN_THUMB = 32;
|
|
5
|
+
let styles_injected = false;
|
|
6
|
+
/**
|
|
7
|
+
* The shared stylesheet for every scrollbar attachment. Visual styling lives
|
|
8
|
+
* here (driven by the --scrollbar-* tokens); geometry is set inline by the
|
|
9
|
+
* attachment. The show transition follows the design system's hover rule:
|
|
10
|
+
* snap in, ease out.
|
|
11
|
+
*/
|
|
12
|
+
function injectStyles() {
|
|
13
|
+
if (styles_injected || typeof document === 'undefined')
|
|
14
|
+
return;
|
|
15
|
+
styles_injected = true;
|
|
16
|
+
const style = document.createElement('style');
|
|
17
|
+
style.setAttribute('data-delight-scrollbar', '');
|
|
18
|
+
style.textContent = `
|
|
19
|
+
[data-scrollbar] { scrollbar-width: none !important; }
|
|
20
|
+
[data-scrollbar]::-webkit-scrollbar { display: none !important; width: 0 !important; height: 0 !important; }
|
|
21
|
+
.delight-scrollbar {
|
|
22
|
+
position: absolute;
|
|
23
|
+
z-index: 10;
|
|
24
|
+
opacity: 0;
|
|
25
|
+
pointer-events: none;
|
|
26
|
+
transition: opacity 250ms var(--ease-out, ease);
|
|
27
|
+
}
|
|
28
|
+
.delight-scrollbar[data-show] {
|
|
29
|
+
opacity: 1;
|
|
30
|
+
pointer-events: auto;
|
|
31
|
+
transition: none;
|
|
32
|
+
}
|
|
33
|
+
.delight-scrollbar-thumb {
|
|
34
|
+
position: absolute;
|
|
35
|
+
border-radius: var(--radius-full, 1e5px);
|
|
36
|
+
background-color: var(--scrollbar-thumb-color, rgb(128 128 128 / 0.5));
|
|
37
|
+
transition:
|
|
38
|
+
background-color 250ms var(--ease-out, ease),
|
|
39
|
+
width 150ms var(--ease-out, ease),
|
|
40
|
+
height 150ms var(--ease-out, ease);
|
|
41
|
+
}
|
|
42
|
+
.delight-scrollbar[data-axis='y'] .delight-scrollbar-thumb {
|
|
43
|
+
right: var(--scrollbar-inset, 2px);
|
|
44
|
+
width: calc(var(--scrollbar-size, 10px) * 0.5);
|
|
45
|
+
}
|
|
46
|
+
.delight-scrollbar[data-axis='y'][data-rtl] .delight-scrollbar-thumb {
|
|
47
|
+
right: auto;
|
|
48
|
+
left: var(--scrollbar-inset, 2px);
|
|
49
|
+
}
|
|
50
|
+
.delight-scrollbar[data-axis='x'] .delight-scrollbar-thumb {
|
|
51
|
+
bottom: var(--scrollbar-inset, 2px);
|
|
52
|
+
height: calc(var(--scrollbar-size, 10px) * 0.5);
|
|
53
|
+
}
|
|
54
|
+
.delight-scrollbar:hover .delight-scrollbar-thumb,
|
|
55
|
+
.delight-scrollbar[data-dragging] .delight-scrollbar-thumb {
|
|
56
|
+
background-color: var(--scrollbar-thumb-color-active, rgb(128 128 128 / 0.8));
|
|
57
|
+
transition:
|
|
58
|
+
width 150ms var(--ease-out, ease),
|
|
59
|
+
height 150ms var(--ease-out, ease);
|
|
60
|
+
}
|
|
61
|
+
.delight-scrollbar[data-axis='y']:hover .delight-scrollbar-thumb,
|
|
62
|
+
.delight-scrollbar[data-axis='y'][data-dragging] .delight-scrollbar-thumb {
|
|
63
|
+
width: calc(var(--scrollbar-size, 10px) - var(--scrollbar-inset, 2px));
|
|
64
|
+
}
|
|
65
|
+
.delight-scrollbar[data-axis='x']:hover .delight-scrollbar-thumb,
|
|
66
|
+
.delight-scrollbar[data-axis='x'][data-dragging] .delight-scrollbar-thumb {
|
|
67
|
+
height: calc(var(--scrollbar-size, 10px) - var(--scrollbar-inset, 2px));
|
|
68
|
+
}
|
|
69
|
+
@media (prefers-reduced-motion: reduce) {
|
|
70
|
+
.delight-scrollbar,
|
|
71
|
+
.delight-scrollbar-thumb { transition: none !important; }
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
document.head.appendChild(style);
|
|
75
|
+
}
|
|
76
|
+
function clamp(value, min, max) {
|
|
77
|
+
return Math.min(Math.max(value, min), max);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A svelte attachment that replaces an element's native scrollbars with the
|
|
81
|
+
* delightstack overlay scrollbar: a token-driven thumb that floats over the
|
|
82
|
+
* content (no layout gutter), fades in while scrolling/hovering and back out
|
|
83
|
+
* when idle, insets itself past the container's rounded corners, and supports
|
|
84
|
+
* dragging and click-to-jump like a native bar.
|
|
85
|
+
*
|
|
86
|
+
* Native scrolling (wheel, keyboard, touch, momentum) is untouched — the
|
|
87
|
+
* element keeps scrolling itself; only the visual scrollbar is replaced. On
|
|
88
|
+
* touch-only devices the attachment does nothing and the native auto-hiding
|
|
89
|
+
* indicators remain.
|
|
90
|
+
*
|
|
91
|
+
* The element's parent is used as the positioning context for the overlay
|
|
92
|
+
* (it is given `position: relative` if static), so the element should keep
|
|
93
|
+
* the same box as its parent or be positioned statically within it.
|
|
94
|
+
* @example
|
|
95
|
+
* ```svelte
|
|
96
|
+
* <div class="content" {@attach scrollbar()}>...</div>
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function scrollbar(options = {}) {
|
|
100
|
+
return (node) => {
|
|
101
|
+
if (typeof window === 'undefined')
|
|
102
|
+
return;
|
|
103
|
+
// Touch devices keep their native auto-hiding overlay indicators
|
|
104
|
+
if (!window.matchMedia('(hover: hover) and (pointer: fine)').matches)
|
|
105
|
+
return;
|
|
106
|
+
const parent_el = node.parentElement;
|
|
107
|
+
if (!parent_el)
|
|
108
|
+
return;
|
|
109
|
+
const parent = parent_el;
|
|
110
|
+
injectStyles();
|
|
111
|
+
const { autohide = true, autohide_delay = 1000, corner_inset, track_insets, } = options;
|
|
112
|
+
node.setAttribute('data-scrollbar', '');
|
|
113
|
+
// The tracks are positioned against the nearest containing block, which
|
|
114
|
+
// must be the parent for the offset math below to hold
|
|
115
|
+
let restore_position;
|
|
116
|
+
if (getComputedStyle(parent).position === 'static') {
|
|
117
|
+
restore_position = parent.style.position;
|
|
118
|
+
parent.style.position = 'relative';
|
|
119
|
+
}
|
|
120
|
+
function createBar(axis) {
|
|
121
|
+
const track = document.createElement('div');
|
|
122
|
+
track.className = 'delight-scrollbar';
|
|
123
|
+
track.setAttribute('data-axis', axis);
|
|
124
|
+
track.setAttribute('aria-hidden', 'true');
|
|
125
|
+
const thumb = document.createElement('div');
|
|
126
|
+
thumb.className = 'delight-scrollbar-thumb';
|
|
127
|
+
track.appendChild(thumb);
|
|
128
|
+
return {
|
|
129
|
+
axis,
|
|
130
|
+
track,
|
|
131
|
+
thumb,
|
|
132
|
+
enabled: false,
|
|
133
|
+
track_size: 0,
|
|
134
|
+
thumb_size: 0,
|
|
135
|
+
hovered: false,
|
|
136
|
+
dragging: false,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const bars = [createBar('y'), createBar('x')];
|
|
140
|
+
for (const bar of bars)
|
|
141
|
+
parent.insertBefore(bar.track, node.nextSibling);
|
|
142
|
+
let rtl = false;
|
|
143
|
+
let shown = false;
|
|
144
|
+
let hide_timer;
|
|
145
|
+
let frame;
|
|
146
|
+
function tokenPx(style, name, fallback) {
|
|
147
|
+
const value = parseFloat(style.getPropertyValue(name));
|
|
148
|
+
return Number.isFinite(value) ? value : fallback;
|
|
149
|
+
}
|
|
150
|
+
/** Repositions both tracks over the element's edges */
|
|
151
|
+
function layout() {
|
|
152
|
+
const style = getComputedStyle(node);
|
|
153
|
+
rtl = style.direction === 'rtl';
|
|
154
|
+
const size = tokenPx(style, '--scrollbar-size', 10);
|
|
155
|
+
const edge = tokenPx(style, '--scrollbar-inset', 2);
|
|
156
|
+
const track_width = size + edge;
|
|
157
|
+
const [y, x] = bars;
|
|
158
|
+
y.enabled =
|
|
159
|
+
SCROLLABLE.test(style.overflowY) && node.scrollHeight - node.clientHeight > 1;
|
|
160
|
+
x.enabled =
|
|
161
|
+
SCROLLABLE.test(style.overflowX) && node.scrollWidth - node.clientWidth > 1;
|
|
162
|
+
const top = node.offsetTop;
|
|
163
|
+
const left = node.offsetLeft;
|
|
164
|
+
const width = node.offsetWidth;
|
|
165
|
+
const height = node.offsetHeight;
|
|
166
|
+
// Rounded corners the thumb must stay clear of. The inset is HALF the
|
|
167
|
+
// computed radius — the thumb hugs the edge, so the curve has receded
|
|
168
|
+
// enough by then (and squircled corners double the computed radius).
|
|
169
|
+
// Scrollers often fill a rounded parent that carries the visual radius
|
|
170
|
+
// (modal/popover/card bodies), so when the element's own corner is
|
|
171
|
+
// square but it sits close to a rounded parent corner, inherit the
|
|
172
|
+
// parent's radius minus however far the element already sits from it.
|
|
173
|
+
const parent_style = getComputedStyle(parent);
|
|
174
|
+
const gap = {
|
|
175
|
+
top,
|
|
176
|
+
left,
|
|
177
|
+
right: parent.clientWidth - (left + width),
|
|
178
|
+
bottom: parent.clientHeight - (top + height),
|
|
179
|
+
};
|
|
180
|
+
function cornerRadius(corner) {
|
|
181
|
+
const own = parseFloat(style[`border${corner}Radius`]) || 0;
|
|
182
|
+
if (own > 0)
|
|
183
|
+
return own / 2;
|
|
184
|
+
const inherited = parseFloat(parent_style[`border${corner}Radius`]) || 0;
|
|
185
|
+
const [vertical_gap, horizontal_gap] = corner === 'TopLeft'
|
|
186
|
+
? [gap.top, gap.left]
|
|
187
|
+
: corner === 'TopRight'
|
|
188
|
+
? [gap.top, gap.right]
|
|
189
|
+
: corner === 'BottomLeft'
|
|
190
|
+
? [gap.bottom, gap.left]
|
|
191
|
+
: [gap.bottom, gap.right];
|
|
192
|
+
return Math.max(0, inherited / 2 - Math.max(vertical_gap, horizontal_gap));
|
|
193
|
+
}
|
|
194
|
+
const radius = {
|
|
195
|
+
tl: cornerRadius('TopLeft'),
|
|
196
|
+
tr: cornerRadius('TopRight'),
|
|
197
|
+
bl: cornerRadius('BottomLeft'),
|
|
198
|
+
br: cornerRadius('BottomRight'),
|
|
199
|
+
};
|
|
200
|
+
const insets = typeof track_insets === 'function' ? track_insets(node) : (track_insets ?? {});
|
|
201
|
+
// Inset the track ends past the rounded corners (and past the other
|
|
202
|
+
// track when both axes are scrollable, so they never overlap)
|
|
203
|
+
const y_start = insets.top ?? corner_inset ?? (rtl ? radius.tl : radius.tr);
|
|
204
|
+
const y_end = (insets.bottom ?? corner_inset ?? (rtl ? radius.bl : radius.br)) +
|
|
205
|
+
(x.enabled ? track_width : 0);
|
|
206
|
+
if (rtl)
|
|
207
|
+
y.track.setAttribute('data-rtl', '');
|
|
208
|
+
else
|
|
209
|
+
y.track.removeAttribute('data-rtl');
|
|
210
|
+
y.track.style.top = `${top + y_start}px`;
|
|
211
|
+
y.track.style.height = `${Math.max(0, height - y_start - y_end)}px`;
|
|
212
|
+
y.track.style.width = `${track_width}px`;
|
|
213
|
+
y.track.style.left = rtl ? `${left}px` : `${left + width - track_width}px`;
|
|
214
|
+
y.track_size = Math.max(0, height - y_start - y_end);
|
|
215
|
+
const x_start = (insets.left ?? corner_inset ?? radius.bl) + (y.enabled && rtl ? track_width : 0);
|
|
216
|
+
const x_end = (insets.right ?? corner_inset ?? radius.br) +
|
|
217
|
+
(y.enabled && !rtl ? track_width : 0);
|
|
218
|
+
x.track.style.left = `${left + x_start}px`;
|
|
219
|
+
x.track.style.width = `${Math.max(0, width - x_start - x_end)}px`;
|
|
220
|
+
x.track.style.height = `${track_width}px`;
|
|
221
|
+
x.track.style.top = `${top + height - track_width}px`;
|
|
222
|
+
x.track_size = Math.max(0, width - x_start - x_end);
|
|
223
|
+
}
|
|
224
|
+
/** Syncs a thumb's size + position to the element's scroll state */
|
|
225
|
+
function updateThumb(bar) {
|
|
226
|
+
if (!bar.enabled)
|
|
227
|
+
return;
|
|
228
|
+
const vertical = bar.axis === 'y';
|
|
229
|
+
const scroll_size = vertical ? node.scrollHeight : node.scrollWidth;
|
|
230
|
+
const client_size = vertical ? node.clientHeight : node.clientWidth;
|
|
231
|
+
const max_scroll = scroll_size - client_size;
|
|
232
|
+
const min = Math.min(MIN_THUMB, bar.track_size / 2);
|
|
233
|
+
bar.thumb_size = clamp((client_size / scroll_size) * bar.track_size, min, bar.track_size);
|
|
234
|
+
const range = bar.track_size - bar.thumb_size;
|
|
235
|
+
const position = vertical ? node.scrollTop : node.scrollLeft;
|
|
236
|
+
let progress = max_scroll > 0 ? clamp(Math.abs(position) / max_scroll, 0, 1) : 0;
|
|
237
|
+
// In RTL, scrollLeft runs from 0 (content start, at the right) to
|
|
238
|
+
// -max_scroll, and the thumb travels right-to-left
|
|
239
|
+
if (!vertical && rtl)
|
|
240
|
+
progress = 1 - progress;
|
|
241
|
+
bar.thumb.style[vertical ? 'height' : 'width'] = `${bar.thumb_size}px`;
|
|
242
|
+
bar.thumb.style.transform = vertical
|
|
243
|
+
? `translateY(${progress * range}px)`
|
|
244
|
+
: `translateX(${progress * range}px)`;
|
|
245
|
+
}
|
|
246
|
+
function syncVisibility() {
|
|
247
|
+
for (const bar of bars) {
|
|
248
|
+
const show = bar.enabled && (shown || !autohide);
|
|
249
|
+
if (show)
|
|
250
|
+
bar.track.setAttribute('data-show', '');
|
|
251
|
+
else
|
|
252
|
+
bar.track.removeAttribute('data-show');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function refresh() {
|
|
256
|
+
layout();
|
|
257
|
+
for (const bar of bars)
|
|
258
|
+
updateThumb(bar);
|
|
259
|
+
syncVisibility();
|
|
260
|
+
}
|
|
261
|
+
/** Batches refreshes from observers/scroll into one per frame */
|
|
262
|
+
function schedule() {
|
|
263
|
+
if (frame !== undefined)
|
|
264
|
+
return;
|
|
265
|
+
frame = requestAnimationFrame(() => {
|
|
266
|
+
frame = undefined;
|
|
267
|
+
refresh();
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
function scheduleHide() {
|
|
271
|
+
clearTimeout(hide_timer);
|
|
272
|
+
if (!autohide)
|
|
273
|
+
return;
|
|
274
|
+
hide_timer = setTimeout(() => {
|
|
275
|
+
if (bars.some((bar) => bar.hovered || bar.dragging))
|
|
276
|
+
return;
|
|
277
|
+
shown = false;
|
|
278
|
+
syncVisibility();
|
|
279
|
+
}, autohide_delay);
|
|
280
|
+
}
|
|
281
|
+
function show() {
|
|
282
|
+
if (!shown) {
|
|
283
|
+
shown = true;
|
|
284
|
+
syncVisibility();
|
|
285
|
+
}
|
|
286
|
+
scheduleHide();
|
|
287
|
+
}
|
|
288
|
+
function onScroll() {
|
|
289
|
+
for (const bar of bars)
|
|
290
|
+
updateThumb(bar);
|
|
291
|
+
show();
|
|
292
|
+
}
|
|
293
|
+
function setupBar(bar) {
|
|
294
|
+
const vertical = bar.axis === 'y';
|
|
295
|
+
bar.track.addEventListener('pointerenter', () => {
|
|
296
|
+
bar.hovered = true;
|
|
297
|
+
show();
|
|
298
|
+
});
|
|
299
|
+
bar.track.addEventListener('pointerleave', () => {
|
|
300
|
+
bar.hovered = false;
|
|
301
|
+
scheduleHide();
|
|
302
|
+
});
|
|
303
|
+
// The track sits over the content's edge, so forward wheel events the
|
|
304
|
+
// native scrollbar would have handled
|
|
305
|
+
bar.track.addEventListener('wheel', (event) => {
|
|
306
|
+
event.preventDefault();
|
|
307
|
+
node.scrollTop += event.deltaY;
|
|
308
|
+
node.scrollLeft += event.deltaX;
|
|
309
|
+
}, { passive: false });
|
|
310
|
+
// Click on the track (not the thumb) jumps to that position
|
|
311
|
+
bar.track.addEventListener('pointerdown', (event) => {
|
|
312
|
+
if (event.target !== bar.track || event.button !== 0)
|
|
313
|
+
return;
|
|
314
|
+
event.preventDefault();
|
|
315
|
+
const rect = bar.track.getBoundingClientRect();
|
|
316
|
+
const offset = vertical ? event.clientY - rect.top : event.clientX - rect.left;
|
|
317
|
+
const range = bar.track_size - bar.thumb_size;
|
|
318
|
+
let progress = range > 0 ? clamp((offset - bar.thumb_size / 2) / range, 0, 1) : 0;
|
|
319
|
+
if (!vertical && rtl)
|
|
320
|
+
progress = 1 - progress;
|
|
321
|
+
const scroll_size = vertical ? node.scrollHeight : node.scrollWidth;
|
|
322
|
+
const client_size = vertical ? node.clientHeight : node.clientWidth;
|
|
323
|
+
const target = progress * (scroll_size - client_size) * (!vertical && rtl ? -1 : 1);
|
|
324
|
+
node.scrollTo({ [vertical ? 'top' : 'left']: target, behavior: 'smooth' });
|
|
325
|
+
});
|
|
326
|
+
// Drag the thumb like a native scrollbar
|
|
327
|
+
bar.thumb.addEventListener('pointerdown', (event) => {
|
|
328
|
+
if (event.button !== 0)
|
|
329
|
+
return;
|
|
330
|
+
event.preventDefault();
|
|
331
|
+
event.stopPropagation();
|
|
332
|
+
bar.thumb.setPointerCapture(event.pointerId);
|
|
333
|
+
bar.dragging = true;
|
|
334
|
+
bar.track.setAttribute('data-dragging', '');
|
|
335
|
+
const start_pointer = vertical ? event.clientY : event.clientX;
|
|
336
|
+
const start_scroll = vertical ? node.scrollTop : node.scrollLeft;
|
|
337
|
+
function onMove(move) {
|
|
338
|
+
const delta = (vertical ? move.clientY : move.clientX) - start_pointer;
|
|
339
|
+
const range = bar.track_size - bar.thumb_size;
|
|
340
|
+
if (range <= 0)
|
|
341
|
+
return;
|
|
342
|
+
const scroll_size = vertical ? node.scrollHeight : node.scrollWidth;
|
|
343
|
+
const client_size = vertical ? node.clientHeight : node.clientWidth;
|
|
344
|
+
const ratio = (scroll_size - client_size) / range;
|
|
345
|
+
// The thumb→scroll mapping works out identically in RTL: both
|
|
346
|
+
// scrollLeft and the thumb's travel direction flip sign
|
|
347
|
+
const target = start_scroll + delta * ratio;
|
|
348
|
+
if (vertical)
|
|
349
|
+
node.scrollTop = target;
|
|
350
|
+
else
|
|
351
|
+
node.scrollLeft = target;
|
|
352
|
+
}
|
|
353
|
+
function onEnd(end) {
|
|
354
|
+
bar.thumb.releasePointerCapture(end.pointerId);
|
|
355
|
+
bar.thumb.removeEventListener('pointermove', onMove);
|
|
356
|
+
bar.thumb.removeEventListener('pointerup', onEnd);
|
|
357
|
+
bar.thumb.removeEventListener('pointercancel', onEnd);
|
|
358
|
+
bar.dragging = false;
|
|
359
|
+
bar.track.removeAttribute('data-dragging');
|
|
360
|
+
scheduleHide();
|
|
361
|
+
}
|
|
362
|
+
bar.thumb.addEventListener('pointermove', onMove);
|
|
363
|
+
bar.thumb.addEventListener('pointerup', onEnd);
|
|
364
|
+
bar.thumb.addEventListener('pointercancel', onEnd);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
for (const bar of bars)
|
|
368
|
+
setupBar(bar);
|
|
369
|
+
node.addEventListener('scroll', onScroll, { passive: true });
|
|
370
|
+
node.addEventListener('pointerenter', show);
|
|
371
|
+
node.addEventListener('pointermove', show);
|
|
372
|
+
node.addEventListener('pointerleave', scheduleHide);
|
|
373
|
+
const resize_observer = new ResizeObserver(schedule);
|
|
374
|
+
resize_observer.observe(node);
|
|
375
|
+
// Content changes (rows added, text edited, …) change scrollHeight
|
|
376
|
+
// without resizing the container itself
|
|
377
|
+
const mutation_observer = new MutationObserver(schedule);
|
|
378
|
+
mutation_observer.observe(node, {
|
|
379
|
+
childList: true,
|
|
380
|
+
subtree: true,
|
|
381
|
+
characterData: true,
|
|
382
|
+
});
|
|
383
|
+
// Initial reveal so overflowing content advertises its scrollability
|
|
384
|
+
refresh();
|
|
385
|
+
if (bars.some((bar) => bar.enabled))
|
|
386
|
+
show();
|
|
387
|
+
return () => {
|
|
388
|
+
clearTimeout(hide_timer);
|
|
389
|
+
if (frame !== undefined)
|
|
390
|
+
cancelAnimationFrame(frame);
|
|
391
|
+
resize_observer.disconnect();
|
|
392
|
+
mutation_observer.disconnect();
|
|
393
|
+
node.removeEventListener('scroll', onScroll);
|
|
394
|
+
node.removeEventListener('pointerenter', show);
|
|
395
|
+
node.removeEventListener('pointermove', show);
|
|
396
|
+
node.removeEventListener('pointerleave', scheduleHide);
|
|
397
|
+
for (const bar of bars)
|
|
398
|
+
bar.track.remove();
|
|
399
|
+
node.removeAttribute('data-scrollbar');
|
|
400
|
+
if (restore_position !== undefined)
|
|
401
|
+
parent.style.position = restore_position;
|
|
402
|
+
};
|
|
403
|
+
};
|
|
404
|
+
}
|