@svgrid/grid 1.0.2 → 1.1.1

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 (216) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +137 -39
  3. package/dist/GridFooter.svelte +164 -0
  4. package/dist/GridFooter.svelte.d.ts +29 -0
  5. package/dist/GridMenus.svelte +648 -0
  6. package/dist/GridMenus.svelte.d.ts +31 -0
  7. package/dist/SvGrid.controller.svelte.d.ts +472 -0
  8. package/dist/SvGrid.controller.svelte.js +1917 -0
  9. package/dist/SvGrid.css +2012 -0
  10. package/dist/SvGrid.helpers.d.ts +35 -0
  11. package/dist/SvGrid.helpers.js +160 -0
  12. package/dist/SvGrid.helpers.test.d.ts +1 -0
  13. package/dist/SvGrid.helpers.test.js +298 -0
  14. package/dist/SvGrid.svelte +2346 -8742
  15. package/dist/SvGrid.svelte.d.ts +4 -357
  16. package/dist/SvGrid.types.d.ts +523 -0
  17. package/dist/SvGrid.types.js +1 -0
  18. package/dist/SvGridChart.svelte +1060 -23
  19. package/dist/SvGridChart.svelte.d.ts +17 -0
  20. package/dist/aligned-grids.d.ts +6 -0
  21. package/dist/aligned-grids.js +84 -0
  22. package/dist/aligned-grids.test.d.ts +1 -0
  23. package/dist/aligned-grids.test.js +75 -0
  24. package/dist/build-api.coverage.test.d.ts +20 -0
  25. package/dist/build-api.coverage.test.js +505 -0
  26. package/dist/build-api.d.ts +5 -0
  27. package/dist/build-api.js +557 -0
  28. package/dist/cell-render.d.ts +15 -0
  29. package/dist/cell-render.js +246 -0
  30. package/dist/cell-render.test.d.ts +1 -0
  31. package/dist/cell-render.test.js +338 -0
  32. package/dist/cell-values.d.ts +28 -0
  33. package/dist/cell-values.js +89 -0
  34. package/dist/chart-export.test.d.ts +1 -0
  35. package/dist/chart-export.test.js +302 -0
  36. package/dist/chart.coverage.test.d.ts +1 -0
  37. package/dist/chart.coverage.test.js +748 -0
  38. package/dist/chart.d.ts +370 -3
  39. package/dist/chart.js +1135 -42
  40. package/dist/clipboard.d.ts +15 -0
  41. package/dist/clipboard.js +420 -0
  42. package/dist/clipboard.test.d.ts +1 -0
  43. package/dist/clipboard.test.js +700 -0
  44. package/dist/collaboration.coverage.test.d.ts +1 -0
  45. package/dist/collaboration.coverage.test.js +200 -0
  46. package/dist/column-groups.d.ts +19 -0
  47. package/dist/column-groups.js +62 -0
  48. package/dist/column-groups.test.d.ts +1 -0
  49. package/dist/column-groups.test.js +56 -0
  50. package/dist/column-types.d.ts +10 -0
  51. package/dist/column-types.js +63 -0
  52. package/dist/column-types.test.d.ts +1 -0
  53. package/dist/column-types.test.js +62 -0
  54. package/dist/columns.d.ts +30 -0
  55. package/dist/columns.js +277 -0
  56. package/dist/columns.test.d.ts +1 -0
  57. package/dist/columns.test.js +625 -0
  58. package/dist/core.d.ts +86 -1
  59. package/dist/css.d.ts +3 -0
  60. package/dist/editing.d.ts +31 -0
  61. package/dist/editing.js +529 -0
  62. package/dist/editing.test.d.ts +1 -0
  63. package/dist/editing.test.js +732 -0
  64. package/dist/editors/cell-editors.coverage.test.d.ts +1 -0
  65. package/dist/editors/cell-editors.coverage.test.js +139 -0
  66. package/dist/editors/cell-editors.d.ts +1 -1
  67. package/dist/facet-buckets.d.ts +13 -0
  68. package/dist/facet-buckets.js +54 -0
  69. package/dist/facet-buckets.test.d.ts +1 -0
  70. package/dist/facet-buckets.test.js +296 -0
  71. package/dist/features.d.ts +5 -0
  72. package/dist/features.js +30 -0
  73. package/dist/filter-operators.d.ts +16 -0
  74. package/dist/filter-operators.js +69 -0
  75. package/dist/filter-operators.test.d.ts +1 -0
  76. package/dist/filter-operators.test.js +135 -0
  77. package/dist/hyperformula-adapter.d.ts +82 -0
  78. package/dist/hyperformula-adapter.js +73 -0
  79. package/dist/hyperformula-adapter.test.d.ts +1 -0
  80. package/dist/hyperformula-adapter.test.js +205 -0
  81. package/dist/index.d.ts +9 -3
  82. package/dist/index.js +8 -2
  83. package/dist/keyboard-handlers.coverage.test.d.ts +1 -0
  84. package/dist/keyboard-handlers.coverage.test.js +495 -0
  85. package/dist/keyboard-handlers.d.ts +7 -0
  86. package/dist/keyboard-handlers.js +202 -0
  87. package/dist/menus.d.ts +40 -0
  88. package/dist/menus.js +390 -0
  89. package/dist/menus.test.d.ts +1 -0
  90. package/dist/menus.test.js +560 -0
  91. package/dist/named-views.coverage.test.d.ts +1 -0
  92. package/dist/named-views.coverage.test.js +180 -0
  93. package/dist/named-views.d.ts +27 -0
  94. package/dist/named-views.js +39 -0
  95. package/dist/row-drag.d.ts +49 -0
  96. package/dist/row-drag.js +221 -0
  97. package/dist/row-drag.test.d.ts +1 -0
  98. package/dist/row-drag.test.js +142 -0
  99. package/dist/row-resize.d.ts +43 -0
  100. package/dist/row-resize.js +158 -0
  101. package/dist/row-resize.test.d.ts +1 -0
  102. package/dist/row-resize.test.js +329 -0
  103. package/dist/scroll-sync.d.ts +9 -0
  104. package/dist/scroll-sync.js +89 -0
  105. package/dist/scroll-sync.test.d.ts +1 -0
  106. package/dist/scroll-sync.test.js +290 -0
  107. package/dist/selection.d.ts +32 -0
  108. package/dist/selection.js +427 -0
  109. package/dist/selection.multi-range.test.d.ts +1 -0
  110. package/dist/selection.multi-range.test.js +55 -0
  111. package/dist/selection.test.d.ts +1 -0
  112. package/dist/selection.test.js +647 -0
  113. package/dist/server-data-source.coverage.test.d.ts +1 -0
  114. package/dist/server-data-source.coverage.test.js +154 -0
  115. package/dist/spreadsheet.d.ts +110 -0
  116. package/dist/spreadsheet.js +242 -0
  117. package/dist/spreadsheet.test.d.ts +1 -0
  118. package/dist/spreadsheet.test.js +446 -0
  119. package/dist/summaries.d.ts +12 -0
  120. package/dist/summaries.js +65 -0
  121. package/dist/sv-grid-scrollbar.js +13 -1
  122. package/dist/svgrid-wrapper.types.d.ts +31 -1
  123. package/dist/svgrid.behavior.test.js +20 -0
  124. package/dist/svgrid.comments-autocomplete.test.d.ts +1 -0
  125. package/dist/svgrid.comments-autocomplete.test.js +96 -0
  126. package/dist/svgrid.context-menu.test.d.ts +1 -0
  127. package/dist/svgrid.context-menu.test.js +102 -0
  128. package/dist/svgrid.interaction.test.js +31 -0
  129. package/dist/svgrid.new-features.wrapper.test.js +64 -6
  130. package/dist/svgrid.wrapper.test.js +27 -1
  131. package/dist/test-setup.js +9 -3
  132. package/dist/virtualization/column-virtualizer.d.ts +2 -0
  133. package/dist/virtualization/scroll-scaling.d.ts +45 -0
  134. package/dist/virtualization/scroll-scaling.js +99 -0
  135. package/dist/virtualization/scroll-scaling.test.d.ts +1 -0
  136. package/dist/virtualization/scroll-scaling.test.js +127 -0
  137. package/dist/virtualization/svelte-virtualizer.svelte.d.ts +2 -0
  138. package/dist/virtualization/svelte-virtualizer.svelte.js +2 -0
  139. package/dist/virtualization/virtualizer.d.ts +7 -0
  140. package/dist/virtualization/virtualizer.js +30 -0
  141. package/package.json +2 -1
  142. package/src/GridFooter.svelte +164 -0
  143. package/src/GridMenus.svelte +648 -0
  144. package/src/SvGrid.controller.svelte.ts +2352 -0
  145. package/src/SvGrid.css +2012 -0
  146. package/src/SvGrid.helpers.test.ts +415 -0
  147. package/src/SvGrid.helpers.ts +185 -0
  148. package/src/SvGrid.svelte +2346 -8742
  149. package/src/SvGrid.types.ts +537 -0
  150. package/src/SvGridChart.svelte +1060 -23
  151. package/src/aligned-grids.test.ts +80 -0
  152. package/src/aligned-grids.ts +87 -0
  153. package/src/build-api.coverage.test.ts +532 -0
  154. package/src/build-api.ts +683 -0
  155. package/src/cell-render.test.ts +451 -0
  156. package/src/cell-render.ts +426 -0
  157. package/src/cell-values.ts +114 -0
  158. package/src/chart-export.test.ts +370 -0
  159. package/src/chart.coverage.test.ts +814 -0
  160. package/src/chart.ts +1352 -47
  161. package/src/clipboard.test.ts +780 -0
  162. package/src/clipboard.ts +552 -0
  163. package/src/collaboration.coverage.test.ts +220 -0
  164. package/src/column-groups.test.ts +59 -0
  165. package/src/column-groups.ts +80 -0
  166. package/src/column-types.test.ts +68 -0
  167. package/src/column-types.ts +82 -0
  168. package/src/columns.test.ts +702 -0
  169. package/src/columns.ts +419 -0
  170. package/src/core.ts +86 -0
  171. package/src/css.d.ts +3 -0
  172. package/src/editing.test.ts +837 -0
  173. package/src/editing.ts +669 -0
  174. package/src/editors/cell-editors.coverage.test.ts +156 -0
  175. package/src/editors/cell-editors.ts +1 -0
  176. package/src/facet-buckets.test.ts +353 -0
  177. package/src/facet-buckets.ts +67 -0
  178. package/src/features.ts +128 -0
  179. package/src/filter-operators.test.ts +174 -0
  180. package/src/filter-operators.ts +87 -0
  181. package/src/hyperformula-adapter.test.ts +256 -0
  182. package/src/hyperformula-adapter.ts +124 -0
  183. package/src/index.ts +49 -0
  184. package/src/keyboard-handlers.coverage.test.ts +560 -0
  185. package/src/keyboard-handlers.ts +353 -0
  186. package/src/keyboard.ts +97 -97
  187. package/src/menus.test.ts +620 -0
  188. package/src/menus.ts +555 -0
  189. package/src/named-views.coverage.test.ts +210 -0
  190. package/src/named-views.ts +48 -0
  191. package/src/row-drag.test.ts +168 -0
  192. package/src/row-drag.ts +255 -0
  193. package/src/row-resize.test.ts +369 -0
  194. package/src/row-resize.ts +171 -0
  195. package/src/scroll-sync.test.ts +330 -0
  196. package/src/scroll-sync.ts +218 -0
  197. package/src/selection.multi-range.test.ts +61 -0
  198. package/src/selection.test.ts +722 -0
  199. package/src/selection.ts +579 -0
  200. package/src/server-data-source.coverage.test.ts +180 -0
  201. package/src/spreadsheet.test.ts +489 -0
  202. package/src/spreadsheet.ts +304 -0
  203. package/src/summaries.ts +204 -0
  204. package/src/sv-grid-scrollbar.ts +13 -1
  205. package/src/svgrid-wrapper.types.ts +31 -1
  206. package/src/svgrid.behavior.test.ts +22 -0
  207. package/src/svgrid.comments-autocomplete.test.ts +112 -0
  208. package/src/svgrid.context-menu.test.ts +126 -0
  209. package/src/svgrid.interaction.test.ts +30 -0
  210. package/src/svgrid.new-features.wrapper.test.ts +67 -6
  211. package/src/svgrid.wrapper.test.ts +27 -1
  212. package/src/test-setup.ts +9 -6
  213. package/src/virtualization/scroll-scaling.test.ts +148 -0
  214. package/src/virtualization/scroll-scaling.ts +121 -0
  215. package/src/virtualization/svelte-virtualizer.svelte.ts +2 -0
  216. package/src/virtualization/virtualizer.ts +26 -0
