@svelte-atoms/core 1.0.0-alpha.27 → 1.0.0-alpha.29
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/README.md +852 -645
- package/dist/components/accordion/accordion-root.svelte +61 -61
- package/dist/components/accordion/item/accordion-item-body.svelte +42 -42
- package/dist/components/accordion/item/accordion-item-header.svelte +50 -50
- package/dist/components/accordion/item/accordion-item-indicator.svelte +50 -50
- package/dist/components/accordion/item/accordion-item-root.svelte +65 -65
- package/dist/components/alert/alert-actions.svelte +43 -42
- package/dist/components/alert/alert-close-button.svelte +70 -72
- package/dist/components/alert/alert-content.svelte +43 -42
- package/dist/components/alert/alert-description.svelte +42 -41
- package/dist/components/alert/alert-icon.svelte +47 -46
- package/dist/components/alert/alert-root.svelte +103 -102
- package/dist/components/alert/alert-title.svelte +42 -41
- package/dist/components/alert/alert.stories.svelte +384 -23
- package/dist/components/alert/alert.stories.svelte.d.ts +2 -5
- package/dist/components/atom/html-atom.svelte +207 -201
- package/dist/components/atom/html-atom.svelte.d.ts +1 -1
- package/dist/components/atom/snippet-renderer.svelte +5 -0
- package/dist/components/atom/snippet-renderer.svelte.d.ts +5 -0
- package/dist/components/avatar/avatar.stories.svelte.d.ts +1 -1
- package/dist/components/badge/badge.stories.svelte.d.ts +1 -1
- package/dist/components/breadcrumb/breadcrumb.stories.svelte.d.ts +1 -1
- package/dist/components/button/button.stories.svelte +17 -14
- package/dist/components/calendar/atoms.d.ts +5 -0
- package/dist/components/calendar/atoms.js +5 -0
- package/dist/components/calendar/bond.svelte.d.ts +72 -0
- package/dist/components/calendar/bond.svelte.js +132 -0
- package/dist/components/calendar/calendar-body.svelte +107 -0
- package/dist/components/calendar/calendar-body.svelte.d.ts +8 -0
- package/dist/components/calendar/calendar-day.svelte +96 -0
- package/dist/components/calendar/calendar-day.svelte.d.ts +4 -0
- package/dist/components/calendar/calendar-header.svelte +29 -0
- package/dist/components/calendar/calendar-header.svelte.d.ts +6 -0
- package/dist/components/calendar/calendar-root.svelte +206 -0
- package/dist/components/calendar/calendar-root.svelte.d.ts +8 -0
- package/dist/components/calendar/calendar-week-day.svelte +34 -0
- package/dist/components/calendar/calendar-week-day.svelte.d.ts +9 -0
- package/dist/components/calendar/calendar.css +26 -0
- package/dist/components/calendar/calendar.stories.svelte +31 -0
- package/dist/components/calendar/calendar.stories.svelte.d.ts +26 -0
- package/dist/components/calendar/index.d.ts +4 -0
- package/dist/components/calendar/index.js +4 -0
- package/dist/components/calendar/runes.svelte.d.ts +3 -0
- package/dist/components/calendar/runes.svelte.js +25 -0
- package/dist/components/calendar/types.d.ts +62 -0
- package/dist/components/calendar/types.js +1 -0
- package/dist/components/card/card-body.svelte +39 -39
- package/dist/components/card/card-description.svelte +41 -41
- package/dist/components/card/card-footer.svelte +41 -41
- package/dist/components/card/card-header.svelte +41 -41
- package/dist/components/card/card-media.svelte +41 -41
- package/dist/components/card/card-root.svelte +91 -91
- package/dist/components/card/card-subtitle.svelte +41 -41
- package/dist/components/card/card-title.svelte +45 -45
- package/dist/components/collapsible/collapsible-body.svelte +39 -39
- package/dist/components/collapsible/collapsible-header.svelte +39 -39
- package/dist/components/collapsible/collapsible-indicator.svelte +50 -50
- package/dist/components/collapsible/collapsible-root.svelte +66 -66
- package/dist/components/combobox/combobox-root.svelte +65 -65
- package/dist/components/container/container.stories.svelte.d.ts +1 -1
- package/dist/components/contextmenu/contextmenu-trigger.svelte.d.ts +1 -1
- package/dist/components/datagrid/bond.svelte.d.ts +2 -2
- package/dist/components/datagrid/datagrid-body.svelte +37 -37
- package/dist/components/datagrid/datagrid-checkbox.svelte +101 -101
- package/dist/components/datagrid/datagrid-footer.svelte +34 -34
- package/dist/components/datagrid/datagrid-header.svelte +49 -49
- package/dist/components/datagrid/datagrid-root.svelte +2 -2
- package/dist/components/datagrid/datagrid.css +5 -47
- package/dist/components/datagrid/td/datagrid-td.svelte +66 -66
- package/dist/components/datagrid/th/datagrid-th.svelte +106 -106
- package/dist/components/datagrid/tr/datagrid-tr.svelte +88 -88
- package/dist/components/datagrid/types.d.ts +1 -1
- package/dist/components/date-picker/atoms.d.ts +3 -0
- package/dist/components/date-picker/atoms.js +3 -0
- package/dist/components/date-picker/bond.svelte.d.ts +67 -0
- package/dist/components/date-picker/bond.svelte.js +174 -0
- package/dist/components/date-picker/date-picker-calendar.svelte +67 -0
- package/dist/components/date-picker/date-picker-calendar.svelte.d.ts +4 -0
- package/dist/components/date-picker/date-picker-header.svelte +100 -0
- package/dist/components/date-picker/date-picker-header.svelte.d.ts +4 -0
- package/dist/components/date-picker/date-picker-months.svelte +142 -0
- package/dist/components/date-picker/date-picker-months.svelte.d.ts +4 -0
- package/dist/components/date-picker/date-picker-root.svelte +95 -0
- package/dist/components/date-picker/date-picker-root.svelte.d.ts +4 -0
- package/dist/components/date-picker/date-picker-years.svelte +205 -0
- package/dist/components/date-picker/date-picker-years.svelte.d.ts +4 -0
- package/dist/components/date-picker/date-picker.stories.svelte +42 -0
- package/dist/components/date-picker/date-picker.stories.svelte.d.ts +3 -0
- package/dist/components/date-picker/index.d.ts +3 -0
- package/dist/components/date-picker/index.js +3 -0
- package/dist/components/date-picker/types.d.ts +53 -0
- package/dist/components/date-picker/types.js +1 -0
- package/dist/components/dialog/dialog-body.svelte +39 -39
- package/dist/components/dialog/dialog-close-button.svelte +58 -58
- package/dist/components/dialog/dialog-content.svelte +1 -1
- package/dist/components/dialog/dialog-description.svelte +40 -40
- package/dist/components/dialog/dialog-footer.svelte +39 -39
- package/dist/components/dialog/dialog-header.svelte +39 -39
- package/dist/components/dialog/dialog-root.svelte +110 -110
- package/dist/components/dialog/dialog-title.svelte +41 -41
- package/dist/components/drawer/drawer-backdrop.svelte +38 -38
- package/dist/components/drawer/drawer-body.svelte +42 -42
- package/dist/components/drawer/drawer-content.svelte +42 -42
- package/dist/components/drawer/drawer-description.svelte +44 -44
- package/dist/components/drawer/drawer-footer.svelte +41 -41
- package/dist/components/drawer/drawer-header.svelte +43 -43
- package/dist/components/drawer/drawer-root.svelte +93 -93
- package/dist/components/drawer/drawer-title.svelte +44 -44
- package/dist/components/dropdown/dropdown-query.svelte +54 -54
- package/dist/components/dropdown/dropdown-root.svelte +59 -59
- package/dist/components/dropdown/dropdown-trigger.svelte +41 -41
- package/dist/components/dropdown/dropdown-value.svelte +60 -60
- package/dist/components/element/types.d.ts +1 -1
- package/dist/components/form/bond.svelte.d.ts +1 -1
- package/dist/components/form/field/field-control.svelte +48 -48
- package/dist/components/form/field/field-label.svelte +24 -24
- package/dist/components/form/field/field-root.svelte +59 -59
- package/dist/components/icon/icon.svelte +44 -44
- package/dist/components/image/image.stories.svelte.d.ts +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +4 -0
- package/dist/components/input/input-control.svelte +103 -103
- package/dist/components/label/label.svelte +25 -25
- package/dist/components/lazy/index.d.ts +1 -0
- package/dist/components/lazy/index.js +1 -0
- package/dist/components/lazy/lazy.stories.svelte +35 -0
- package/dist/components/lazy/lazy.stories.svelte.d.ts +19 -0
- package/dist/components/lazy/lazy.svelte +28 -0
- package/dist/components/lazy/lazy.svelte.d.ts +5 -0
- package/dist/components/lazy/types.d.ts +10 -0
- package/dist/components/lazy/types.js +1 -0
- package/dist/components/menu/menu-list.svelte +40 -39
- package/dist/components/menu/menu-list.svelte.d.ts +1 -0
- package/dist/components/popover/popover-arrow.svelte +111 -111
- package/dist/components/popover/popover-content.svelte +178 -139
- package/dist/components/popover/popover-root.svelte +16 -17
- package/dist/components/popover/popover.stories.svelte +0 -15
- package/dist/components/portal/portal-root.svelte +83 -83
- package/dist/components/portal/teleport.svelte +50 -50
- package/dist/components/qr-code/index.d.ts +2 -0
- package/dist/components/qr-code/index.js +2 -0
- package/dist/components/qr-code/qr-code.stories.svelte +27 -0
- package/dist/components/qr-code/qr-code.stories.svelte.d.ts +26 -0
- package/dist/components/qr-code/qr-code.svelte +75 -0
- package/dist/components/qr-code/qr-code.svelte.d.ts +4 -0
- package/dist/components/qr-code/types.d.ts +14 -0
- package/dist/components/qr-code/types.js +1 -0
- package/dist/components/radio/radio.svelte +109 -109
- package/dist/components/radio/types.svelte.d.ts +1 -1
- package/dist/components/scrollable/scrollable-container.svelte +82 -82
- package/dist/components/scrollable/scrollable-content.svelte +41 -41
- package/dist/components/scrollable/scrollable-root.svelte +100 -100
- package/dist/components/scrollable/scrollable-thumb.svelte +75 -75
- package/dist/components/scrollable/scrollable-track.svelte +59 -59
- package/dist/components/scrollable/scrollable.stories.svelte.d.ts +1 -1
- package/dist/components/sidebar/bond.svelte.d.ts +0 -5
- package/dist/components/sidebar/bond.svelte.js +1 -12
- package/dist/components/sidebar/sidebar-content.svelte +39 -39
- package/dist/components/sidebar/sidebar-content.svelte.d.ts +2 -2
- package/dist/components/sidebar/sidebar-root.svelte +41 -68
- package/dist/components/sidebar/sidebar-root.svelte.d.ts +2 -2
- package/dist/components/sidebar/sidebar.stories.svelte +54 -52
- package/dist/components/sidebar/types.d.ts +6 -6
- package/dist/components/tabs/tab/tab-body.svelte +52 -52
- package/dist/components/tabs/tab/tab-description.svelte +41 -41
- package/dist/components/tabs/tab/tab-header.svelte +71 -71
- package/dist/components/tabs/tab/tab-root.svelte +86 -86
- package/dist/components/toast/toast-description.svelte +38 -38
- package/dist/components/toast/toast-root.svelte +61 -61
- package/dist/components/toast/toast-title.svelte +35 -35
- package/dist/components/tree/tree-body.svelte +39 -39
- package/dist/components/tree/tree-header.svelte +54 -54
- package/dist/components/tree/tree-indicator.svelte +40 -40
- package/dist/components/tree/tree-root.svelte +65 -65
- package/dist/components/virtual/virtual-root.svelte +239 -239
- package/dist/context/preset.svelte.d.ts +1 -1
- package/dist/icons/icon-arrow-down.svelte.d.ts +1 -1
- package/dist/icons/icon-checkmark.svelte.d.ts +1 -1
- package/dist/icons/icon-close.svelte.d.ts +1 -1
- package/dist/icons/icon-more-vert.svelte.d.ts +1 -1
- package/dist/runes/container.svelte.d.ts +2 -2
- package/dist/shared/bond.svelte.d.ts +1 -1
- package/dist/utils/state.d.ts +1 -1
- package/dist/utils/state.js +2 -1
- package/llm/variants.md +650 -103
- package/package.json +465 -437
|
@@ -1,239 +1,239 @@
|
|
|
1
|
-
<script lang="ts" generics="T">
|
|
2
|
-
import type { VirtualListViewportProps } from './types';
|
|
3
|
-
import { throttle } from 'es-toolkit';
|
|
4
|
-
import { onMount, tick } from 'svelte';
|
|
5
|
-
import { twMerge } from 'tailwind-merge';
|
|
6
|
-
|
|
7
|
-
let {
|
|
8
|
-
class: klass = '',
|
|
9
|
-
data = [],
|
|
10
|
-
itemHeight,
|
|
11
|
-
overscan = 5,
|
|
12
|
-
children,
|
|
13
|
-
header,
|
|
14
|
-
onScroll,
|
|
15
|
-
scrollToIndex,
|
|
16
|
-
...restProps
|
|
17
|
-
}: VirtualListViewportProps<T> = $props();
|
|
18
|
-
|
|
19
|
-
// Internal state
|
|
20
|
-
let start = $state(0);
|
|
21
|
-
let end = $state(0);
|
|
22
|
-
|
|
23
|
-
const visibleItems = $derived(
|
|
24
|
-
data.slice(start, end).map((d, i) => ({ index: i + start, data: d }))
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
let viewportElement: HTMLElement | undefined = $state();
|
|
28
|
-
let contentElement: HTMLElement | undefined = $state();
|
|
29
|
-
let top = $state(0);
|
|
30
|
-
let bottom = $state(0);
|
|
31
|
-
let viewportHeight = $state(0);
|
|
32
|
-
|
|
33
|
-
// Height management with exponential moving average
|
|
34
|
-
let heightMap: number[] = $state([]);
|
|
35
|
-
let averageHeight = $state(itemHeight || 50);
|
|
36
|
-
let heightSampleCount = $state(0);
|
|
37
|
-
|
|
38
|
-
let rows: HTMLElement[] = $state([]);
|
|
39
|
-
let mounted = $state(false);
|
|
40
|
-
|
|
41
|
-
// trigger initial refresh
|
|
42
|
-
onMount(() => {
|
|
43
|
-
if (!contentElement) return;
|
|
44
|
-
|
|
45
|
-
rows = Array.from(contentElement.getElementsByClassName('virtual-list-row')) as HTMLElement[];
|
|
46
|
-
|
|
47
|
-
mounted = true;
|
|
48
|
-
|
|
49
|
-
// Give the browser time to render and measure the viewport
|
|
50
|
-
setTimeout(() => {
|
|
51
|
-
if (viewportElement) {
|
|
52
|
-
viewportHeight = viewportElement.offsetHeight;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
refresh();
|
|
56
|
-
}, 0);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Watch for data changes and viewport size changes
|
|
60
|
-
$effect(() => {
|
|
61
|
-
if (mounted) refresh();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Handle scroll to specific index
|
|
65
|
-
$effect(() => {
|
|
66
|
-
if (scrollToIndex !== undefined && viewportElement && mounted) {
|
|
67
|
-
scrollToItem(scrollToIndex);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const handleScroll = throttle(async () => {
|
|
72
|
-
if (!viewportElement) return;
|
|
73
|
-
|
|
74
|
-
const { scrollTop } = viewportElement;
|
|
75
|
-
|
|
76
|
-
// Call user's onScroll callback
|
|
77
|
-
onScroll?.(scrollTop);
|
|
78
|
-
|
|
79
|
-
await refresh();
|
|
80
|
-
}, 1000 / 60);
|
|
81
|
-
|
|
82
|
-
// Improved height estimation using exponential moving average
|
|
83
|
-
function updateAverageHeight(newHeight: number) {
|
|
84
|
-
if (heightSampleCount < 1) {
|
|
85
|
-
averageHeight = newHeight;
|
|
86
|
-
heightSampleCount = 1;
|
|
87
|
-
} else {
|
|
88
|
-
// Exponential moving average with alpha = 0.1 for stability
|
|
89
|
-
const alpha = Math.min(0.1, 1 / heightSampleCount);
|
|
90
|
-
averageHeight = alpha * newHeight + (1 - alpha) * averageHeight;
|
|
91
|
-
heightSampleCount++;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function getItemHeight(index: number): number {
|
|
96
|
-
return heightMap[index] || itemHeight || averageHeight;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async function refresh() {
|
|
100
|
-
if (!viewportElement || !mounted) return;
|
|
101
|
-
|
|
102
|
-
const { scrollTop } = viewportElement;
|
|
103
|
-
|
|
104
|
-
// Calculate visible range with overscan buffer
|
|
105
|
-
const startIndex = Math.max(0, findStartIndex(scrollTop) - overscan);
|
|
106
|
-
const endIndex = Math.min(data.length, findEndIndex(scrollTop, startIndex) + overscan);
|
|
107
|
-
|
|
108
|
-
start = startIndex;
|
|
109
|
-
end = endIndex;
|
|
110
|
-
|
|
111
|
-
await tick(); // Wait for DOM update
|
|
112
|
-
|
|
113
|
-
// Update height measurements for visible items
|
|
114
|
-
updateHeights();
|
|
115
|
-
updatePadding();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function findStartIndex(scrollTop: number): number {
|
|
119
|
-
let index = 0;
|
|
120
|
-
let accumulatedHeight = 0;
|
|
121
|
-
|
|
122
|
-
while (index < data.length && accumulatedHeight + getItemHeight(index) <= scrollTop) {
|
|
123
|
-
accumulatedHeight += getItemHeight(index);
|
|
124
|
-
index++;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return index;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function findEndIndex(scrollTop: number, startIndex: number): number {
|
|
131
|
-
let index = startIndex;
|
|
132
|
-
let accumulatedHeight = 0;
|
|
133
|
-
|
|
134
|
-
// Start from the scroll position
|
|
135
|
-
for (let i = 0; i < startIndex; i++) {
|
|
136
|
-
accumulatedHeight += getItemHeight(i);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Use a minimum viewport height to ensure we render enough items initially
|
|
140
|
-
const effectiveViewportHeight = Math.max(viewportHeight, 800);
|
|
141
|
-
|
|
142
|
-
while (index < data.length && accumulatedHeight <= scrollTop + effectiveViewportHeight) {
|
|
143
|
-
accumulatedHeight += getItemHeight(index);
|
|
144
|
-
index++;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return index;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function updateHeights() {
|
|
151
|
-
if (!contentElement) return;
|
|
152
|
-
|
|
153
|
-
rows = Array.from(contentElement.getElementsByClassName('virtual-list-row')) as HTMLElement[];
|
|
154
|
-
|
|
155
|
-
// Update heights for currently rendered items
|
|
156
|
-
rows.forEach((row, i) => {
|
|
157
|
-
const actualIndex = start + i;
|
|
158
|
-
const measuredHeight = itemHeight || row.offsetHeight;
|
|
159
|
-
|
|
160
|
-
if (heightMap[actualIndex] !== measuredHeight) {
|
|
161
|
-
heightMap[actualIndex] = measuredHeight;
|
|
162
|
-
if (!itemHeight) {
|
|
163
|
-
updateAverageHeight(measuredHeight);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function updatePadding() {
|
|
170
|
-
// Calculate top padding (sum of heights above visible area)
|
|
171
|
-
let topPadding = 0;
|
|
172
|
-
for (let i = 0; i < start; i++) {
|
|
173
|
-
topPadding += getItemHeight(i);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Calculate bottom padding (estimated heights below visible area)
|
|
177
|
-
let bottomPadding = 0;
|
|
178
|
-
for (let i = end; i < data.length; i++) {
|
|
179
|
-
bottomPadding += getItemHeight(i);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
top = topPadding;
|
|
183
|
-
bottom = bottomPadding;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
async function scrollToItem(index: number) {
|
|
187
|
-
if (!viewportElement || index < 0 || index >= data.length) return;
|
|
188
|
-
|
|
189
|
-
let targetScrollTop = 0;
|
|
190
|
-
for (let i = 0; i < index; i++) {
|
|
191
|
-
targetScrollTop += getItemHeight(i);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
viewportElement.scrollTo({
|
|
195
|
-
top: targetScrollTop,
|
|
196
|
-
behavior: 'smooth'
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
</script>
|
|
200
|
-
|
|
201
|
-
<div class="absolute inset-0 block size-full max-h-full">
|
|
202
|
-
<div
|
|
203
|
-
bind:this={viewportElement}
|
|
204
|
-
bind:offsetHeight={viewportHeight}
|
|
205
|
-
class={twMerge(
|
|
206
|
-
'virtual-list-viewport virtual-list-viewport relative block h-full max-h-full w-full flex-1 overflow-y-auto',
|
|
207
|
-
klass
|
|
208
|
-
)}
|
|
209
|
-
onscroll={handleScroll}
|
|
210
|
-
>
|
|
211
|
-
<table class={twMerge('virtual-list-contents w-full')} {...restProps}>
|
|
212
|
-
{@render header?.()}
|
|
213
|
-
|
|
214
|
-
<tbody bind:this={contentElement}>
|
|
215
|
-
<!-- Top spacer row -->
|
|
216
|
-
{#if top > 0}
|
|
217
|
-
<tr style="height: {top}px;">
|
|
218
|
-
<td colspan="100" style="padding: 0; border: none;"></td>
|
|
219
|
-
</tr>
|
|
220
|
-
{/if}
|
|
221
|
-
|
|
222
|
-
{@render children?.({ items: visibleItems })}
|
|
223
|
-
|
|
224
|
-
<!-- Bottom spacer row -->
|
|
225
|
-
{#if bottom > 0}
|
|
226
|
-
<tr style="height: {bottom}px;">
|
|
227
|
-
<td colspan="100" style="padding: 0; border: none;"></td>
|
|
228
|
-
</tr>
|
|
229
|
-
{/if}
|
|
230
|
-
</tbody>
|
|
231
|
-
</table>
|
|
232
|
-
</div>
|
|
233
|
-
</div>
|
|
234
|
-
|
|
235
|
-
<style>
|
|
236
|
-
.virtual-list-viewport {
|
|
237
|
-
-webkit-overflow-scrolling: touch;
|
|
238
|
-
}
|
|
239
|
-
</style>
|
|
1
|
+
<script lang="ts" generics="T">
|
|
2
|
+
import type { VirtualListViewportProps } from './types';
|
|
3
|
+
import { throttle } from 'es-toolkit';
|
|
4
|
+
import { onMount, tick } from 'svelte';
|
|
5
|
+
import { twMerge } from 'tailwind-merge';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
class: klass = '',
|
|
9
|
+
data = [],
|
|
10
|
+
itemHeight,
|
|
11
|
+
overscan = 5,
|
|
12
|
+
children,
|
|
13
|
+
header,
|
|
14
|
+
onScroll,
|
|
15
|
+
scrollToIndex,
|
|
16
|
+
...restProps
|
|
17
|
+
}: VirtualListViewportProps<T> = $props();
|
|
18
|
+
|
|
19
|
+
// Internal state
|
|
20
|
+
let start = $state(0);
|
|
21
|
+
let end = $state(0);
|
|
22
|
+
|
|
23
|
+
const visibleItems = $derived(
|
|
24
|
+
data.slice(start, end).map((d, i) => ({ index: i + start, data: d }))
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
let viewportElement: HTMLElement | undefined = $state();
|
|
28
|
+
let contentElement: HTMLElement | undefined = $state();
|
|
29
|
+
let top = $state(0);
|
|
30
|
+
let bottom = $state(0);
|
|
31
|
+
let viewportHeight = $state(0);
|
|
32
|
+
|
|
33
|
+
// Height management with exponential moving average
|
|
34
|
+
let heightMap: number[] = $state([]);
|
|
35
|
+
let averageHeight = $state(itemHeight || 50);
|
|
36
|
+
let heightSampleCount = $state(0);
|
|
37
|
+
|
|
38
|
+
let rows: HTMLElement[] = $state([]);
|
|
39
|
+
let mounted = $state(false);
|
|
40
|
+
|
|
41
|
+
// trigger initial refresh
|
|
42
|
+
onMount(() => {
|
|
43
|
+
if (!contentElement) return;
|
|
44
|
+
|
|
45
|
+
rows = Array.from(contentElement.getElementsByClassName('virtual-list-row')) as HTMLElement[];
|
|
46
|
+
|
|
47
|
+
mounted = true;
|
|
48
|
+
|
|
49
|
+
// Give the browser time to render and measure the viewport
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
if (viewportElement) {
|
|
52
|
+
viewportHeight = viewportElement.offsetHeight;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
refresh();
|
|
56
|
+
}, 0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Watch for data changes and viewport size changes
|
|
60
|
+
$effect(() => {
|
|
61
|
+
if (mounted) refresh();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Handle scroll to specific index
|
|
65
|
+
$effect(() => {
|
|
66
|
+
if (scrollToIndex !== undefined && viewportElement && mounted) {
|
|
67
|
+
scrollToItem(scrollToIndex);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const handleScroll = throttle(async () => {
|
|
72
|
+
if (!viewportElement) return;
|
|
73
|
+
|
|
74
|
+
const { scrollTop } = viewportElement;
|
|
75
|
+
|
|
76
|
+
// Call user's onScroll callback
|
|
77
|
+
onScroll?.(scrollTop);
|
|
78
|
+
|
|
79
|
+
await refresh();
|
|
80
|
+
}, 1000 / 60);
|
|
81
|
+
|
|
82
|
+
// Improved height estimation using exponential moving average
|
|
83
|
+
function updateAverageHeight(newHeight: number) {
|
|
84
|
+
if (heightSampleCount < 1) {
|
|
85
|
+
averageHeight = newHeight;
|
|
86
|
+
heightSampleCount = 1;
|
|
87
|
+
} else {
|
|
88
|
+
// Exponential moving average with alpha = 0.1 for stability
|
|
89
|
+
const alpha = Math.min(0.1, 1 / heightSampleCount);
|
|
90
|
+
averageHeight = alpha * newHeight + (1 - alpha) * averageHeight;
|
|
91
|
+
heightSampleCount++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function getItemHeight(index: number): number {
|
|
96
|
+
return heightMap[index] || itemHeight || averageHeight;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function refresh() {
|
|
100
|
+
if (!viewportElement || !mounted) return;
|
|
101
|
+
|
|
102
|
+
const { scrollTop } = viewportElement;
|
|
103
|
+
|
|
104
|
+
// Calculate visible range with overscan buffer
|
|
105
|
+
const startIndex = Math.max(0, findStartIndex(scrollTop) - overscan);
|
|
106
|
+
const endIndex = Math.min(data.length, findEndIndex(scrollTop, startIndex) + overscan);
|
|
107
|
+
|
|
108
|
+
start = startIndex;
|
|
109
|
+
end = endIndex;
|
|
110
|
+
|
|
111
|
+
await tick(); // Wait for DOM update
|
|
112
|
+
|
|
113
|
+
// Update height measurements for visible items
|
|
114
|
+
updateHeights();
|
|
115
|
+
updatePadding();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function findStartIndex(scrollTop: number): number {
|
|
119
|
+
let index = 0;
|
|
120
|
+
let accumulatedHeight = 0;
|
|
121
|
+
|
|
122
|
+
while (index < data.length && accumulatedHeight + getItemHeight(index) <= scrollTop) {
|
|
123
|
+
accumulatedHeight += getItemHeight(index);
|
|
124
|
+
index++;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return index;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function findEndIndex(scrollTop: number, startIndex: number): number {
|
|
131
|
+
let index = startIndex;
|
|
132
|
+
let accumulatedHeight = 0;
|
|
133
|
+
|
|
134
|
+
// Start from the scroll position
|
|
135
|
+
for (let i = 0; i < startIndex; i++) {
|
|
136
|
+
accumulatedHeight += getItemHeight(i);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Use a minimum viewport height to ensure we render enough items initially
|
|
140
|
+
const effectiveViewportHeight = Math.max(viewportHeight, 800);
|
|
141
|
+
|
|
142
|
+
while (index < data.length && accumulatedHeight <= scrollTop + effectiveViewportHeight) {
|
|
143
|
+
accumulatedHeight += getItemHeight(index);
|
|
144
|
+
index++;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return index;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function updateHeights() {
|
|
151
|
+
if (!contentElement) return;
|
|
152
|
+
|
|
153
|
+
rows = Array.from(contentElement.getElementsByClassName('virtual-list-row')) as HTMLElement[];
|
|
154
|
+
|
|
155
|
+
// Update heights for currently rendered items
|
|
156
|
+
rows.forEach((row, i) => {
|
|
157
|
+
const actualIndex = start + i;
|
|
158
|
+
const measuredHeight = itemHeight || row.offsetHeight;
|
|
159
|
+
|
|
160
|
+
if (heightMap[actualIndex] !== measuredHeight) {
|
|
161
|
+
heightMap[actualIndex] = measuredHeight;
|
|
162
|
+
if (!itemHeight) {
|
|
163
|
+
updateAverageHeight(measuredHeight);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function updatePadding() {
|
|
170
|
+
// Calculate top padding (sum of heights above visible area)
|
|
171
|
+
let topPadding = 0;
|
|
172
|
+
for (let i = 0; i < start; i++) {
|
|
173
|
+
topPadding += getItemHeight(i);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Calculate bottom padding (estimated heights below visible area)
|
|
177
|
+
let bottomPadding = 0;
|
|
178
|
+
for (let i = end; i < data.length; i++) {
|
|
179
|
+
bottomPadding += getItemHeight(i);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
top = topPadding;
|
|
183
|
+
bottom = bottomPadding;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function scrollToItem(index: number) {
|
|
187
|
+
if (!viewportElement || index < 0 || index >= data.length) return;
|
|
188
|
+
|
|
189
|
+
let targetScrollTop = 0;
|
|
190
|
+
for (let i = 0; i < index; i++) {
|
|
191
|
+
targetScrollTop += getItemHeight(i);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
viewportElement.scrollTo({
|
|
195
|
+
top: targetScrollTop,
|
|
196
|
+
behavior: 'smooth'
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
</script>
|
|
200
|
+
|
|
201
|
+
<div class="absolute inset-0 block size-full max-h-full">
|
|
202
|
+
<div
|
|
203
|
+
bind:this={viewportElement}
|
|
204
|
+
bind:offsetHeight={viewportHeight}
|
|
205
|
+
class={twMerge(
|
|
206
|
+
'virtual-list-viewport virtual-list-viewport relative block h-full max-h-full w-full flex-1 overflow-y-auto',
|
|
207
|
+
klass
|
|
208
|
+
)}
|
|
209
|
+
onscroll={handleScroll}
|
|
210
|
+
>
|
|
211
|
+
<table class={twMerge('virtual-list-contents w-full')} {...restProps}>
|
|
212
|
+
{@render header?.()}
|
|
213
|
+
|
|
214
|
+
<tbody bind:this={contentElement}>
|
|
215
|
+
<!-- Top spacer row -->
|
|
216
|
+
{#if top > 0}
|
|
217
|
+
<tr style="height: {top}px;">
|
|
218
|
+
<td colspan="100" style="padding: 0; border: none;"></td>
|
|
219
|
+
</tr>
|
|
220
|
+
{/if}
|
|
221
|
+
|
|
222
|
+
{@render children?.({ items: visibleItems })}
|
|
223
|
+
|
|
224
|
+
<!-- Bottom spacer row -->
|
|
225
|
+
{#if bottom > 0}
|
|
226
|
+
<tr style="height: {bottom}px;">
|
|
227
|
+
<td colspan="100" style="padding: 0; border: none;"></td>
|
|
228
|
+
</tr>
|
|
229
|
+
{/if}
|
|
230
|
+
</tbody>
|
|
231
|
+
</table>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<style>
|
|
236
|
+
.virtual-list-viewport {
|
|
237
|
+
-webkit-overflow-scrolling: touch;
|
|
238
|
+
}
|
|
239
|
+
</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ClassValue } from 'svelte/elements';
|
|
2
2
|
import type { Base } from '../components/atom';
|
|
3
3
|
import type { Bond } from '../shared';
|
|
4
|
-
export type PresetModuleName = 'accordion' | 'accordion.item.body' | 'accordion.item.header' | 'accordion.item.indicator' | 'accordion.item' | 'alert.actions' | 'alert.close-button' | 'alert.content' | 'alert.description' | 'alert.icon' | 'alert' | 'alert.title' | 'card.content' | 'card.description' | 'card.footer' | 'card.header' | 'card.media' | 'card' | 'card.subtitle' | 'card.title' | 'collapsible.body' | 'collapsible.header' | 'collapsible.indicator' | 'collapsible' | 'combobox.control' | 'combobox.trigger' | 'combobox.item' | 'dialog.close-button' | 'dialog.body' | 'dialog.content' | 'dialog.description' | 'dialog.footer' | 'dialog.header' | 'dialog' | 'dialog.title' | 'divider' | 'dropdown.placeholder' | 'dropdown.query' | 'dropdown.trigger' | 'dropdown.value' | 'dropdown' | 'field.control' | 'field.label' | 'field' | 'form' | 'icon' | 'input' | 'input.control' | 'input.placeholder' | 'label' | 'layer.inner' | 'layer' | 'link' | 'list.divider' | 'list.group' | 'list.item' | 'list.item' | 'list.item' | 'dropdown.item' | 'menu.body' | 'popover.arrow' | 'popover.indicator' | 'popover.content' | 'popover.trigger' | 'portal.inner' | 'portal' | 'root' | 'root.portals' | 'sidebar.content' | 'sidebar' | 'drawer.backdrop' | 'drawer.body' | 'drawer.content' | 'drawer.description' | 'drawer.title' | 'drawer.footer' | 'drawer.header' | 'drawer' | 'stack.root' | 'stack.item' | 'tabs.body' | 'tabs.header' | 'tabs' | 'tab.header' | 'tab.body' | 'tab.description' | 'tab' | 'tree.body' | 'tree.header' | 'tree.indicator' | 'tree' | 'datagrid' | 'datagrid.header' | 'datagrid.th' | 'datagrid.body' | 'datagrid.tr' | 'datagrid.td' | 'datagrid.footer' | 'datagrid.checkbox' | 'datagrid.sort-icon' | 'scrollable' | 'scrollable.container' | 'scrollable.content' | 'scrollable.track' | 'scrollable.thumb' | 'breadcrumb' | 'breadcrumb.item' | 'breadcrumb.separator' | 'badge' | 'button' | 'checkbox' | 'checkbox.checkmark' | 'checkbox.indeterminate' | 'radio' | 'radio.group' | 'container';
|
|
4
|
+
export type PresetModuleName = 'accordion' | 'accordion.item.body' | 'accordion.item.header' | 'accordion.item.indicator' | 'accordion.item' | 'alert.actions' | 'alert.close-button' | 'alert.content' | 'alert.description' | 'alert.icon' | 'alert' | 'alert.title' | 'card.content' | 'card.description' | 'card.footer' | 'card.header' | 'card.media' | 'card' | 'card.subtitle' | 'card.title' | 'collapsible.body' | 'collapsible.header' | 'collapsible.indicator' | 'collapsible' | 'combobox.control' | 'combobox.trigger' | 'combobox.item' | 'dialog.close-button' | 'dialog.body' | 'dialog.content' | 'dialog.description' | 'dialog.footer' | 'dialog.header' | 'dialog' | 'dialog.title' | 'divider' | 'dropdown.placeholder' | 'dropdown.query' | 'dropdown.trigger' | 'dropdown.value' | 'dropdown' | 'field.control' | 'field.label' | 'field' | 'form' | 'icon' | 'input' | 'input.control' | 'input.placeholder' | 'label' | 'layer.inner' | 'layer' | 'link' | 'list.divider' | 'list.group' | 'list.item' | 'list.item' | 'list.item' | 'dropdown.item' | 'menu.body' | 'popover.arrow' | 'popover.indicator' | 'popover.content' | 'popover.trigger' | 'portal.inner' | 'portal' | 'root' | 'root.portals' | 'sidebar.content' | 'sidebar' | 'drawer.backdrop' | 'drawer.body' | 'drawer.content' | 'drawer.description' | 'drawer.title' | 'drawer.footer' | 'drawer.header' | 'drawer' | 'stack.root' | 'stack.item' | 'tabs.body' | 'tabs.header' | 'tabs' | 'tab.header' | 'tab.body' | 'tab.description' | 'tab' | 'tree.body' | 'tree.header' | 'tree.indicator' | 'tree' | 'datagrid' | 'datagrid.header' | 'datagrid.th' | 'datagrid.body' | 'datagrid.tr' | 'datagrid.td' | 'datagrid.footer' | 'datagrid.checkbox' | 'datagrid.sort-icon' | 'scrollable' | 'scrollable.container' | 'scrollable.content' | 'scrollable.track' | 'scrollable.thumb' | 'breadcrumb' | 'breadcrumb.item' | 'breadcrumb.separator' | 'badge' | 'button' | 'checkbox' | 'checkbox.checkmark' | 'checkbox.indeterminate' | 'radio' | 'radio.group' | 'container' | 'calendar' | 'calendar.day' | 'calendar.header' | 'calendar.weekday' | 'calendar.body' | 'datepicker.trigger' | 'datepicker.calendar' | 'datepicker.years' | 'datepicker.months' | 'datepicker.header';
|
|
5
5
|
export type PresetEntryRecord = {
|
|
6
6
|
[key: string]: unknown;
|
|
7
7
|
class?: ClassValue;
|
package/dist/utils/state.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type StateDefiner<T extends object> = (base: T) => T;
|
|
2
2
|
export declare function defineState<T extends object>(definers: StateDefiner<Partial<T>>[], base?: () => Partial<T>): T;
|
|
3
|
-
export declare function defineProperty<T extends
|
|
3
|
+
export declare function defineProperty<T extends Record<string, unknown>, R>(property: keyof T | (string & {}), get: () => R, set?: (value: R) => void): (base: T) => T;
|