@svgrid/grid 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +137 -39
- package/dist/GridFooter.svelte +164 -0
- package/dist/GridFooter.svelte.d.ts +29 -0
- package/dist/GridMenus.svelte +570 -0
- package/dist/GridMenus.svelte.d.ts +31 -0
- package/dist/SvGrid.controller.svelte.d.ts +429 -0
- package/dist/SvGrid.controller.svelte.js +1732 -0
- package/dist/SvGrid.css +1709 -0
- package/dist/SvGrid.helpers.d.ts +35 -0
- package/dist/SvGrid.helpers.js +160 -0
- package/dist/SvGrid.svelte +344 -7043
- package/dist/SvGrid.svelte.d.ts +4 -357
- package/dist/SvGrid.types.d.ts +436 -0
- package/dist/SvGrid.types.js +1 -0
- package/dist/SvGridChart.svelte +1060 -23
- package/dist/SvGridChart.svelte.d.ts +17 -0
- package/dist/build-api.d.ts +5 -0
- package/dist/build-api.js +527 -0
- package/dist/cell-render.d.ts +15 -0
- package/dist/cell-render.js +246 -0
- package/dist/cell-values.d.ts +28 -0
- package/dist/cell-values.js +89 -0
- package/dist/chart.d.ts +370 -3
- package/dist/chart.js +1135 -42
- package/dist/clipboard.d.ts +15 -0
- package/dist/clipboard.js +356 -0
- package/dist/columns.d.ts +30 -0
- package/dist/columns.js +277 -0
- package/dist/core.d.ts +1 -1
- package/dist/css.d.ts +3 -0
- package/dist/editing.d.ts +24 -0
- package/dist/editing.js +343 -0
- package/dist/editors/cell-editors.d.ts +1 -1
- package/dist/facet-buckets.d.ts +13 -0
- package/dist/facet-buckets.js +54 -0
- package/dist/features.d.ts +5 -0
- package/dist/features.js +30 -0
- package/dist/filter-operators.d.ts +16 -0
- package/dist/filter-operators.js +69 -0
- package/dist/hyperformula-adapter.d.ts +82 -0
- package/dist/hyperformula-adapter.js +73 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +5 -2
- package/dist/keyboard-handlers.d.ts +7 -0
- package/dist/keyboard-handlers.js +197 -0
- package/dist/menus.d.ts +40 -0
- package/dist/menus.js +389 -0
- package/dist/named-views.d.ts +27 -0
- package/dist/named-views.js +39 -0
- package/dist/row-resize.d.ts +43 -0
- package/dist/row-resize.js +158 -0
- package/dist/scroll-sync.d.ts +9 -0
- package/dist/scroll-sync.js +86 -0
- package/dist/selection.d.ts +26 -0
- package/dist/selection.js +387 -0
- package/dist/spreadsheet.d.ts +80 -0
- package/dist/spreadsheet.js +194 -0
- package/dist/summaries.d.ts +12 -0
- package/dist/summaries.js +65 -0
- package/dist/svgrid-wrapper.types.d.ts +12 -1
- package/dist/svgrid.comments-autocomplete.test.d.ts +1 -0
- package/dist/svgrid.comments-autocomplete.test.js +96 -0
- package/dist/svgrid.context-menu.test.d.ts +1 -0
- package/dist/svgrid.context-menu.test.js +102 -0
- package/dist/svgrid.new-features.wrapper.test.js +30 -4
- package/dist/svgrid.wrapper.test.js +27 -1
- package/dist/virtualization/column-virtualizer.d.ts +2 -0
- package/dist/virtualization/scroll-scaling.d.ts +28 -0
- package/dist/virtualization/scroll-scaling.js +64 -0
- package/dist/virtualization/scroll-scaling.test.d.ts +1 -0
- package/dist/virtualization/scroll-scaling.test.js +86 -0
- package/dist/virtualization/svelte-virtualizer.svelte.d.ts +2 -0
- package/dist/virtualization/svelte-virtualizer.svelte.js +2 -0
- package/dist/virtualization/virtualizer.d.ts +7 -0
- package/dist/virtualization/virtualizer.js +30 -0
- package/package.json +1 -1
- package/src/GridFooter.svelte +164 -0
- package/src/GridMenus.svelte +570 -0
- package/src/SvGrid.controller.svelte.ts +2195 -0
- package/src/SvGrid.css +1747 -0
- package/src/SvGrid.helpers.test.ts +415 -0
- package/src/SvGrid.helpers.ts +185 -0
- package/src/SvGrid.svelte +348 -7043
- package/src/SvGrid.types.ts +456 -0
- package/src/SvGridChart.svelte +1060 -23
- package/src/build-api.coverage.test.ts +532 -0
- package/src/build-api.ts +663 -0
- package/src/cell-render.test.ts +451 -0
- package/src/cell-render.ts +426 -0
- package/src/cell-values.ts +114 -0
- package/src/chart-export.test.ts +370 -0
- package/src/chart.coverage.test.ts +814 -0
- package/src/chart.ts +1352 -47
- package/src/clipboard.test.ts +731 -0
- package/src/clipboard.ts +524 -0
- package/src/collaboration.coverage.test.ts +220 -0
- package/src/columns.test.ts +702 -0
- package/src/columns.ts +419 -0
- package/src/core.ts +8 -0
- package/src/css.d.ts +3 -0
- package/src/editing.test.ts +837 -0
- package/src/editing.ts +513 -0
- package/src/editors/cell-editors.coverage.test.ts +156 -0
- package/src/editors/cell-editors.ts +1 -0
- package/src/facet-buckets.test.ts +353 -0
- package/src/facet-buckets.ts +67 -0
- package/src/features.ts +128 -0
- package/src/filter-operators.test.ts +174 -0
- package/src/filter-operators.ts +87 -0
- package/src/hyperformula-adapter.test.ts +256 -0
- package/src/hyperformula-adapter.ts +124 -0
- package/src/index.ts +37 -0
- package/src/keyboard-handlers.coverage.test.ts +560 -0
- package/src/keyboard-handlers.ts +353 -0
- package/src/keyboard.ts +97 -97
- package/src/menus.test.ts +620 -0
- package/src/menus.ts +554 -0
- package/src/named-views.coverage.test.ts +210 -0
- package/src/named-views.ts +48 -0
- package/src/row-resize.test.ts +369 -0
- package/src/row-resize.ts +171 -0
- package/src/scroll-sync.test.ts +330 -0
- package/src/scroll-sync.ts +216 -0
- package/src/selection.test.ts +722 -0
- package/src/selection.ts +545 -0
- package/src/server-data-source.coverage.test.ts +180 -0
- package/src/spreadsheet.test.ts +445 -0
- package/src/spreadsheet.ts +246 -0
- package/src/summaries.ts +204 -0
- package/src/sv-grid-scrollbar.ts +13 -1
- package/src/svgrid-wrapper.types.ts +12 -1
- package/src/svgrid.behavior.test.ts +22 -0
- package/src/svgrid.comments-autocomplete.test.ts +112 -0
- package/src/svgrid.context-menu.test.ts +126 -0
- package/src/svgrid.interaction.test.ts +30 -0
- package/src/svgrid.new-features.wrapper.test.ts +65 -4
- package/src/svgrid.wrapper.test.ts +27 -1
- package/src/test-setup.ts +9 -6
- package/src/virtualization/scroll-scaling.test.ts +148 -0
- package/src/virtualization/scroll-scaling.ts +121 -0
- package/src/virtualization/svelte-virtualizer.svelte.ts +2 -0
- package/src/virtualization/virtualizer.ts +26 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Pure math for "huge list" vertical scroll scaling.
|
|
2
|
+
//
|
|
3
|
+
// Browsers cap how tall a single element may be (Chrome/Safari ~33.5M px,
|
|
4
|
+
// Firefox ~17.9M, mobile/high-DPR lower). Once a virtualized grid's true
|
|
5
|
+
// content height (count * rowHeight) exceeds that cap the scroll container
|
|
6
|
+
// silently clamps its scrollHeight and the tail rows become unreachable - a
|
|
7
|
+
// 1,000,000-row grid that only scrolls to ~994,000 on a phone.
|
|
8
|
+
//
|
|
9
|
+
// The fix (the technique react-virtualized calls "scaling"): size the DOM
|
|
10
|
+
// scroll spacer to a capped height the browser CAN render, and map between the
|
|
11
|
+
// limited DOM scroll range and the full logical range the virtualizer works
|
|
12
|
+
// in. This module is the pure, side-effect-free core of that mapping so the
|
|
13
|
+
// invariants can be unit-tested independently of the Svelte controller.
|
|
14
|
+
//
|
|
15
|
+
// When the true height fits under the cap, scaling is INERT: every mapping is
|
|
16
|
+
// the identity and callers behave exactly as before.
|
|
17
|
+
function clamp01(value) {
|
|
18
|
+
if (value <= 0)
|
|
19
|
+
return 0;
|
|
20
|
+
if (value >= 1)
|
|
21
|
+
return 1;
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build the scaling mapping for one axis.
|
|
26
|
+
*
|
|
27
|
+
* @param trueTotal The real content height in logical px (count * rowH, or
|
|
28
|
+
* the cumulative offset total for variable rows).
|
|
29
|
+
* @param maxDomHeight The browser's max element height (detected at runtime).
|
|
30
|
+
* @param viewport The scroll viewport height in px.
|
|
31
|
+
*
|
|
32
|
+
* Invariants (verified in scroll-scaling.test.ts):
|
|
33
|
+
* - Inert when `trueTotal <= maxDomHeight`: both maps are the identity.
|
|
34
|
+
* - `domTotal <= maxDomHeight` always, so the spacer never exceeds the cap.
|
|
35
|
+
* - Endpoints line up: domTop 0 -> logical 0, and the DOM max
|
|
36
|
+
* (`domTotal - viewport`) -> the logical max (`trueTotal - viewport`), so
|
|
37
|
+
* the very last row is always reachable.
|
|
38
|
+
* - `logicalToDom` is the inverse of `domToLogical` across the range.
|
|
39
|
+
* - Both maps are monotonic non-decreasing and clamp out-of-range inputs.
|
|
40
|
+
*/
|
|
41
|
+
export function createRowScrollScaling(trueTotal, maxDomHeight, viewport) {
|
|
42
|
+
const safeMax = Math.max(maxDomHeight, 1);
|
|
43
|
+
const domTotal = Math.min(Math.max(trueTotal, 0), safeMax);
|
|
44
|
+
const active = trueTotal > safeMax;
|
|
45
|
+
// Scrollable ranges (content minus one viewport). Guard against zero so the
|
|
46
|
+
// ratios below never divide by zero on tiny / empty grids.
|
|
47
|
+
const domRange = Math.max(domTotal - viewport, 1);
|
|
48
|
+
const logicalRange = Math.max(trueTotal - viewport, 0);
|
|
49
|
+
return {
|
|
50
|
+
active,
|
|
51
|
+
domTotal,
|
|
52
|
+
domToLogical(domTop) {
|
|
53
|
+
if (!active)
|
|
54
|
+
return domTop;
|
|
55
|
+
return clamp01(domTop / domRange) * logicalRange;
|
|
56
|
+
},
|
|
57
|
+
logicalToDom(logical) {
|
|
58
|
+
if (!active)
|
|
59
|
+
return logical;
|
|
60
|
+
const lr = Math.max(logicalRange, 1);
|
|
61
|
+
return clamp01(logical / lr) * Math.max(domTotal - viewport, 0);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { createRowScrollScaling } from './scroll-scaling';
|
|
3
|
+
const VIEWPORT = 600;
|
|
4
|
+
describe('createRowScrollScaling - inert (content fits under the cap)', () => {
|
|
5
|
+
// 1M rows * 18px = 18M, under a 33.5M (Chrome) cap -> no scaling.
|
|
6
|
+
const s = createRowScrollScaling(18000000, 33554400, VIEWPORT);
|
|
7
|
+
it('is inactive and exposes the true height as the DOM height', () => {
|
|
8
|
+
expect(s.active).toBe(false);
|
|
9
|
+
expect(s.domTotal).toBe(18000000);
|
|
10
|
+
});
|
|
11
|
+
it('both maps are the identity', () => {
|
|
12
|
+
for (const v of [0, 1, 1234, 9000000, 17999400]) {
|
|
13
|
+
expect(s.domToLogical(v)).toBe(v);
|
|
14
|
+
expect(s.logicalToDom(v)).toBe(v);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
describe('createRowScrollScaling - active (content exceeds the cap)', () => {
|
|
19
|
+
// 1M rows * 32px = 32M, over Firefox's ~17.9M cap -> scaling engages.
|
|
20
|
+
const trueTotal = 32000000;
|
|
21
|
+
const maxDom = 17895697;
|
|
22
|
+
const s = createRowScrollScaling(trueTotal, maxDom, VIEWPORT);
|
|
23
|
+
const domMax = s.domTotal - VIEWPORT;
|
|
24
|
+
const logicalMax = trueTotal - VIEWPORT;
|
|
25
|
+
it('caps the DOM height at the browser limit', () => {
|
|
26
|
+
expect(s.active).toBe(true);
|
|
27
|
+
expect(s.domTotal).toBe(maxDom);
|
|
28
|
+
expect(s.domTotal).toBeLessThanOrEqual(maxDom);
|
|
29
|
+
});
|
|
30
|
+
it('maps the endpoints exactly so the first and last rows are reachable', () => {
|
|
31
|
+
expect(s.domToLogical(0)).toBe(0);
|
|
32
|
+
// The bottom of the DOM scroll range reaches the bottom of the logical range.
|
|
33
|
+
expect(s.domToLogical(domMax)).toBeCloseTo(logicalMax, 3);
|
|
34
|
+
expect(s.logicalToDom(0)).toBe(0);
|
|
35
|
+
expect(s.logicalToDom(logicalMax)).toBeCloseTo(domMax, 3);
|
|
36
|
+
});
|
|
37
|
+
it('clamps out-of-range inputs instead of overshooting', () => {
|
|
38
|
+
expect(s.domToLogical(-100)).toBe(0);
|
|
39
|
+
expect(s.domToLogical(domMax + 10000)).toBeCloseTo(logicalMax, 3);
|
|
40
|
+
expect(s.logicalToDom(-100)).toBe(0);
|
|
41
|
+
expect(s.logicalToDom(logicalMax + 10000)).toBeCloseTo(domMax, 3);
|
|
42
|
+
});
|
|
43
|
+
it('logicalToDom is the inverse of domToLogical across the range', () => {
|
|
44
|
+
for (let i = 0; i <= 10; i += 1) {
|
|
45
|
+
const domTop = (domMax * i) / 10;
|
|
46
|
+
const roundTrip = s.logicalToDom(s.domToLogical(domTop));
|
|
47
|
+
expect(roundTrip).toBeCloseTo(domTop, 2);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
it('both maps are monotonic non-decreasing', () => {
|
|
51
|
+
let prevL = -1;
|
|
52
|
+
let prevD = -1;
|
|
53
|
+
for (let i = 0; i <= 20; i += 1) {
|
|
54
|
+
const domTop = (domMax * i) / 20;
|
|
55
|
+
const logical = s.domToLogical(domTop);
|
|
56
|
+
expect(logical).toBeGreaterThanOrEqual(prevL);
|
|
57
|
+
prevL = logical;
|
|
58
|
+
const logIn = (logicalMax * i) / 20;
|
|
59
|
+
const dom = s.logicalToDom(logIn);
|
|
60
|
+
expect(dom).toBeGreaterThanOrEqual(prevD);
|
|
61
|
+
prevD = dom;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('createRowScrollScaling - edge cases', () => {
|
|
66
|
+
it('handles an empty grid without dividing by zero', () => {
|
|
67
|
+
const s = createRowScrollScaling(0, 17895697, VIEWPORT);
|
|
68
|
+
expect(s.active).toBe(false);
|
|
69
|
+
expect(s.domTotal).toBe(0);
|
|
70
|
+
expect(s.domToLogical(0)).toBe(0);
|
|
71
|
+
expect(s.logicalToDom(0)).toBe(0);
|
|
72
|
+
});
|
|
73
|
+
it('handles content shorter than the viewport', () => {
|
|
74
|
+
const s = createRowScrollScaling(200, 17895697, VIEWPORT);
|
|
75
|
+
expect(s.active).toBe(false);
|
|
76
|
+
expect(s.domToLogical(0)).toBe(0);
|
|
77
|
+
});
|
|
78
|
+
it('extreme scale (100M rows) keeps the last row reachable', () => {
|
|
79
|
+
const trueTotal = 100000000 * 32; // 3.2 billion px
|
|
80
|
+
const s = createRowScrollScaling(trueTotal, 33554400, VIEWPORT);
|
|
81
|
+
expect(s.active).toBe(true);
|
|
82
|
+
expect(s.domTotal).toBe(33554400);
|
|
83
|
+
const domMax = s.domTotal - VIEWPORT;
|
|
84
|
+
expect(s.domToLogical(domMax)).toBeCloseTo(trueTotal - VIEWPORT, 0);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -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
|
@@ -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}
|