@@ -0,0 +1,127 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createRowScrollScaling, resolveMaxDomHeight } from './scroll-scaling';
3
+ const VIEWPORT = 600;
4
+ const FALLBACK = 8000000;
5
+ describe('createRowScrollScaling - inert (content fits under the cap)', () => {
6
+ // 1M rows * 18px = 18M, under a 33.5M (Chrome) cap -> no scaling.
7
+ const s = createRowScrollScaling(18000000, 33554400, VIEWPORT);
8
+ it('is inactive and exposes the true height as the DOM height', () => {
9
+ expect(s.active).toBe(false);
10
+ expect(s.domTotal).toBe(18000000);
11
+ });
12
+ it('both maps are the identity', () => {
13
+ for (const v of [0, 1, 1234, 9000000, 17999400]) {
14
+ expect(s.domToLogical(v)).toBe(v);
15
+ expect(s.logicalToDom(v)).toBe(v);
16
+ }
17
+ });
18
+ });
19
+ describe('createRowScrollScaling - active (content exceeds the cap)', () => {
20
+ // 1M rows * 32px = 32M, over Firefox's ~17.9M cap -> scaling engages.
21
+ const trueTotal = 32000000;
22
+ const maxDom = 17895697;
23
+ const s = createRowScrollScaling(trueTotal, maxDom, VIEWPORT);
24
+ const domMax = s.domTotal - VIEWPORT;
25
+ const logicalMax = trueTotal - VIEWPORT;
26
+ it('caps the DOM height at the browser limit', () => {
27
+ expect(s.active).toBe(true);
28
+ expect(s.domTotal).toBe(maxDom);
29
+ expect(s.domTotal).toBeLessThanOrEqual(maxDom);
30
+ });
31
+ it('maps the endpoints exactly so the first and last rows are reachable', () => {
32
+ expect(s.domToLogical(0)).toBe(0);
33
+ // The bottom of the DOM scroll range reaches the bottom of the logical range.
34
+ expect(s.domToLogical(domMax)).toBeCloseTo(logicalMax, 3);
35
+ expect(s.logicalToDom(0)).toBe(0);
36
+ expect(s.logicalToDom(logicalMax)).toBeCloseTo(domMax, 3);
37
+ });
38
+ it('clamps out-of-range inputs instead of overshooting', () => {
39
+ expect(s.domToLogical(-100)).toBe(0);
40
+ expect(s.domToLogical(domMax + 10000)).toBeCloseTo(logicalMax, 3);
41
+ expect(s.logicalToDom(-100)).toBe(0);
42
+ expect(s.logicalToDom(logicalMax + 10000)).toBeCloseTo(domMax, 3);
43
+ });
44
+ it('logicalToDom is the inverse of domToLogical across the range', () => {
45
+ for (let i = 0; i <= 10; i += 1) {
46
+ const domTop = (domMax * i) / 10;
47
+ const roundTrip = s.logicalToDom(s.domToLogical(domTop));
48
+ expect(roundTrip).toBeCloseTo(domTop, 2);
49
+ }
50
+ });
51
+ it('both maps are monotonic non-decreasing', () => {
52
+ let prevL = -1;
53
+ let prevD = -1;
54
+ for (let i = 0; i <= 20; i += 1) {
55
+ const domTop = (domMax * i) / 20;
56
+ const logical = s.domToLogical(domTop);
57
+ expect(logical).toBeGreaterThanOrEqual(prevL);
58
+ prevL = logical;
59
+ const logIn = (logicalMax * i) / 20;
60
+ const dom = s.logicalToDom(logIn);
61
+ expect(dom).toBeGreaterThanOrEqual(prevD);
62
+ prevD = dom;
63
+ }
64
+ });
65
+ });
66
+ describe('resolveMaxDomHeight - picks the smaller, real scrollable cap', () => {
67
+ it('keeps the desktop cap when both signals agree', () => {
68
+ // Chrome ~33.5M; offsetHeight and scrollHeight match. 0.5% safety shave.
69
+ const cap = resolveMaxDomHeight(33554400, 33554400, FALLBACK);
70
+ expect(cap).toBeLessThan(33554400);
71
+ expect(cap).toBeGreaterThan(33000000);
72
+ });
73
+ it('uses the SCROLL cap when a phone over-reports offsetHeight', () => {
74
+ // High-DPR mobile: layout says 24M, but the container only scrolls ~6M.
75
+ // Trusting offsetHeight is exactly what stranded the last rows.
76
+ const cap = resolveMaxDomHeight(24000000, 6000000, FALLBACK);
77
+ expect(cap).toBeLessThanOrEqual(6000000);
78
+ expect(cap).toBeGreaterThan(5900000);
79
+ });
80
+ it('shaves a safety margin so the spacer never sits at the exact edge', () => {
81
+ const cap = resolveMaxDomHeight(10000000, 10000000, FALLBACK);
82
+ expect(cap).toBeLessThan(10000000);
83
+ });
84
+ it('falls back when both readings are junk (jsdom returns 0)', () => {
85
+ expect(resolveMaxDomHeight(0, 0, FALLBACK)).toBe(FALLBACK);
86
+ });
87
+ it('falls back when the browser did not clamp (returned ~1e9)', () => {
88
+ expect(resolveMaxDomHeight(1000000000, 1000000000, FALLBACK)).toBe(FALLBACK);
89
+ });
90
+ it('ignores a junk signal but trusts the valid one', () => {
91
+ // offsetHeight unclamped/garbage, scrollHeight is the real cap.
92
+ const cap = resolveMaxDomHeight(1000000000, 5000000, FALLBACK);
93
+ expect(cap).toBeLessThanOrEqual(5000000);
94
+ expect(cap).toBeGreaterThan(4900000);
95
+ });
96
+ it('feeds straight into the scaler so the last row stays reachable', () => {
97
+ const maxDom = resolveMaxDomHeight(24000000, 6000000, FALLBACK);
98
+ const trueTotal = 500000 * 32; // 16M px of rows, over the phone cap
99
+ const s = createRowScrollScaling(trueTotal, maxDom, VIEWPORT);
100
+ expect(s.active).toBe(true);
101
+ expect(s.domTotal).toBeLessThanOrEqual(maxDom);
102
+ const domMax = s.domTotal - VIEWPORT;
103
+ expect(s.domToLogical(domMax)).toBeCloseTo(trueTotal - VIEWPORT, 0);
104
+ });
105
+ });
106
+ describe('createRowScrollScaling - edge cases', () => {
107
+ it('handles an empty grid without dividing by zero', () => {
108
+ const s = createRowScrollScaling(0, 17895697, VIEWPORT);
109
+ expect(s.active).toBe(false);
110
+ expect(s.domTotal).toBe(0);
111
+ expect(s.domToLogical(0)).toBe(0);
112
+ expect(s.logicalToDom(0)).toBe(0);
113
+ });
114
+ it('handles content shorter than the viewport', () => {
115
+ const s = createRowScrollScaling(200, 17895697, VIEWPORT);
116
+ expect(s.active).toBe(false);
117
+ expect(s.domToLogical(0)).toBe(0);
118
+ });
119
+ it('extreme scale (100M rows) keeps the last row reachable', () => {
120
+ const trueTotal = 100000000 * 32; // 3.2 billion px
121
+ const s = createRowScrollScaling(trueTotal, 33554400, VIEWPORT);
122
+ expect(s.active).toBe(true);
123
+ expect(s.domTotal).toBe(33554400);
124
+ const domMax = s.domTotal - VIEWPORT;
125
+ expect(s.domToLogical(domMax)).toBeCloseTo(trueTotal - VIEWPORT, 0);
126
+ });
127
+ });
@@ -7,5 +7,7 @@ export declare function createSvelteVirtualizer(options: VirtualizerOptions): {
7
7
  scrollToIndex: (index: number) => void;
8
8
  getVirtualItems: () => import("./types").VirtualItem[];
9
9
  getTotalSize: () => number;
10
+ getOffsetForIndex: (index: number) => number;
11
+ getSizeForIndex: (index: number) => number;
10
12
  getState: () => import("./types").VirtualizerState;
11
13
  };
