@svelte-atoms/core 1.0.0-alpha.26 → 1.0.0-alpha.27

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.
Files changed (282) hide show
  1. package/dist/components/accordion/accordion-root.svelte +61 -79
  2. package/dist/components/accordion/accordion-root.svelte.d.ts +2 -15
  3. package/dist/components/accordion/index.d.ts +2 -1
  4. package/dist/components/accordion/index.js +2 -1
  5. package/dist/components/accordion/item/accordion-item-body.svelte +42 -52
  6. package/dist/components/accordion/item/accordion-item-body.svelte.d.ts +2 -8
  7. package/dist/components/accordion/item/accordion-item-header.svelte +50 -60
  8. package/dist/components/accordion/item/accordion-item-header.svelte.d.ts +3 -20
  9. package/dist/components/accordion/item/accordion-item-indicator.svelte +50 -59
  10. package/dist/components/accordion/item/accordion-item-indicator.svelte.d.ts +2 -8
  11. package/dist/components/accordion/item/accordion-item-root.svelte +65 -79
  12. package/dist/components/accordion/item/accordion-item-root.svelte.d.ts +2 -12
  13. package/dist/components/accordion/item/index.d.ts +1 -0
  14. package/dist/components/accordion/item/types.d.ts +52 -0
  15. package/dist/components/accordion/item/types.js +1 -0
  16. package/dist/components/accordion/types.d.ts +21 -0
  17. package/dist/components/accordion/types.js +1 -0
  18. package/dist/components/alert/alert-actions.svelte +42 -52
  19. package/dist/components/alert/alert-actions.svelte.d.ts +3 -30
  20. package/dist/components/alert/alert-close-button.svelte +72 -79
  21. package/dist/components/alert/alert-close-button.svelte.d.ts +8 -35
  22. package/dist/components/alert/alert-content.svelte +42 -52
  23. package/dist/components/alert/alert-content.svelte.d.ts +3 -30
  24. package/dist/components/alert/alert-description.svelte +41 -51
  25. package/dist/components/alert/alert-description.svelte.d.ts +7 -10
  26. package/dist/components/alert/alert-icon.svelte +46 -56
  27. package/dist/components/alert/alert-icon.svelte.d.ts +2 -8
  28. package/dist/components/alert/alert-root.svelte +102 -118
  29. package/dist/components/alert/alert-root.svelte.d.ts +2 -13
  30. package/dist/components/alert/alert-title.svelte +41 -51
  31. package/dist/components/alert/alert-title.svelte.d.ts +2 -8
  32. package/dist/components/alert/index.d.ts +1 -0
  33. package/dist/components/alert/index.js +1 -0
  34. package/dist/components/alert/types.d.ts +85 -0
  35. package/dist/components/alert/types.js +1 -0
  36. package/dist/components/atom/html-atom.svelte.d.ts +2 -22
  37. package/dist/components/atom/types.d.ts +7 -2
  38. package/dist/components/avatar/types.d.ts +7 -2
  39. package/dist/components/badge/types.d.ts +7 -2
  40. package/dist/components/button/index.d.ts +1 -0
  41. package/dist/components/button/index.js +1 -0
  42. package/dist/components/button/types.d.ts +7 -2
  43. package/dist/components/card/card-body.svelte +39 -45
  44. package/dist/components/card/card-body.svelte.d.ts +7 -4
  45. package/dist/components/card/card-description.svelte +41 -48
  46. package/dist/components/card/card-description.svelte.d.ts +7 -7
  47. package/dist/components/card/card-footer.svelte +41 -48
  48. package/dist/components/card/card-footer.svelte.d.ts +7 -4
  49. package/dist/components/card/card-header.svelte +41 -48
  50. package/dist/components/card/card-header.svelte.d.ts +7 -4
  51. package/dist/components/card/card-media.svelte +41 -48
  52. package/dist/components/card/card-media.svelte.d.ts +7 -4
  53. package/dist/components/card/card-root.svelte +91 -91
  54. package/dist/components/card/card-root.svelte.d.ts +1 -1
  55. package/dist/components/card/card-subtitle.svelte +41 -48
  56. package/dist/components/card/card-subtitle.svelte.d.ts +12 -9
  57. package/dist/components/card/card-title.svelte +45 -52
  58. package/dist/components/card/card-title.svelte.d.ts +12 -9
  59. package/dist/components/card/index.d.ts +1 -0
  60. package/dist/components/card/index.js +1 -0
  61. package/dist/components/card/types.d.ts +57 -2
  62. package/dist/components/checkbox/types.d.ts +7 -2
  63. package/dist/components/collapsible/collapsible-body.svelte +39 -52
  64. package/dist/components/collapsible/collapsible-body.svelte.d.ts +2 -9
  65. package/dist/components/collapsible/collapsible-header.svelte +39 -52
  66. package/dist/components/collapsible/collapsible-header.svelte.d.ts +2 -9
  67. package/dist/components/collapsible/collapsible-indicator.svelte +50 -65
  68. package/dist/components/collapsible/collapsible-indicator.svelte.d.ts +3 -10
  69. package/dist/components/collapsible/collapsible-root.svelte +66 -85
  70. package/dist/components/collapsible/collapsible-root.svelte.d.ts +2 -14
  71. package/dist/components/collapsible/index.d.ts +1 -0
  72. package/dist/components/collapsible/index.js +1 -0
  73. package/dist/components/collapsible/types.d.ts +54 -0
  74. package/dist/components/collapsible/types.js +1 -0
  75. package/dist/components/combobox/combobox-root.svelte +65 -68
  76. package/dist/components/combobox/combobox-root.svelte.d.ts +5 -18
  77. package/dist/components/combobox/index.d.ts +1 -0
  78. package/dist/components/combobox/index.js +1 -0
  79. package/dist/components/combobox/types.d.ts +25 -0
  80. package/dist/components/combobox/types.js +1 -0
  81. package/dist/components/container/types.d.ts +7 -2
  82. package/dist/components/contextmenu/types.d.ts +8 -0
  83. package/dist/components/contextmenu/types.js +1 -0
  84. package/dist/components/datagrid/datagrid-body.svelte +37 -44
  85. package/dist/components/datagrid/datagrid-body.svelte.d.ts +17 -20
  86. package/dist/components/datagrid/datagrid-checkbox.svelte +101 -108
  87. package/dist/components/datagrid/datagrid-checkbox.svelte.d.ts +4 -6
  88. package/dist/components/datagrid/datagrid-footer.svelte +34 -34
  89. package/dist/components/datagrid/datagrid-footer.svelte.d.ts +1 -1
  90. package/dist/components/datagrid/datagrid-header.svelte +49 -49
  91. package/dist/components/datagrid/datagrid-header.svelte.d.ts +1 -1
  92. package/dist/components/datagrid/datagrid-root.svelte +59 -59
  93. package/dist/components/datagrid/datagrid-root.svelte.d.ts +1 -1
  94. package/dist/components/datagrid/td/datagrid-td.svelte +66 -80
  95. package/dist/components/datagrid/td/datagrid-td.svelte.d.ts +7 -16
  96. package/dist/components/datagrid/th/datagrid-th.svelte +106 -127
  97. package/dist/components/datagrid/th/datagrid-th.svelte.d.ts +2 -20
  98. package/dist/components/datagrid/tr/bond.svelte.d.ts +3 -1
  99. package/dist/components/datagrid/tr/bond.svelte.js +4 -2
  100. package/dist/components/datagrid/tr/datagrid-tr.svelte +88 -103
  101. package/dist/components/datagrid/tr/datagrid-tr.svelte.d.ts +2 -18
  102. package/dist/components/datagrid/types.d.ts +85 -37
  103. package/dist/components/dialog/dialog-body.svelte +39 -45
  104. package/dist/components/dialog/dialog-body.svelte.d.ts +2 -2
  105. package/dist/components/dialog/dialog-close-button.svelte +58 -61
  106. package/dist/components/dialog/dialog-close-button.svelte.d.ts +7 -7
  107. package/dist/components/dialog/dialog-content.svelte +62 -68
  108. package/dist/components/dialog/dialog-content.svelte.d.ts +2 -2
  109. package/dist/components/dialog/dialog-description.svelte +40 -46
  110. package/dist/components/dialog/dialog-description.svelte.d.ts +2 -2
  111. package/dist/components/dialog/dialog-footer.svelte +39 -45
  112. package/dist/components/dialog/dialog-footer.svelte.d.ts +2 -2
  113. package/dist/components/dialog/dialog-header.svelte +39 -45
  114. package/dist/components/dialog/dialog-header.svelte.d.ts +2 -2
  115. package/dist/components/dialog/dialog-root.svelte +3 -16
  116. package/dist/components/dialog/dialog-root.svelte.d.ts +2 -12
  117. package/dist/components/dialog/dialog-title.svelte +41 -47
  118. package/dist/components/dialog/dialog-title.svelte.d.ts +7 -7
  119. package/dist/components/dialog/index.d.ts +1 -0
  120. package/dist/components/dialog/index.js +1 -0
  121. package/dist/components/dialog/types.d.ts +67 -0
  122. package/dist/components/dialog/types.js +1 -0
  123. package/dist/components/divider/types.d.ts +10 -0
  124. package/dist/components/divider/types.js +1 -0
  125. package/dist/components/drawer/drawer-backdrop.svelte +38 -47
  126. package/dist/components/drawer/drawer-backdrop.svelte.d.ts +3 -26
  127. package/dist/components/drawer/drawer-body.svelte +42 -56
  128. package/dist/components/drawer/drawer-body.svelte.d.ts +3 -16
  129. package/dist/components/drawer/drawer-content.svelte +42 -55
  130. package/dist/components/drawer/drawer-content.svelte.d.ts +3 -14
  131. package/dist/components/drawer/drawer-description.svelte +44 -57
  132. package/dist/components/drawer/drawer-description.svelte.d.ts +3 -14
  133. package/dist/components/drawer/drawer-footer.svelte +41 -54
  134. package/dist/components/drawer/drawer-footer.svelte.d.ts +3 -14
  135. package/dist/components/drawer/drawer-header.svelte +43 -56
  136. package/dist/components/drawer/drawer-header.svelte.d.ts +3 -14
  137. package/dist/components/drawer/drawer-root.svelte +3 -28
  138. package/dist/components/drawer/drawer-root.svelte.d.ts +3 -34
  139. package/dist/components/drawer/drawer-title.svelte +44 -57
  140. package/dist/components/drawer/drawer-title.svelte.d.ts +3 -14
  141. package/dist/components/drawer/index.d.ts +1 -0
  142. package/dist/components/drawer/index.js +1 -0
  143. package/dist/components/drawer/types.d.ts +86 -0
  144. package/dist/components/drawer/types.js +1 -0
  145. package/dist/components/dropdown/dropdown-query.svelte +54 -53
  146. package/dist/components/dropdown/dropdown-query.svelte.d.ts +11 -10
  147. package/dist/components/dropdown/dropdown-root.svelte +59 -59
  148. package/dist/components/dropdown/dropdown-trigger.svelte +41 -52
  149. package/dist/components/dropdown/dropdown-trigger.svelte.d.ts +1 -8
  150. package/dist/components/dropdown/dropdown-value.svelte +60 -60
  151. package/dist/components/dropdown/index.d.ts +1 -0
  152. package/dist/components/dropdown/index.js +1 -0
  153. package/dist/components/dropdown/types.d.ts +37 -0
  154. package/dist/components/dropdown/types.js +1 -0
  155. package/dist/components/element/html-element.svelte.d.ts +2 -14
  156. package/dist/components/element/svg-element.svelte.d.ts +2 -14
  157. package/dist/components/element/types.d.ts +14 -7
  158. package/dist/components/form/field/field-control.svelte +48 -65
  159. package/dist/components/form/field/field-control.svelte.d.ts +5 -19
  160. package/dist/components/form/field/field-label.svelte +24 -31
  161. package/dist/components/form/field/field-label.svelte.d.ts +1 -2
  162. package/dist/components/form/field/field-root.svelte +59 -88
  163. package/dist/components/form/field/field-root.svelte.d.ts +5 -20
  164. package/dist/components/form/index.d.ts +1 -0
  165. package/dist/components/form/index.js +1 -0
  166. package/dist/components/form/types.d.ts +76 -0
  167. package/dist/components/form/types.js +1 -0
  168. package/dist/components/icon/icon.svelte +44 -55
  169. package/dist/components/icon/icon.svelte.d.ts +4 -29
  170. package/dist/components/icon/types.d.ts +11 -7
  171. package/dist/components/input/index.d.ts +1 -0
  172. package/dist/components/input/index.js +1 -0
  173. package/dist/components/input/input-control.svelte +103 -107
  174. package/dist/components/input/input-control.svelte.d.ts +2 -3
  175. package/dist/components/input/input-icon.svelte.d.ts +1 -1
  176. package/dist/components/input/input-placeholder.svelte.d.ts +2 -19
  177. package/dist/components/input/types.d.ts +18 -7
  178. package/dist/components/label/index.d.ts +1 -0
  179. package/dist/components/label/index.js +1 -0
  180. package/dist/components/label/label.svelte +25 -41
  181. package/dist/components/label/label.svelte.d.ts +3 -27
  182. package/dist/components/label/types.d.ts +11 -0
  183. package/dist/components/label/types.js +1 -0
  184. package/dist/components/layer/layer-inner.svelte.d.ts +2 -19
  185. package/dist/components/layer/layer-root.svelte.d.ts +2 -19
  186. package/dist/components/layer/types.d.ts +11 -0
  187. package/dist/components/layer/types.js +1 -0
  188. package/dist/components/link/types.d.ts +8 -0
  189. package/dist/components/link/types.js +1 -0
  190. package/dist/components/list/types.d.ts +8 -0
  191. package/dist/components/list/types.js +1 -0
  192. package/dist/components/menu/index.d.ts +1 -0
  193. package/dist/components/menu/index.js +1 -0
  194. package/dist/components/menu/types.d.ts +15 -0
  195. package/dist/components/menu/types.js +1 -0
  196. package/dist/components/popover/index.d.ts +1 -0
  197. package/dist/components/popover/index.js +1 -0
  198. package/dist/components/popover/popover-arrow.svelte +1 -1
  199. package/dist/components/popover/popover-arrow.svelte.d.ts +2 -18
  200. package/dist/components/popover/popover-content.svelte +1 -1
  201. package/dist/components/popover/popover-root.svelte +49 -49
  202. package/dist/components/popover/popover-trigger.svelte +47 -47
  203. package/dist/components/popover/types.d.ts +32 -10
  204. package/dist/components/portal/active-portal.svelte +22 -22
  205. package/dist/components/portal/index.d.ts +1 -0
  206. package/dist/components/portal/index.js +1 -0
  207. package/dist/components/portal/portal-inner.svelte.d.ts +2 -19
  208. package/dist/components/portal/portal-root.svelte +83 -88
  209. package/dist/components/portal/portal-root.svelte.d.ts +2 -22
  210. package/dist/components/portal/teleport.svelte +4 -9
  211. package/dist/components/portal/teleport.svelte.d.ts +3 -22
  212. package/dist/components/portal/types.d.ts +39 -0
  213. package/dist/components/portal/types.js +1 -0
  214. package/dist/components/radio/radio.svelte +109 -109
  215. package/dist/components/radio/radio.svelte.d.ts +14 -36
  216. package/dist/components/root/root.svelte +121 -121
  217. package/dist/components/root/types.d.ts +8 -0
  218. package/dist/components/root/types.js +1 -0
  219. package/dist/components/scrollable/index.d.ts +1 -0
  220. package/dist/components/scrollable/index.js +1 -0
  221. package/dist/components/scrollable/scrollable-container.svelte +82 -89
  222. package/dist/components/scrollable/scrollable-container.svelte.d.ts +2 -6
  223. package/dist/components/scrollable/scrollable-content.svelte +41 -51
  224. package/dist/components/scrollable/scrollable-content.svelte.d.ts +1 -6
  225. package/dist/components/scrollable/scrollable-root.svelte +100 -120
  226. package/dist/components/scrollable/scrollable-root.svelte.d.ts +3 -19
  227. package/dist/components/scrollable/scrollable-thumb.svelte +75 -86
  228. package/dist/components/scrollable/scrollable-thumb.svelte.d.ts +1 -7
  229. package/dist/components/scrollable/scrollable-track.svelte +59 -70
  230. package/dist/components/scrollable/scrollable-track.svelte.d.ts +1 -7
  231. package/dist/components/scrollable/types.d.ts +62 -0
  232. package/dist/components/scrollable/types.js +1 -0
  233. package/dist/components/sidebar/index.d.ts +1 -0
  234. package/dist/components/sidebar/index.js +1 -0
  235. package/dist/components/sidebar/types.d.ts +16 -5
  236. package/dist/components/stack/stack-root.svelte.d.ts +2 -19
  237. package/dist/components/stack/types.d.ts +12 -0
  238. package/dist/components/stack/types.js +1 -0
  239. package/dist/components/tabs/index.d.ts +1 -0
  240. package/dist/components/tabs/index.js +1 -0
  241. package/dist/components/tabs/tab/tab-body.svelte +52 -61
  242. package/dist/components/tabs/tab/tab-body.svelte.d.ts +2 -8
  243. package/dist/components/tabs/tab/tab-description.svelte +41 -50
  244. package/dist/components/tabs/tab/tab-description.svelte.d.ts +2 -8
  245. package/dist/components/tabs/tab/tab-header.svelte +71 -81
  246. package/dist/components/tabs/tab/tab-header.svelte.d.ts +2 -11
  247. package/dist/components/tabs/tab/tab-root.svelte +86 -86
  248. package/dist/components/tabs/types.d.ts +55 -0
  249. package/dist/components/tabs/types.js +1 -0
  250. package/dist/components/textarea/index.d.ts +1 -0
  251. package/dist/components/textarea/index.js +1 -0
  252. package/dist/components/textarea/types.d.ts +28 -0
  253. package/dist/components/textarea/types.js +1 -0
  254. package/dist/components/toast/index.d.ts +1 -0
  255. package/dist/components/toast/index.js +1 -0
  256. package/dist/components/toast/toast-description.svelte +38 -44
  257. package/dist/components/toast/toast-description.svelte.d.ts +8 -34
  258. package/dist/components/toast/toast-root.svelte +61 -74
  259. package/dist/components/toast/toast-root.svelte.d.ts +4 -43
  260. package/dist/components/toast/toast-title.svelte +35 -43
  261. package/dist/components/toast/toast-title.svelte.d.ts +2 -34
  262. package/dist/components/toast/types.d.ts +40 -0
  263. package/dist/components/toast/types.js +1 -0
  264. package/dist/components/tooltip/types.d.ts +13 -0
  265. package/dist/components/tooltip/types.js +1 -0
  266. package/dist/components/tree/index.d.ts +1 -0
  267. package/dist/components/tree/index.js +1 -0
  268. package/dist/components/tree/tree-body.svelte +39 -50
  269. package/dist/components/tree/tree-body.svelte.d.ts +2 -10
  270. package/dist/components/tree/tree-header.svelte +54 -66
  271. package/dist/components/tree/tree-header.svelte.d.ts +3 -29
  272. package/dist/components/tree/tree-indicator.svelte +40 -50
  273. package/dist/components/tree/tree-indicator.svelte.d.ts +3 -9
  274. package/dist/components/tree/tree-root.svelte +65 -80
  275. package/dist/components/tree/tree-root.svelte.d.ts +2 -12
  276. package/dist/components/tree/types.d.ts +59 -0
  277. package/dist/components/tree/types.js +1 -0
  278. package/dist/components/virtual/types.d.ts +23 -0
  279. package/dist/components/virtual/types.js +1 -0
  280. package/dist/components/virtual/virtual-root.svelte +239 -258
  281. package/dist/components/virtual/virtual-root.svelte.d.ts +1 -18
  282. package/package.json +1 -1
