@x33025/sveltely 0.1.18 → 0.1.21
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/dist/actions/LoaderOverlay.svelte +33 -8
- package/dist/actions/LoaderOverlay.svelte.d.ts +3 -0
- package/dist/actions/loader.d.ts +3 -0
- package/dist/actions/loader.js +20 -7
- package/dist/components/Library/AnimatedNumber/AnimatedNumber.demo.svelte +3 -9
- package/dist/components/Library/ArticleEditor/ArticleEditor.svelte +1 -1
- package/dist/components/Library/ArticleEditor/ArticleEditorHeader.svelte +20 -30
- package/dist/components/Library/ArticleEditor/Blocks/Code.svelte +0 -1
- package/dist/components/Library/ArticleEditor/Blocks/FAQ.svelte +1 -1
- package/dist/components/Library/ArticleEditor/Blocks/Heading.svelte +7 -7
- package/dist/components/Library/ArticleEditor/Blocks/Image.svelte +20 -36
- package/dist/components/Library/ArticleEditor/Blocks/Image.svelte.d.ts +1 -0
- package/dist/components/Library/ArticleEditor/Blocks/List.svelte +2 -2
- package/dist/components/Library/ArticleEditor/Blocks/Paragraph.svelte +1 -1
- package/dist/components/Library/ArticleEditor/Blocks/index.d.ts +0 -1
- package/dist/components/Library/ArticleEditor/Blocks/index.js +0 -1
- package/dist/components/Library/AsyncButton/AsyncButton.demo.svelte +2 -6
- package/dist/components/Library/AsyncButton/AsyncButton.svelte +9 -5
- package/dist/components/Library/AsyncButton/AsyncButton.svelte.d.ts +2 -1
- package/dist/components/Library/Button/Button.demo.svelte +2 -17
- package/dist/components/Library/Button/Button.demo.svelte.d.ts +0 -1
- package/dist/components/Library/Button/Button.svelte +15 -16
- package/dist/components/Library/Button/Button.svelte.d.ts +2 -1
- package/dist/components/Library/Calendar/Calendar.svelte +17 -27
- package/dist/components/Library/Checkbox/Checkbox.demo.svelte +7 -4
- package/dist/components/Library/Checkbox/Checkbox.svelte +24 -61
- package/dist/components/Library/Checkbox/Checkbox.svelte.d.ts +2 -4
- package/dist/components/Library/ChipInput/ChipInput.demo.svelte +2 -2
- package/dist/components/Library/ChipInput/ChipInput.svelte +7 -11
- package/dist/components/Library/ChipInput/ChipInput.svelte.d.ts +3 -2
- package/dist/components/Library/Dropdown/Action.svelte +1 -1
- package/dist/components/Library/Dropdown/Dropdown.demo.svelte +10 -10
- package/dist/components/Library/Dropdown/Dropdown.svelte +2 -6
- package/dist/components/Library/Dropdown/Item.svelte +2 -2
- package/dist/components/Library/Dropdown/Section.svelte +1 -1
- package/dist/components/Library/Dropdown/Trigger.svelte +3 -7
- package/dist/components/Library/Image/Image.demo.svelte +3 -3
- package/dist/components/Library/Image/Image.svelte +57 -12
- package/dist/components/Library/Image/Image.svelte.d.ts +1 -2
- package/dist/components/Library/Image/ImagePlaceholder.demo.svelte +12 -0
- package/dist/components/Library/Image/ImagePlaceholder.demo.svelte.d.ts +23 -0
- package/dist/components/Library/Image/ImagePlaceholder.svelte +28 -4
- package/dist/components/Library/Image/ImagePlaceholder.svelte.d.ts +1 -1
- package/dist/components/Library/Image/index.d.ts +1 -0
- package/dist/components/Library/Image/index.js +1 -0
- package/dist/components/Library/ImageMask/BrushPreview.svelte +6 -6
- package/dist/components/Library/ImageMask/ImageMask.demo.svelte +10 -8
- package/dist/components/Library/ImageMask/ImageMask.svelte +14 -6
- package/dist/components/Library/ImageMask/ImageMask.svelte.d.ts +1 -2
- package/dist/components/Library/ImageMask/MaskLayer.svelte +12 -6
- package/dist/components/Library/Label/Label.demo.svelte +16 -3
- package/dist/components/Library/Label/Label.svelte +15 -3
- package/dist/components/Library/Label/Label.svelte.d.ts +1 -0
- package/dist/components/Library/Link/Link.svelte +10 -22
- package/dist/components/Library/Link/Link.svelte.d.ts +2 -3
- package/dist/components/Library/Loader/Loader.demo.svelte +9 -3
- package/dist/components/Library/NavigationStack/Link.svelte +8 -12
- package/dist/components/Library/NavigationStack/Link.svelte.d.ts +1 -3
- package/dist/components/Library/NavigationStack/SidebarToggle.svelte +8 -2
- package/dist/components/Library/NumberField/NumberField.svelte +21 -17
- package/dist/components/Library/NumberField/NumberField.svelte.d.ts +4 -2
- package/dist/components/Library/Pagination/Pagination.svelte +3 -3
- package/dist/components/Library/Popover/Popover.svelte +2 -7
- package/dist/components/Library/ScrollView/ScrollView.demo.svelte +50 -0
- package/dist/components/Library/ScrollView/ScrollView.demo.svelte.d.ts +10 -0
- package/dist/components/Library/ScrollView/ScrollView.svelte +414 -67
- package/dist/components/Library/ScrollView/ScrollView.svelte.d.ts +17 -1
- package/dist/components/Library/ScrollView/index.d.ts +1 -1
- package/dist/components/Library/SearchField/SearchField.demo.svelte +2 -2
- package/dist/components/Library/SearchField/SearchField.svelte +9 -4
- package/dist/components/Library/SearchField/SearchField.svelte.d.ts +2 -1
- package/dist/components/Library/SegmentedPicker/SegmentedPicker.demo.svelte +2 -2
- package/dist/components/Library/SegmentedPicker/SegmentedPicker.svelte +7 -7
- package/dist/components/Library/Sheet/Sheet.demo.svelte +1 -1
- package/dist/components/Library/Sheet/Sheet.svelte +2 -7
- package/dist/components/Library/Slider/Slider.demo.svelte +1 -1
- package/dist/components/Library/Slider/Slider.svelte +11 -7
- package/dist/components/Library/Slider/Slider.svelte.d.ts +2 -1
- package/dist/components/Library/Spinner/Spinner.demo.svelte +1 -1
- package/dist/components/Library/Switch/Switch.demo.svelte +7 -4
- package/dist/components/Library/Switch/Switch.svelte +28 -68
- package/dist/components/Library/Switch/Switch.svelte.d.ts +2 -4
- package/dist/components/Library/Table/Column.svelte +81 -0
- package/dist/components/Library/Table/Column.svelte.d.ts +39 -0
- package/dist/components/Library/Table/Table.demo.svelte +148 -0
- package/dist/components/Library/Table/Table.demo.svelte.d.ts +10 -0
- package/dist/components/Library/Table/Table.svelte +624 -0
- package/dist/components/Library/Table/Table.svelte.d.ts +42 -0
- package/dist/components/Library/Table/context.d.ts +5 -0
- package/dist/components/Library/Table/context.js +2 -0
- package/dist/components/Library/Table/index.js +5 -0
- package/dist/components/Library/Table/types.d.ts +37 -0
- package/dist/components/Library/Table/types.js +1 -0
- package/dist/components/Library/Text/Text.demo.svelte +21 -0
- package/dist/components/Library/Text/Text.demo.svelte.d.ts +24 -0
- package/dist/components/Library/Text/Text.svelte +41 -0
- package/dist/components/Library/Text/Text.svelte.d.ts +9 -0
- package/dist/components/Library/Text/index.d.ts +1 -0
- package/dist/components/Library/Text/index.js +1 -0
- package/dist/components/Library/TextEditor/TextEditor.svelte +15 -9
- package/dist/components/Library/TextEditor/TextEditor.svelte.d.ts +2 -4
- package/dist/components/Library/TextField/TextField.demo.svelte +1 -1
- package/dist/components/Library/TextField/TextField.svelte +21 -18
- package/dist/components/Library/TextField/TextField.svelte.d.ts +4 -2
- package/dist/components/Library/TextShimmer/TextShimmer.demo.svelte +1 -1
- package/dist/components/Library/TimePicker/TimePicker.demo.svelte +10 -2
- package/dist/components/Library/TimePicker/TimePicker.svelte +10 -5
- package/dist/components/Library/TokenSearchField/TokenSearchField.demo.svelte +4 -2
- package/dist/components/Library/TokenSearchField/TokenSearchField.svelte +11 -11
- package/dist/components/Library/TokenSearchField/TokenSearchField.svelte.d.ts +2 -1
- package/dist/components/Library/WheelPicker/WheelColumn.svelte +183 -126
- package/dist/components/Library/WheelPicker/WheelPicker.svelte +4 -4
- package/dist/components/Library/WheelPicker/WheelPicker.svelte.d.ts +2 -2
- package/dist/components/Library/WheelPicker/index.d.ts +1 -1
- package/dist/components/Library/WheelPicker/types.d.ts +6 -0
- package/dist/components/Local/ColorStyleControls.svelte +201 -0
- package/dist/components/Local/ColorStyleControls.svelte.d.ts +13 -0
- package/dist/components/Local/HeroCard.svelte +3 -3
- package/dist/components/Local/LayoutStyleControls.svelte +67 -0
- package/dist/components/Local/LayoutStyleControls.svelte.d.ts +11 -0
- package/dist/components/Local/StyleControls.svelte +48 -124
- package/dist/components/Local/StyleControls.svelte.d.ts +7 -5
- package/dist/index.d.ts +9 -2
- package/dist/index.js +5 -1
- package/dist/style/index.css +7 -12
- package/dist/style/label.d.ts +2 -1
- package/dist/style/label.js +2 -1
- package/dist/style/surface.js +4 -0
- package/dist/style/text-editor.d.ts +2 -13
- package/dist/style/text-editor.js +1 -12
- package/dist/style/text.d.ts +26 -0
- package/dist/style/text.js +69 -0
- package/dist/style/tooltip.d.ts +4 -0
- package/dist/style/tooltip.js +1 -0
- package/dist/style.css +44 -111
- package/package.json +1 -1
- package/dist/components/Library/ArticleEditor/Blocks/ImagePreview.svelte +0 -71
- package/dist/components/Library/ArticleEditor/Blocks/ImagePreview.svelte.d.ts +0 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { tick } from 'svelte';
|
|
2
|
+
import { tick, untrack } from 'svelte';
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
4
|
import { loader as loaderAction } from '../../../actions/loader';
|
|
5
5
|
import { extractLoaderProps, resolveLoaderOptions, type LoaderProps } from '../../../style/loader';
|
|
@@ -33,26 +33,46 @@
|
|
|
33
33
|
};
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
export type ScrollViewHandle = {
|
|
37
|
+
element: HTMLElement | null;
|
|
38
|
+
get scrollTop(): number;
|
|
39
|
+
set scrollTop(value: number);
|
|
40
|
+
get scrollLeft(): number;
|
|
41
|
+
set scrollLeft(value: number);
|
|
42
|
+
get clientWidth(): number;
|
|
43
|
+
get clientHeight(): number;
|
|
44
|
+
get scrollWidth(): number;
|
|
45
|
+
get scrollHeight(): number;
|
|
46
|
+
getBoundingClientRect(): DOMRect;
|
|
47
|
+
scrollTo: (options: ScrollToOptions | number, y?: number) => void;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type ScrollViewOverscrollBehavior = 'auto' | 'contain' | 'none';
|
|
51
|
+
|
|
36
52
|
type Props = {
|
|
37
53
|
children: Snippet;
|
|
38
|
-
viewport?:
|
|
54
|
+
viewport?: ScrollViewHandle | null;
|
|
39
55
|
axis?: ScrollAxis;
|
|
40
56
|
contentStyles?: StyleProps;
|
|
41
57
|
onScroll?: (geometry: ScrollGeometry) => void;
|
|
42
58
|
scrollGradient?: boolean;
|
|
43
59
|
scrollGradientSize?: number | string;
|
|
60
|
+
showIndicators?: boolean;
|
|
61
|
+
overscrollBehavior?: ScrollViewOverscrollBehavior;
|
|
44
62
|
} & LayoutProps &
|
|
45
63
|
StyleProps &
|
|
46
64
|
LoaderProps;
|
|
47
65
|
|
|
48
66
|
let {
|
|
49
67
|
children,
|
|
50
|
-
viewport = $bindable<
|
|
68
|
+
viewport = $bindable<ScrollViewHandle | null>(null),
|
|
51
69
|
axis = 'vertical',
|
|
52
70
|
contentStyles = {},
|
|
53
71
|
onScroll,
|
|
54
72
|
scrollGradient = true,
|
|
55
|
-
scrollGradientSize = '
|
|
73
|
+
scrollGradientSize = '2.5rem',
|
|
74
|
+
showIndicators = true,
|
|
75
|
+
overscrollBehavior = 'contain',
|
|
56
76
|
...restProps
|
|
57
77
|
}: Props = $props();
|
|
58
78
|
|
|
@@ -76,71 +96,297 @@
|
|
|
76
96
|
);
|
|
77
97
|
let canScrollToTop = $state(false);
|
|
78
98
|
let canScrollToBottom = $state(false);
|
|
99
|
+
let viewportElement = $state<HTMLDivElement | null>(null);
|
|
100
|
+
let contentElement = $state<HTMLDivElement | null>(null);
|
|
101
|
+
let offsetX = $state(0);
|
|
102
|
+
let offsetY = $state(0);
|
|
103
|
+
let canScrollX = $state(false);
|
|
104
|
+
let canScrollY = $state(false);
|
|
105
|
+
let thumbXSize = $state(0);
|
|
106
|
+
let thumbYSize = $state(0);
|
|
107
|
+
let thumbXOffset = $state(0);
|
|
108
|
+
let thumbYOffset = $state(0);
|
|
109
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
110
|
+
let mutationObserver: MutationObserver | null = null;
|
|
79
111
|
const scrollGradientEnabled = $derived(scrollGradient && axis !== 'horizontal');
|
|
112
|
+
const verticalScrollbarEnabled = $derived(showIndicators && axis !== 'horizontal' && canScrollY);
|
|
113
|
+
const horizontalScrollbarEnabled = $derived(showIndicators && axis !== 'vertical' && canScrollX);
|
|
80
114
|
|
|
81
|
-
|
|
82
|
-
if (!
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
115
|
+
const measuredContentSize = () => {
|
|
116
|
+
if (!contentElement) return { width: 0, height: 0 };
|
|
117
|
+
|
|
118
|
+
const contentRect = contentElement.getBoundingClientRect();
|
|
119
|
+
let width = Math.max(contentElement.scrollWidth, contentElement.offsetWidth, contentRect.width);
|
|
120
|
+
let height = Math.max(contentElement.scrollHeight, contentElement.offsetHeight, contentRect.height);
|
|
86
121
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
122
|
+
for (const child of Array.from(contentElement.children)) {
|
|
123
|
+
const childElement = child as HTMLElement;
|
|
124
|
+
const childRect = childElement.getBoundingClientRect();
|
|
125
|
+
const childRight = childRect.right - contentRect.left;
|
|
126
|
+
const childBottom = childRect.bottom - contentRect.top;
|
|
127
|
+
width = Math.max(width, childElement.scrollWidth, childElement.offsetWidth, childRight);
|
|
128
|
+
height = Math.max(height, childElement.scrollHeight, childElement.offsetHeight, childBottom);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { width, height };
|
|
132
|
+
};
|
|
133
|
+
const contentWidth = () => measuredContentSize().width;
|
|
134
|
+
const contentHeight = () => measuredContentSize().height;
|
|
135
|
+
const maxOffsetX = () =>
|
|
136
|
+
viewportElement ? Math.max(0, contentWidth() - viewportElement.clientWidth) : 0;
|
|
137
|
+
const maxOffsetY = () =>
|
|
138
|
+
viewportElement ? Math.max(0, contentHeight() - viewportElement.clientHeight) : 0;
|
|
139
|
+
const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
|
|
140
|
+
|
|
141
|
+
function getScrollGeometry(): ScrollGeometry {
|
|
142
|
+
const maxX = maxOffsetX();
|
|
143
|
+
const maxY = maxOffsetY();
|
|
92
144
|
|
|
93
145
|
return {
|
|
94
146
|
axis,
|
|
95
|
-
offset: { x, y },
|
|
147
|
+
offset: { x: offsetX, y: offsetY },
|
|
96
148
|
viewport: {
|
|
97
|
-
width:
|
|
98
|
-
height:
|
|
149
|
+
width: viewportElement?.clientWidth ?? 0,
|
|
150
|
+
height: viewportElement?.clientHeight ?? 0
|
|
99
151
|
},
|
|
100
152
|
content: {
|
|
101
|
-
width:
|
|
102
|
-
height:
|
|
153
|
+
width: contentWidth(),
|
|
154
|
+
height: contentHeight()
|
|
103
155
|
},
|
|
104
156
|
remaining: {
|
|
105
|
-
top:
|
|
106
|
-
right: maxX -
|
|
107
|
-
bottom: maxY -
|
|
108
|
-
left:
|
|
157
|
+
top: offsetY,
|
|
158
|
+
right: maxX - offsetX,
|
|
159
|
+
bottom: maxY - offsetY,
|
|
160
|
+
left: offsetX
|
|
109
161
|
},
|
|
110
162
|
progress: {
|
|
111
|
-
x: maxX === 0 ? 1 :
|
|
112
|
-
y: maxY === 0 ? 1 :
|
|
163
|
+
x: maxX === 0 ? 1 : offsetX / maxX,
|
|
164
|
+
y: maxY === 0 ? 1 : offsetY / maxY
|
|
113
165
|
}
|
|
114
166
|
};
|
|
115
167
|
}
|
|
116
168
|
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
169
|
+
function syncScrollbarGeometry() {
|
|
170
|
+
if (!viewportElement || !contentElement) {
|
|
171
|
+
canScrollX = false;
|
|
172
|
+
canScrollY = false;
|
|
173
|
+
thumbXSize = 0;
|
|
174
|
+
thumbYSize = 0;
|
|
175
|
+
thumbXOffset = 0;
|
|
176
|
+
thumbYOffset = 0;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const maxX = maxOffsetX();
|
|
180
|
+
const maxY = maxOffsetY();
|
|
181
|
+
canScrollX = maxX > 1;
|
|
182
|
+
canScrollY = maxY > 1;
|
|
183
|
+
offsetX = clamp(offsetX, 0, maxX);
|
|
184
|
+
offsetY = clamp(offsetY, 0, maxY);
|
|
185
|
+
|
|
186
|
+
if (canScrollX) {
|
|
187
|
+
const ratio = viewportElement.clientWidth / contentWidth();
|
|
188
|
+
thumbXSize = Math.max(24, viewportElement.clientWidth * ratio);
|
|
189
|
+
thumbXOffset = (offsetX / maxX) * Math.max(0, viewportElement.clientWidth - thumbXSize);
|
|
190
|
+
} else {
|
|
191
|
+
thumbXSize = 0;
|
|
192
|
+
thumbXOffset = 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (canScrollY) {
|
|
196
|
+
const ratio = viewportElement.clientHeight / contentHeight();
|
|
197
|
+
thumbYSize = Math.max(24, viewportElement.clientHeight * ratio);
|
|
198
|
+
thumbYOffset = (offsetY / maxY) * Math.max(0, viewportElement.clientHeight - thumbYSize);
|
|
199
|
+
} else {
|
|
200
|
+
thumbYSize = 0;
|
|
201
|
+
thumbYOffset = 0;
|
|
202
|
+
}
|
|
121
203
|
}
|
|
122
204
|
|
|
205
|
+
function syncScrollGradient() {
|
|
206
|
+
if (!scrollGradientEnabled || !viewportElement || !contentElement) {
|
|
207
|
+
canScrollToTop = false;
|
|
208
|
+
canScrollToBottom = false;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const maxY = maxOffsetY();
|
|
213
|
+
canScrollToTop = maxY > 1 && offsetY > 1;
|
|
214
|
+
canScrollToBottom = maxY > 1 && maxY - offsetY > 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function publishScroll() {
|
|
218
|
+
syncScrollbarGeometry();
|
|
219
|
+
syncScrollGradient();
|
|
220
|
+
onScroll?.(getScrollGeometry());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function scrollTo(nextX: number, nextY: number) {
|
|
224
|
+
const previousX = offsetX;
|
|
225
|
+
const previousY = offsetY;
|
|
226
|
+
offsetX = clamp(axis === 'vertical' ? 0 : nextX, 0, maxOffsetX());
|
|
227
|
+
offsetY = clamp(axis === 'horizontal' ? 0 : nextY, 0, maxOffsetY());
|
|
228
|
+
publishScroll();
|
|
229
|
+
return offsetX !== previousX || offsetY !== previousY;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function shouldContainOverscroll(consumed: boolean) {
|
|
233
|
+
if (overscrollBehavior === 'none') return true;
|
|
234
|
+
return overscrollBehavior === 'contain' && consumed;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const scrollViewHandle: ScrollViewHandle = {
|
|
238
|
+
get element() {
|
|
239
|
+
return viewportElement;
|
|
240
|
+
},
|
|
241
|
+
get scrollTop() {
|
|
242
|
+
return offsetY;
|
|
243
|
+
},
|
|
244
|
+
set scrollTop(value: number) {
|
|
245
|
+
scrollTo(offsetX, value);
|
|
246
|
+
},
|
|
247
|
+
get scrollLeft() {
|
|
248
|
+
return offsetX;
|
|
249
|
+
},
|
|
250
|
+
set scrollLeft(value: number) {
|
|
251
|
+
scrollTo(value, offsetY);
|
|
252
|
+
},
|
|
253
|
+
get clientWidth() {
|
|
254
|
+
return viewportElement?.clientWidth ?? 0;
|
|
255
|
+
},
|
|
256
|
+
get clientHeight() {
|
|
257
|
+
return viewportElement?.clientHeight ?? 0;
|
|
258
|
+
},
|
|
259
|
+
get scrollWidth() {
|
|
260
|
+
return contentWidth();
|
|
261
|
+
},
|
|
262
|
+
get scrollHeight() {
|
|
263
|
+
return contentHeight();
|
|
264
|
+
},
|
|
265
|
+
getBoundingClientRect() {
|
|
266
|
+
return viewportElement?.getBoundingClientRect() ?? new DOMRect();
|
|
267
|
+
},
|
|
268
|
+
scrollTo(options: ScrollToOptions | number, y?: number) {
|
|
269
|
+
if (typeof options === 'number') {
|
|
270
|
+
scrollTo(options, y ?? offsetY);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
scrollTo(options.left ?? offsetX, options.top ?? offsetY);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
123
277
|
function handleWheel(event: WheelEvent) {
|
|
124
|
-
const
|
|
278
|
+
const wheelX = axis === 'horizontal' && event.deltaX === 0 ? event.deltaY : event.deltaX;
|
|
279
|
+
const consumed = scrollTo(offsetX + wheelX, offsetY + event.deltaY);
|
|
125
280
|
|
|
126
|
-
|
|
127
|
-
|
|
281
|
+
if (consumed || shouldContainOverscroll(consumed)) {
|
|
282
|
+
event.preventDefault();
|
|
283
|
+
event.stopPropagation();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
288
|
+
const line = 40;
|
|
289
|
+
const pageX = viewportElement?.clientWidth ?? 0;
|
|
290
|
+
const pageY = viewportElement?.clientHeight ?? 0;
|
|
291
|
+
|
|
292
|
+
let consumed = false;
|
|
128
293
|
|
|
129
|
-
if (
|
|
130
|
-
if (
|
|
294
|
+
if (event.key === 'ArrowDown') consumed = scrollTo(offsetX, offsetY + line);
|
|
295
|
+
else if (event.key === 'ArrowUp') consumed = scrollTo(offsetX, offsetY - line);
|
|
296
|
+
else if (event.key === 'ArrowRight') consumed = scrollTo(offsetX + line, offsetY);
|
|
297
|
+
else if (event.key === 'ArrowLeft') consumed = scrollTo(offsetX - line, offsetY);
|
|
298
|
+
else if (event.key === 'PageDown') consumed = scrollTo(offsetX, offsetY + pageY);
|
|
299
|
+
else if (event.key === 'PageUp') consumed = scrollTo(offsetX, offsetY - pageY);
|
|
300
|
+
else if (event.key === 'Home') consumed = scrollTo(0, 0);
|
|
301
|
+
else if (event.key === 'End') consumed = scrollTo(maxOffsetX(), maxOffsetY());
|
|
302
|
+
else return;
|
|
303
|
+
|
|
304
|
+
if (consumed || shouldContainOverscroll(consumed)) event.preventDefault();
|
|
131
305
|
}
|
|
132
306
|
|
|
133
307
|
$effect(() => {
|
|
134
|
-
if (!
|
|
308
|
+
if (!viewportElement || !contentElement) return;
|
|
309
|
+
viewport = scrollViewHandle;
|
|
310
|
+
resizeObserver?.disconnect();
|
|
311
|
+
resizeObserver = new ResizeObserver(() => untrack(publishScroll));
|
|
312
|
+
resizeObserver.observe(viewportElement);
|
|
313
|
+
resizeObserver.observe(contentElement);
|
|
314
|
+
|
|
315
|
+
const observeContentChildren = () => {
|
|
316
|
+
if (!resizeObserver || !contentElement) return;
|
|
317
|
+
for (const child of Array.from(contentElement.children)) {
|
|
318
|
+
resizeObserver.observe(child);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
observeContentChildren();
|
|
323
|
+
mutationObserver?.disconnect();
|
|
324
|
+
mutationObserver = new MutationObserver(() => {
|
|
325
|
+
observeContentChildren();
|
|
326
|
+
untrack(publishScroll);
|
|
327
|
+
});
|
|
328
|
+
mutationObserver.observe(contentElement, { childList: true });
|
|
329
|
+
|
|
135
330
|
void tick().then(() => {
|
|
136
|
-
if (!
|
|
137
|
-
|
|
331
|
+
if (!viewportElement || !contentElement) return;
|
|
332
|
+
untrack(publishScroll);
|
|
138
333
|
});
|
|
334
|
+
|
|
335
|
+
return () => {
|
|
336
|
+
resizeObserver?.disconnect();
|
|
337
|
+
resizeObserver = null;
|
|
338
|
+
mutationObserver?.disconnect();
|
|
339
|
+
mutationObserver = null;
|
|
340
|
+
};
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
$effect(() => {
|
|
344
|
+
scrollGradientEnabled;
|
|
345
|
+
if (!viewportElement || !contentElement) return;
|
|
346
|
+
untrack(publishScroll);
|
|
139
347
|
});
|
|
348
|
+
|
|
349
|
+
function startThumbDrag(event: PointerEvent, orientation: 'horizontal' | 'vertical') {
|
|
350
|
+
if (!viewportElement) return;
|
|
351
|
+
|
|
352
|
+
event.preventDefault();
|
|
353
|
+
event.stopPropagation();
|
|
354
|
+
|
|
355
|
+
const startPointer = orientation === 'horizontal' ? event.clientX : event.clientY;
|
|
356
|
+
const startScroll = orientation === 'horizontal' ? offsetX : offsetY;
|
|
357
|
+
|
|
358
|
+
const move = (moveEvent: PointerEvent) => {
|
|
359
|
+
if (!viewportElement) return;
|
|
360
|
+
const viewportSize =
|
|
361
|
+
orientation === 'horizontal' ? viewportElement.clientWidth : viewportElement.clientHeight;
|
|
362
|
+
const contentSize = orientation === 'horizontal' ? contentWidth() : contentHeight();
|
|
363
|
+
const thumbSize = orientation === 'horizontal' ? thumbXSize : thumbYSize;
|
|
364
|
+
const maxScroll = Math.max(0, contentSize - viewportSize);
|
|
365
|
+
const maxThumbOffset = Math.max(1, viewportSize - thumbSize);
|
|
366
|
+
const delta = (orientation === 'horizontal' ? moveEvent.clientX : moveEvent.clientY) - startPointer;
|
|
367
|
+
const nextScroll = startScroll + (delta / maxThumbOffset) * maxScroll;
|
|
368
|
+
|
|
369
|
+
if (orientation === 'horizontal') {
|
|
370
|
+
scrollTo(nextScroll, offsetY);
|
|
371
|
+
} else {
|
|
372
|
+
scrollTo(offsetX, nextScroll);
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const up = () => {
|
|
377
|
+
window.removeEventListener('pointermove', move);
|
|
378
|
+
window.removeEventListener('pointerup', up);
|
|
379
|
+
window.removeEventListener('pointercancel', up);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
window.addEventListener('pointermove', move);
|
|
383
|
+
window.addEventListener('pointerup', up);
|
|
384
|
+
window.addEventListener('pointercancel', up);
|
|
385
|
+
}
|
|
386
|
+
|
|
140
387
|
</script>
|
|
141
388
|
|
|
142
389
|
<div
|
|
143
|
-
bind:this={viewport}
|
|
144
390
|
class="scroll-view"
|
|
145
391
|
class:scroll-view-vertical={axis === 'vertical'}
|
|
146
392
|
class:scroll-view-horizontal={axis === 'horizontal'}
|
|
@@ -151,17 +397,56 @@
|
|
|
151
397
|
style={rootStyle}
|
|
152
398
|
{...props}
|
|
153
399
|
use:loaderAction={loaderOptions}
|
|
154
|
-
onscroll={handleScroll}
|
|
155
|
-
onwheel={handleWheel}
|
|
156
400
|
>
|
|
157
401
|
{#if scrollGradientEnabled}
|
|
158
|
-
<div class="scroll-view-gradient scroll-view-gradient-top"></div>
|
|
402
|
+
<div class="scroll-view-gradient scroll-view-gradient-top" aria-hidden="true"></div>
|
|
159
403
|
{/if}
|
|
160
|
-
|
|
161
|
-
|
|
404
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_noninteractive_element_interactions -->
|
|
405
|
+
<div
|
|
406
|
+
bind:this={viewportElement}
|
|
407
|
+
class="scroll-view-viewport"
|
|
408
|
+
role="region"
|
|
409
|
+
aria-label="Scrollable content"
|
|
410
|
+
tabindex="0"
|
|
411
|
+
onwheel={handleWheel}
|
|
412
|
+
onkeydown={handleKeydown}
|
|
413
|
+
>
|
|
414
|
+
<div
|
|
415
|
+
bind:this={contentElement}
|
|
416
|
+
class="scroll-view-content"
|
|
417
|
+
style={`${contentStyle} transform: translate3d(${-offsetX}px, ${-offsetY}px, 0);`}
|
|
418
|
+
>
|
|
419
|
+
{@render children()}
|
|
420
|
+
</div>
|
|
162
421
|
</div>
|
|
163
422
|
{#if scrollGradientEnabled}
|
|
164
|
-
<div class="scroll-view-gradient scroll-view-gradient-bottom"></div>
|
|
423
|
+
<div class="scroll-view-gradient scroll-view-gradient-bottom" aria-hidden="true"></div>
|
|
424
|
+
{/if}
|
|
425
|
+
{#if verticalScrollbarEnabled}
|
|
426
|
+
<div class="scroll-view-scrollbar scroll-view-scrollbar-vertical" aria-hidden="true">
|
|
427
|
+
<button
|
|
428
|
+
type="button"
|
|
429
|
+
class="scroll-view-thumb scroll-view-thumb-vertical"
|
|
430
|
+
aria-label="Scroll vertically"
|
|
431
|
+
style:height={`${thumbYSize}px`}
|
|
432
|
+
style:transform={`translateY(${thumbYOffset}px)`}
|
|
433
|
+
onpointerdown={(event) => startThumbDrag(event, 'vertical')}
|
|
434
|
+
tabindex="-1"
|
|
435
|
+
></button>
|
|
436
|
+
</div>
|
|
437
|
+
{/if}
|
|
438
|
+
{#if horizontalScrollbarEnabled}
|
|
439
|
+
<div class="scroll-view-scrollbar scroll-view-scrollbar-horizontal" aria-hidden="true">
|
|
440
|
+
<button
|
|
441
|
+
type="button"
|
|
442
|
+
class="scroll-view-thumb scroll-view-thumb-horizontal"
|
|
443
|
+
aria-label="Scroll horizontally"
|
|
444
|
+
style:width={`${thumbXSize}px`}
|
|
445
|
+
style:transform={`translateX(${thumbXOffset}px)`}
|
|
446
|
+
onpointerdown={(event) => startThumbDrag(event, 'horizontal')}
|
|
447
|
+
tabindex="-1"
|
|
448
|
+
></button>
|
|
449
|
+
</div>
|
|
165
450
|
{/if}
|
|
166
451
|
</div>
|
|
167
452
|
|
|
@@ -171,33 +456,29 @@
|
|
|
171
456
|
min-height: 0;
|
|
172
457
|
width: 100%;
|
|
173
458
|
height: 100%;
|
|
174
|
-
border-radius: var(--scroll-view-border-radius,
|
|
459
|
+
border-radius: var(--scroll-view-border-radius, 0);
|
|
175
460
|
background: var(--scroll-view-background, transparent);
|
|
176
461
|
color: var(--scroll-view-color, inherit);
|
|
177
|
-
scrollbar-gutter: stable;
|
|
178
|
-
overscroll-behavior: contain;
|
|
179
462
|
position: relative;
|
|
463
|
+
overflow: hidden;
|
|
180
464
|
}
|
|
181
465
|
|
|
182
|
-
.scroll-view-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
overflow-y: hidden;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
.scroll-view-both {
|
|
193
|
-
overflow: auto;
|
|
466
|
+
.scroll-view-viewport {
|
|
467
|
+
width: 100%;
|
|
468
|
+
height: 100%;
|
|
469
|
+
min-width: 0;
|
|
470
|
+
min-height: 0;
|
|
471
|
+
overflow: hidden;
|
|
472
|
+
touch-action: none;
|
|
194
473
|
}
|
|
195
474
|
|
|
196
475
|
.scroll-view-content {
|
|
476
|
+
width: 100%;
|
|
197
477
|
min-width: 100%;
|
|
198
478
|
min-height: 100%;
|
|
199
479
|
padding: var(--scroll-view-content-padding-y, var(--sveltely-inset))
|
|
200
480
|
var(--scroll-view-content-padding-x, var(--sveltely-inset));
|
|
481
|
+
will-change: transform;
|
|
201
482
|
}
|
|
202
483
|
|
|
203
484
|
.scroll-view-horizontal .scroll-view-content {
|
|
@@ -215,10 +496,11 @@
|
|
|
215
496
|
}
|
|
216
497
|
|
|
217
498
|
.scroll-view-gradient {
|
|
218
|
-
position:
|
|
499
|
+
position: absolute;
|
|
500
|
+
left: 0;
|
|
501
|
+
right: 0;
|
|
219
502
|
z-index: 2;
|
|
220
503
|
height: var(--scroll-view-gradient-size, 1rem);
|
|
221
|
-
flex: 0 0 var(--scroll-view-gradient-size, 1rem);
|
|
222
504
|
pointer-events: none;
|
|
223
505
|
opacity: 0;
|
|
224
506
|
transition: opacity 120ms ease;
|
|
@@ -226,18 +508,83 @@
|
|
|
226
508
|
|
|
227
509
|
.scroll-view-gradient-top {
|
|
228
510
|
top: 0;
|
|
229
|
-
|
|
230
|
-
|
|
511
|
+
background: linear-gradient(
|
|
512
|
+
180deg in oklab,
|
|
513
|
+
rgb(255 255 255 / 0.72) 0%,
|
|
514
|
+
rgb(255 255 255 / 0.67) 8.1%,
|
|
515
|
+
rgb(255 255 255 / 0.58) 15.5%,
|
|
516
|
+
rgb(255 255 255 / 0.46) 24.5%,
|
|
517
|
+
rgb(255 255 255 / 0.32) 38.4%,
|
|
518
|
+
rgb(255 255 255 / 0.18) 58.7%,
|
|
519
|
+
rgb(255 255 255 / 0.07) 80.2%,
|
|
520
|
+
rgb(255 255 255 / 0) 100%
|
|
521
|
+
);
|
|
231
522
|
}
|
|
232
523
|
|
|
233
524
|
.scroll-view-gradient-bottom {
|
|
234
525
|
bottom: 0;
|
|
235
|
-
|
|
236
|
-
|
|
526
|
+
background: linear-gradient(
|
|
527
|
+
0deg in oklab,
|
|
528
|
+
rgb(255 255 255 / 0.72) 0%,
|
|
529
|
+
rgb(255 255 255 / 0.67) 8.1%,
|
|
530
|
+
rgb(255 255 255 / 0.58) 15.5%,
|
|
531
|
+
rgb(255 255 255 / 0.46) 24.5%,
|
|
532
|
+
rgb(255 255 255 / 0.32) 38.4%,
|
|
533
|
+
rgb(255 255 255 / 0.18) 58.7%,
|
|
534
|
+
rgb(255 255 255 / 0.07) 80.2%,
|
|
535
|
+
rgb(255 255 255 / 0) 100%
|
|
536
|
+
);
|
|
237
537
|
}
|
|
238
538
|
|
|
239
|
-
.scroll-view-can-scroll-up .scroll-view-gradient-top,
|
|
240
|
-
.scroll-view-can-scroll-down .scroll-view-gradient-bottom {
|
|
539
|
+
.scroll-view-can-scroll-up > .scroll-view-gradient-top,
|
|
540
|
+
.scroll-view-can-scroll-down > .scroll-view-gradient-bottom {
|
|
241
541
|
opacity: 1;
|
|
242
542
|
}
|
|
543
|
+
|
|
544
|
+
.scroll-view-scrollbar {
|
|
545
|
+
position: absolute;
|
|
546
|
+
z-index: 3;
|
|
547
|
+
pointer-events: none;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.scroll-view-scrollbar-vertical {
|
|
551
|
+
top: calc(var(--sveltely-inset) * 0.5);
|
|
552
|
+
right: calc(var(--sveltely-inset) * 0.5);
|
|
553
|
+
bottom: calc(var(--sveltely-inset) * 0.5);
|
|
554
|
+
width: 0.375rem;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.scroll-view-scrollbar-horizontal {
|
|
558
|
+
right: calc(var(--sveltely-inset) * 0.5);
|
|
559
|
+
bottom: calc(var(--sveltely-inset) * 0.5);
|
|
560
|
+
left: calc(var(--sveltely-inset) * 0.5);
|
|
561
|
+
height: 0.375rem;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.scroll-view-thumb {
|
|
565
|
+
position: absolute;
|
|
566
|
+
display: block;
|
|
567
|
+
border: 0;
|
|
568
|
+
border-radius: 999px;
|
|
569
|
+
background: color-mix(in oklab, var(--sveltely-text-secondary-color) 48%, transparent);
|
|
570
|
+
padding: 0;
|
|
571
|
+
pointer-events: auto;
|
|
572
|
+
transition: background-color 120ms ease;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.scroll-view-thumb:hover {
|
|
576
|
+
background: color-mix(in oklab, var(--sveltely-text-secondary-color) 72%, transparent);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.scroll-view-thumb-vertical {
|
|
580
|
+
top: 0;
|
|
581
|
+
right: 0;
|
|
582
|
+
width: 100%;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.scroll-view-thumb-horizontal {
|
|
586
|
+
bottom: 0;
|
|
587
|
+
left: 0;
|
|
588
|
+
height: 100%;
|
|
589
|
+
}
|
|
243
590
|
</style>
|
|
@@ -28,14 +28,30 @@ export type ScrollGeometry = {
|
|
|
28
28
|
y: number;
|
|
29
29
|
};
|
|
30
30
|
};
|
|
31
|
+
export type ScrollViewHandle = {
|
|
32
|
+
element: HTMLElement | null;
|
|
33
|
+
get scrollTop(): number;
|
|
34
|
+
set scrollTop(value: number);
|
|
35
|
+
get scrollLeft(): number;
|
|
36
|
+
set scrollLeft(value: number);
|
|
37
|
+
get clientWidth(): number;
|
|
38
|
+
get clientHeight(): number;
|
|
39
|
+
get scrollWidth(): number;
|
|
40
|
+
get scrollHeight(): number;
|
|
41
|
+
getBoundingClientRect(): DOMRect;
|
|
42
|
+
scrollTo: (options: ScrollToOptions | number, y?: number) => void;
|
|
43
|
+
};
|
|
44
|
+
export type ScrollViewOverscrollBehavior = 'auto' | 'contain' | 'none';
|
|
31
45
|
type Props = {
|
|
32
46
|
children: Snippet;
|
|
33
|
-
viewport?:
|
|
47
|
+
viewport?: ScrollViewHandle | null;
|
|
34
48
|
axis?: ScrollAxis;
|
|
35
49
|
contentStyles?: StyleProps;
|
|
36
50
|
onScroll?: (geometry: ScrollGeometry) => void;
|
|
37
51
|
scrollGradient?: boolean;
|
|
38
52
|
scrollGradientSize?: number | string;
|
|
53
|
+
showIndicators?: boolean;
|
|
54
|
+
overscrollBehavior?: ScrollViewOverscrollBehavior;
|
|
39
55
|
} & LayoutProps & StyleProps & LoaderProps;
|
|
40
56
|
declare const ScrollView: import("svelte").Component<Props, {}, "viewport">;
|
|
41
57
|
type ScrollView = ReturnType<typeof ScrollView>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default } from './ScrollView.svelte';
|
|
2
|
-
export type { ScrollGeometry } from './ScrollView.svelte';
|
|
2
|
+
export type { ScrollGeometry, ScrollViewHandle, ScrollViewOverscrollBehavior } from './ScrollView.svelte';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
2
|
export const demo = {
|
|
3
|
-
name: '
|
|
3
|
+
name: 'Search Field',
|
|
4
4
|
description: 'Search field with icon, input, and optional confirmation hint.'
|
|
5
5
|
};
|
|
6
6
|
</script>
|
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
|
|
14
14
|
<div class="vstack w-full max-w-sm gap-2">
|
|
15
15
|
<SearchField bind:value placeholder="Search components..." />
|
|
16
|
-
<p class="text-xs text-[var(--sveltely-secondary-color)]">Value: {value || 'empty'}</p>
|
|
16
|
+
<p class="text-xs text-[var(--sveltely-text-secondary-color)]">Value: {value || 'empty'}</p>
|
|
17
17
|
</div>
|