@@ -15,6 +15,8 @@ export function createSvelteVirtualizer(options) {
15
15
  scrollToIndex: virtualizer.scrollToIndex,
16
16
  getVirtualItems: virtualizer.getVirtualItems,
17
17
  getTotalSize: virtualizer.getTotalSize,
18
+ getOffsetForIndex: virtualizer.getOffsetForIndex,
19
+ getSizeForIndex: virtualizer.getSizeForIndex,
18
20
  getState: virtualizer.getState,
19
21
  };
20
22
  }
@@ -7,6 +7,13 @@ export declare function createVirtualizer(initial: VirtualizerOptions): {
7
7
  scrollToIndex(index: number): void;
8
8
  getVirtualItems(): VirtualItem[];
9
9
  getTotalSize(): number;
10
+ /** Cumulative offset of row `index` from the top in px, regardless
11
+ * of whether `estimateSize` is uniform or per-index. Uses the
12
+ * cached offsets array under function-form sizing so the lookup
13
+ * is O(1) instead of O(index). */
14
+ getOffsetForIndex(index: number): number;
15
+ /** Height of row `index` in px (whichever estimateSize provides). */
16
+ getSizeForIndex(index: number): number;
10
17
  getState(): VirtualizerState;
11
18
  subscribe(listener: VirtualizerListener): () => boolean;
12
19
  };