@@ -1,258 +1,239 @@
1
- <script module lang="ts">
2
- export type VirtualListViewportProps<T> = {
3
- class?: string;
4
- data: T[];
5
- itemHeight?: number; // Optional for dynamic heights
6
- overscan?: number; // Buffer items to render above/below viewport
7
- onScroll?: (scrollTop: number) => void;
8
- scrollToIndex?: number; // Scroll to specific item
9
- header?: Snippet;
10
- children: Snippet<
11
- [
12
- {
13
- items: { index: number; data: T }[];
14
- }
15
- ]
16
- >;
17
- };
18
- </script>
19
-
20
- <script lang="ts" generics="T">
21
- import { throttle } from 'es-toolkit';
22
-
23
- import { onMount, tick, type Snippet } from 'svelte';
24
- import { twMerge } from 'tailwind-merge';
25
-
26
- let {
27
- class: klass = '',
28
- data = [],
29
- itemHeight,
30
- overscan = 5,
31
- children,
32
- header,
33
- onScroll,
34
- scrollToIndex,
35
- ...restProps
36
- }: VirtualListViewportProps<T> = $props();
37
-
38
- // Internal state
39
- let start = $state(0);
40
- let end = $state(0);
41
-
42
- const visibleItems = $derived(
43
- data.slice(start, end).map((d, i) => ({ index: i + start, data: d }))
44
- );
45
-
46
- let viewportElement: HTMLElement | undefined = $state();
47
- let contentElement: HTMLElement | undefined = $state();
48
- let top = $state(0);
49
- let bottom = $state(0);
50
- let viewportHeight = $state(0);
51
-
52
- // Height management with exponential moving average
53
- let heightMap: number[] = $state([]);
54
- let averageHeight = $state(itemHeight || 50);
55
- let heightSampleCount = $state(0);
56
-
57
- let rows: HTMLElement[] = $state([]);
58
- let mounted = $state(false);
59
-
60
- // trigger initial refresh
61
- onMount(() => {
62
- if (!contentElement) return;
63
-
64
- rows = Array.from(contentElement.getElementsByClassName('virtual-list-row')) as HTMLElement[];
65
-
66
- mounted = true;
67
-
68
- // Give the browser time to render and measure the viewport
69
- setTimeout(() => {
70
- if (viewportElement) {
71
- viewportHeight = viewportElement.offsetHeight;
72
- }
73
-
74
- refresh();
75
- }, 0);
76
- });
77
-
78
- // Watch for data changes and viewport size changes
79
- $effect(() => {
80
- if (mounted) refresh();
81
- });
82
-
83
- // Handle scroll to specific index
84
- $effect(() => {
85
- if (scrollToIndex !== undefined && viewportElement && mounted) {
86
- scrollToItem(scrollToIndex);
87
- }
88
- });
89
-
90
- const handleScroll = throttle(async () => {
91
- if (!viewportElement) return;
92
-
93
- const { scrollTop } = viewportElement;
94
-
95
- // Call user's onScroll callback
96
- onScroll?.(scrollTop);
97
-
98
- await refresh();
99
- }, 1000 / 60);
100
-
101
- // Improved height estimation using exponential moving average
102
- function updateAverageHeight(newHeight: number) {
103
- if (heightSampleCount < 1) {
104
- averageHeight = newHeight;
105
- heightSampleCount = 1;
106
- } else {
107
- // Exponential moving average with alpha = 0.1 for stability
108
- const alpha = Math.min(0.1, 1 / heightSampleCount);
109
- averageHeight = alpha * newHeight + (1 - alpha) * averageHeight;
110
- heightSampleCount++;
111
- }
112
- }
113
-
114
- function getItemHeight(index: number): number {
115
- return heightMap[index] || itemHeight || averageHeight;
116
- }
117
-
118
- async function refresh() {
119
- if (!viewportElement || !mounted) return;
120
-
121
- const { scrollTop } = viewportElement;
122
-
123
- // Calculate visible range with overscan buffer
124
- const startIndex = Math.max(0, findStartIndex(scrollTop) - overscan);
125
- const endIndex = Math.min(data.length, findEndIndex(scrollTop, startIndex) + overscan);
126
-
127
- start = startIndex;
128
- end = endIndex;
129
-
130
- await tick(); // Wait for DOM update
131
-
132
- // Update height measurements for visible items
133
- updateHeights();
134
- updatePadding();
135
- }
136
-
137
- function findStartIndex(scrollTop: number): number {
138
- let index = 0;
139
- let accumulatedHeight = 0;
140
-
141
- while (index < data.length && accumulatedHeight + getItemHeight(index) <= scrollTop) {
142
- accumulatedHeight += getItemHeight(index);
143
- index++;
144
- }
145
-
146
- return index;
147
- }
148
-
149
- function findEndIndex(scrollTop: number, startIndex: number): number {
150
- let index = startIndex;
151
- let accumulatedHeight = 0;
152
-
153
- // Start from the scroll position
154
- for (let i = 0; i < startIndex; i++) {
155
- accumulatedHeight += getItemHeight(i);
156
- }
157
-
158
- // Use a minimum viewport height to ensure we render enough items initially
159
- const effectiveViewportHeight = Math.max(viewportHeight, 800);
160
-
161
- while (index < data.length && accumulatedHeight <= scrollTop + effectiveViewportHeight) {
162
- accumulatedHeight += getItemHeight(index);
163
- index++;
164
- }
165
-
166
- return index;
167
- }
168
-
169
- function updateHeights() {
170
- if (!contentElement) return;
171
-
172
- rows = Array.from(contentElement.getElementsByClassName('virtual-list-row')) as HTMLElement[];
173
-
174
- // Update heights for currently rendered items
175
- rows.forEach((row, i) => {
176
- const actualIndex = start + i;
177
- const measuredHeight = itemHeight || row.offsetHeight;
178
-
179
- if (heightMap[actualIndex] !== measuredHeight) {
180
- heightMap[actualIndex] = measuredHeight;
181
- if (!itemHeight) {
182
- updateAverageHeight(measuredHeight);
183
- }
184
- }
185
- });
186
- }
187
-
188
- function updatePadding() {
189
- // Calculate top padding (sum of heights above visible area)
190
- let topPadding = 0;
191
- for (let i = 0; i < start; i++) {
192
- topPadding += getItemHeight(i);
193
- }
194
-
195
- // Calculate bottom padding (estimated heights below visible area)
196
- let bottomPadding = 0;
197
- for (let i = end; i < data.length; i++) {
198
- bottomPadding += getItemHeight(i);
199
- }
200
-
201
- top = topPadding;
202
- bottom = bottomPadding;
203
- }
204
-
205
- async function scrollToItem(index: number) {
206
- if (!viewportElement || index < 0 || index >= data.length) return;
207
-
208
- let targetScrollTop = 0;
209
- for (let i = 0; i < index; i++) {
210
- targetScrollTop += getItemHeight(i);
211
- }
212
-
213
- viewportElement.scrollTo({
214
- top: targetScrollTop,
215
- behavior: 'smooth'
216
- });
217
- }
218
- </script>
219
-
220
- <div class="absolute inset-0 block size-full max-h-full">
221
- <div
222
- bind:this={viewportElement}
223
- bind:offsetHeight={viewportHeight}
224
- class={twMerge(
225
- 'virtual-list-viewport virtual-list-viewport relative block h-full max-h-full w-full flex-1 overflow-y-auto',
226
- klass
227
- )}
228
- onscroll={handleScroll}
229
- >
230
- <table class={twMerge('virtual-list-contents w-full')} {...restProps}>
231
- {@render header?.()}
232
-
233
- <tbody bind:this={contentElement}>
234
- <!-- Top spacer row -->
235
- {#if top > 0}
236
- <tr style="height: {top}px;">
237
- <td colspan="100" style="padding: 0; border: none;"></td>
238
- </tr>
239
- {/if}
240
-
241
- {@render children?.({ items: visibleItems })}
242
-
243
- <!-- Bottom spacer row -->
244
- {#if bottom > 0}
245
- <tr style="height: {bottom}px;">
246
- <td colspan="100" style="padding: 0; border: none;"></td>
247
- </tr>
248
- {/if}
249
- </tbody>
250
- </table>
251
- </div>
252
- </div>
253
-
254
- <style>
255
- .virtual-list-viewport {
256
- -webkit-overflow-scrolling: touch;
257
- }
258
- </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,21 +1,4 @@
1
- export type VirtualListViewportProps<T> = {
2
- class?: string;
3
- data: T[];
4
- itemHeight?: number;
5
- overscan?: number;
6
- onScroll?: (scrollTop: number) => void;
7
- scrollToIndex?: number;
8
- header?: Snippet;
9
- children: Snippet<[
10
- {
11
- items: {
12
- index: number;
13
- data: T;
14
- }[];
15
- }
16
- ]>;
17
- };
18
- import { type Snippet } from 'svelte';
1
+ import type { VirtualListViewportProps } from './types';
19
2
  declare function $$render<T>(): {
20
3
  props: VirtualListViewportProps<T>;
21
4
  exports: {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-atoms/core",
3
- "version": "1.0.0-alpha.26",
3
+ "version": "1.0.0-alpha.27",
4
4
  "description": "A modular, accessible, and extensible Svelte UI component library.",
5
5
  "repository": {
6
6
  "type": "git",