@@ -221,6 +221,36 @@ export function createVirtualizer(initial) {
221
221
  getTotalSize() {
222
222
  return state.totalSize;
223
223
  },
224
+ /** Cumulative offset of row `index` from the top in px, regardless
225
+ * of whether `estimateSize` is uniform or per-index. Uses the
226
+ * cached offsets array under function-form sizing so the lookup
227
+ * is O(1) instead of O(index). */
228
+ getOffsetForIndex(index) {
229
+ if (index <= 0)
230
+ return 0;
231
+ const count = Math.max(options.count, 0);
232
+ const bounded = Math.min(index, count);
233
+ if (typeof options.estimateSize === 'function') {
234
+ const offsets = getOffsets();
235
+ if (offsets)
236
+ return offsets[bounded] ?? 0;
237
+ let acc = 0;
238
+ const fn = options.estimateSize;
239
+ for (let i = 0; i < bounded; i += 1)
240
+ acc += Math.max(fn(i), 1);
241
+ return acc;
242
+ }
243
+ return bounded * Math.max(options.estimateSize, 1);
244
+ },
245
+ /** Height of row `index` in px (whichever estimateSize provides). */
246
+ getSizeForIndex(index) {
247
+ if (index < 0 || index >= options.count)
248
+ return 0;
249
+ if (typeof options.estimateSize === 'function') {
250
+ return Math.max(options.estimateSize(index), 1);
251
+ }
252
+ return Math.max(options.estimateSize, 1);
253
+ },
224
254
  getState() {
225
255
  return state;
226
256
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svgrid/grid",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "SvGrid - a headless-first, Svelte 5-native data grid engine and render component.",
5
5
  "author": "jQWidgets <boikom@jqwidgets.com>",
6
6
  "license": "MIT",
@@ -60,6 +60,7 @@
60
60
  "@vitest/coverage-v8": "^4.1.5",
61
61
  "eslint-plugin-svelte": "^3.17.1",
62
62
  "jsdom": "^29.1.1",
63
+ "publint": "^0.3.21",
63
64
  "svelte": "^5.55.5",
64
65
  "svelte-check": "^4.4.6"
65
66
  },
@@ -0,0 +1,164 @@
1
+ <script lang="ts" generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData">
2
+ import {
3
+ getGridCellA11yProps,
4
+ getGridCellDomId,
5
+ getGridHeaderA11yProps,
6
+ getGridRootA11yProps,
7
+ getGridRowA11yProps,
8
+ type EditorContext,
9
+ type CellEditorOption,
10
+ type Column,
11
+ type Row,
12
+ type RowData,
13
+ type TableFeatures,
14
+ } from "./index";
15
+ import "./sv-grid-scrollbar";
16
+ import type { Snippet } from "svelte";
17
+ import {
18
+ RenderSnippetConfig,
19
+ RenderComponentConfig,
20
+ } from "./render-component";
21
+ import {
22
+ buildSparkline,
23
+ toSparklineValues,
24
+ } from "./sparkline";
25
+ import SvGridDropdown from "./SvGridDropdown.svelte";
26
+ import type {
27
+ Props,
28
+ SelectionPoint,
29
+ SelectionRange,
30
+ CellEditState,
31
+ FilterOperator,
32
+ FilterOption,
33
+ MenuPosition,
34
+ } from "./SvGrid.types";
35
+ import {
36
+ cfTextStyle,
37
+ fmtStat,
38
+ getEditableInputValue,
39
+ getEditorInputType,
40
+ toValueArray,
41
+ getOptionLabel,
42
+ getOptionColor,
43
+ colorfulChipStyle,
44
+ getEditorClass,
45
+ } from "./SvGrid.helpers";
46
+ import {
47
+ } from "./SvGrid.controller.svelte";
48
+ import type { SvGridController } from "./SvGrid.controller.svelte";
49
+
50
+ let { ctrl }: { ctrl: SvGridController<TFeatures, TData> } = $props();
51
+
52
+ // View facade: re-bind the controller's reactive members so the markup
53
+ // (moved verbatim from SvGrid.svelte) stays identical.
54
+ const paginationEnabled = $derived(ctrl.paginationEnabled);
55
+ const grid = $derived(ctrl.grid);
56
+ const paginationState = $derived(ctrl.paginationState);
57
+ const allRowsBeforePagination = $derived(ctrl.allRowsBeforePagination);
58
+ const statusBarEnabled = $derived(ctrl.statusBarEnabled);
59
+ const statusBarAggregates = $derived(ctrl.statusBarAggregates);
60
+ const statusBarStats = $derived(ctrl.statusBarStats);
61
+ const changePage = $derived(ctrl.changePage);
62
+ const goToPage = $derived(ctrl.goToPage);
63
+ const setPageSize = $derived(ctrl.setPageSize);
64
+ </script>
65
+
66
+ {#if statusBarEnabled && statusBarStats}
67
+ {@const s = statusBarStats}
68
+ <div class="sv-grid-status-bar" role="status" aria-live="polite">
69
+ {#each statusBarAggregates as agg (agg)}
70
+ {#if agg === "count"}
71
+ <span class="sv-grid-status-item"
72
+ ><span class="sv-grid-status-label">Count</span>{fmtStat(s.count)}</span
73
+ >
74
+ {:else if agg === "numericCount"}
75
+ <span class="sv-grid-status-item"
76
+ ><span class="sv-grid-status-label">Numeric</span>{fmtStat(s.numericCount)}</span
77
+ >
78
+ {:else if s.numericCount > 0 && agg === "sum"}
79
+ <span class="sv-grid-status-item"
80
+ ><span class="sv-grid-status-label">Sum</span>{fmtStat(s.sum)}</span
81
+ >
82
+ {:else if s.numericCount > 0 && agg === "avg"}
83
+ <span class="sv-grid-status-item"
84
+ ><span class="sv-grid-status-label">Avg</span>{fmtStat(s.avg)}</span
85
+ >
86
+ {:else if s.numericCount > 0 && agg === "min"}
87
+ <span class="sv-grid-status-item"
88
+ ><span class="sv-grid-status-label">Min</span>{fmtStat(s.min)}</span
89
+ >
90
+ {:else if s.numericCount > 0 && agg === "max"}
91
+ <span class="sv-grid-status-item"
92
+ ><span class="sv-grid-status-label">Max</span>{fmtStat(s.max)}</span
93
+ >
94
+ {/if}
95
+ {/each}
96
+ </div>
97
+ {/if}
98
+
99
+ {#if paginationEnabled}
100
+ {@const totalRows = allRowsBeforePagination.length}
101
+ {@const pageSize = paginationState.pageSize}
102
+ {@const pageCount = Math.max(1, Math.ceil(totalRows / pageSize))}
103
+ {@const currentPage = Math.min(paginationState.pageIndex + 1, pageCount)}
104
+ {@const rangeStart =
105
+ totalRows === 0 ? 0 : (currentPage - 1) * pageSize + 1}
106
+ {@const rangeEnd = Math.min(totalRows, currentPage * pageSize)}
107
+ {@const onFirst = currentPage <= 1}
108
+ {@const onLast = currentPage >= pageCount}
109
+ <div class="sv-grid-pagination" role="navigation" aria-label="Pagination">
110
+ <label class="sv-grid-pagination-pagesize">
111
+ <span>Page Size:</span>
112
+ <select
113
+ onchange={(event) =>
114
+ setPageSize(
115
+ parseInt((event.currentTarget as HTMLSelectElement).value, 10),
116
+ )}
117
+ >
118
+ <option value="10" selected={pageSize === 10}>10</option>
119
+ <option value="25" selected={pageSize === 25}>25</option>
120
+ <option value="50" selected={pageSize === 50}>50</option>
121
+ <option value="100" selected={pageSize === 100}>100</option>
122
+ </select>
123
+ </label>
124
+ <span class="sv-grid-pagination-range">
125
+ <strong>{rangeStart.toLocaleString()}</strong> to
126
+ <strong>{rangeEnd.toLocaleString()}</strong> of
127
+ <strong>{totalRows.toLocaleString()}</strong>
128
+ </span>
129
+ <div class="sv-grid-pagination-nav">
130
+ <button
131
+ type="button"
132
+ class="sv-grid-pagination-btn"
133
+ disabled={onFirst}
134
+ onclick={() => goToPage(0)}
135
+ aria-label="First page">⇤</button
136
+ >
137
+ <button
138
+ type="button"
139
+ class="sv-grid-pagination-btn"
140
+ disabled={onFirst}
141
+ onclick={() => changePage(-1)}
142
+ aria-label="Previous page">‹</button
143
+ >
144
+ <span class="sv-grid-pagination-label">
145
+ Page <strong>{currentPage.toLocaleString()}</strong> of
146
+ <strong>{pageCount.toLocaleString()}</strong>
147
+ </span>
148
+ <button
149
+ type="button"
150
+ class="sv-grid-pagination-btn"
151
+ disabled={onLast}
152
+ onclick={() => changePage(1)}
153
+ aria-label="Next page">›</button
154
+ >
155
+ <button
156
+ type="button"
157
+ class="sv-grid-pagination-btn"
158
+ disabled={onLast}
159
+ onclick={() => goToPage(pageCount - 1)}
160
+ aria-label="Last page">⇥</button
161
+ >
162
+ </div>
163
+ </div>
164
+ {/if}