@svgrid/grid 3.0.6 → 3.0.8

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 (77) hide show
  1. package/README.md +1 -1
  2. package/dist/SvGrid.controller.svelte.d.ts +2 -0
  3. package/dist/SvGrid.controller.svelte.js +46 -9
  4. package/dist/SvGrid.css +16 -5
  5. package/dist/SvGrid.helpers.js +2 -4
  6. package/dist/SvGrid.svelte +4 -1
  7. package/dist/cdn/{GridMenus-C-NtjlrJ.js → GridMenus-B8-Gbfq2.js} +2 -2
  8. package/dist/cdn/{GridMenus-TssHej3q.js → GridMenus-D15bhpX5.js} +2 -2
  9. package/dist/cdn/{SvGridCellEditor-BlyCSp0q.js → SvGridCellEditor-9yz-SbdZ.js} +1 -1
  10. package/dist/cdn/{SvGridCellEditor-ClRRA9lW.js → SvGridCellEditor-D7-ql1Hd.js} +1 -1
  11. package/dist/cdn/{SvGridChart-DsmwTyZF.js → SvGridChart-DMUOFxOe.js} +3 -3
  12. package/dist/cdn/{SvGridChart-B4EV8U6t.js → SvGridChart-N7ik0oWy.js} +3 -3
  13. package/dist/cdn/{SvGridChartBuilder-cHloHobN.js → SvGridChartBuilder-CB_Jnm2k.js} +3 -3
  14. package/dist/cdn/{SvGridChartBuilder-vOl7C0Za.js → SvGridChartBuilder-Ca7gF8ZK.js} +3 -3
  15. package/dist/cdn/{SvGridChartPanel-AcqF-Ll5.js → SvGridChartPanel-9GOJRHP7.js} +5 -5
  16. package/dist/cdn/{SvGridChartPanel-biMtSBPA.js → SvGridChartPanel-Dl-FujN8.js} +5 -5
  17. package/dist/cdn/{SvGridChartView-I3rDHXAq.js → SvGridChartView-B1LFgoPP.js} +2 -2
  18. package/dist/cdn/{SvGridChartView-Dx_GmZuL.js → SvGridChartView-By3n7DGZ.js} +2 -2
  19. package/dist/cdn/{cell-formatting-DVVKOSI9.js → cell-formatting-Mnv84zoE.js} +27 -5
  20. package/dist/cdn/{chart-DEZxwC0i.js → chart-By7h2Guv.js} +1 -1
  21. package/dist/cdn/{chart-panel-messages-D12XcTZJ.js → chart-panel-messages-BC-tOgFY.js} +1 -1
  22. package/dist/cdn/{chart-panel-messages-DguS7gC5.js → chart-panel-messages-DUaXr-sb.js} +1 -1
  23. package/dist/cdn/{chart-summary-CMAXBGY-.js → chart-summary-BDtrNEiA.js} +1 -1
  24. package/dist/cdn/{chart-trend-BkEp61Gj.js → chart-trend-C1aOAPBZ.js} +1 -1
  25. package/dist/cdn/{editor-registry-BhgE3S84.js → editor-registry-BVeHvlZR.js} +49 -50
  26. package/dist/cdn/{export-format-YzdwBww2.js → export-format-y1wdyCcv.js} +93 -97
  27. package/dist/cdn/{src-CgoeSM8k.js → src-CleI0g9l.js} +3256 -3198
  28. package/dist/cdn/{src-DfwxB1TH.js → src-Dz625Fhx.js} +3184 -3126
  29. package/dist/cdn/svgrid.js +11 -11
  30. package/dist/cdn/svgrid.svelte-external.js +11 -11
  31. package/dist/cdn/validate-GEs4ZPSW.js +80 -0
  32. package/dist/cell-formatting.d.ts +9 -0
  33. package/dist/cell-formatting.js +34 -0
  34. package/dist/core.js +124 -21
  35. package/dist/export-format.d.ts +8 -0
  36. package/dist/export-format.js +23 -16
  37. package/dist/headless.d.ts +1 -1
  38. package/dist/headless.js +1 -1
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +1 -1
  41. package/dist/keyboard-handlers.js +5 -1
  42. package/dist/selection.js +14 -4
  43. package/dist/{subscribe.js → subscribe.svelte.js} +5 -1
  44. package/dist/validate.js +10 -3
  45. package/dist/virtualization/column-virtualizer.d.ts +1 -0
  46. package/dist/virtualization/svelte-virtualizer.svelte.d.ts +1 -0
  47. package/dist/virtualization/svelte-virtualizer.svelte.js +1 -0
  48. package/dist/virtualization/virtualizer.d.ts +3 -0
  49. package/dist/virtualization/virtualizer.js +14 -1
  50. package/package.json +1 -1
  51. package/src/SvGrid.controller.svelte.ts +45 -13
  52. package/src/SvGrid.css +16 -5
  53. package/src/SvGrid.helpers.ts +2 -4
  54. package/src/SvGrid.svelte +4 -1
  55. package/src/cell-formatting.ts +31 -0
  56. package/src/core.sort.test.ts +86 -0
  57. package/src/core.ts +114 -21
  58. package/src/export-dates.test.ts +114 -0
  59. package/src/export-format.ts +26 -14
  60. package/src/headless.reactivity.svelte.test.ts +89 -0
  61. package/src/headless.ts +1 -1
  62. package/src/index.ts +1 -1
  63. package/src/keyboard-handlers.ts +5 -1
  64. package/src/selection.test.ts +37 -0
  65. package/src/selection.ts +16 -6
  66. package/src/{subscribe.test.ts → subscribe.svelte.test.ts} +1 -1
  67. package/src/{subscribe.ts → subscribe.svelte.ts} +5 -1
  68. package/src/svgrid.behavior.test.ts +29 -0
  69. package/src/svgrid.data-push.svelte.test.ts +94 -0
  70. package/src/svgrid.live-update-paths.svelte.test.ts +4 -2
  71. package/src/validate.test.ts +16 -0
  72. package/src/validate.ts +10 -2
  73. package/src/virtualization/svelte-virtualizer.svelte.ts +1 -0
  74. package/src/virtualization/virtualizer.test.ts +44 -0
  75. package/src/virtualization/virtualizer.ts +13 -1
  76. package/dist/cdn/validate-AZoD8BoR.js +0 -80
  77. /package/dist/{subscribe.d.ts → subscribe.svelte.d.ts} +0 -0
package/src/index.ts CHANGED
@@ -73,7 +73,7 @@ export {
73
73
 
74
74
  export { createGrid, createSvGrid, type SvelteGrid } from './createGrid.svelte'
75
75
  export { createGridState, createSvGridState } from './createGridState.svelte'
76
- export { subscribeGrid, subscribeSvGrid } from './subscribe'
76
+ export { subscribeGrid, subscribeSvGrid } from './subscribe.svelte'
77
77
  export { default as SvGrid } from './SvGrid.svelte'
78
78
  // Kanban board view. The `board` prop + its config types live in the free grid,
79
79
  // but the RENDERER (SvGridBoard) ships in @svgrid/enterprise and registers itself
@@ -216,7 +216,11 @@ export function createKeyboard<
216
216
  }
217
217
  }
218
218
  if (column?.columnDef.editorType === "checkbox") {
219
- ctx.toggleBooleanCell(current.rowIndex, current.colIndex);
219
+ // Same rule as a click: `editable: false` (or an editable callback
220
+ // saying no) keeps the checkbox read-only.
221
+ if (ctx.isCellEditableAt?.(current.rowIndex, current.colIndex) ?? true) {
222
+ ctx.toggleBooleanCell(current.rowIndex, current.colIndex);
223
+ }
220
224
  return;
221
225
  }
222
226
  ctx.onCellDoubleClick(current.rowIndex, current.colIndex);
@@ -657,6 +657,43 @@ describe('createSelection - onCellClick', () => {
657
657
  expect(ctx.selectionRange.anchor).toEqual({ rowIndex: 0, colIndex: 0 })
658
658
  })
659
659
 
660
+ describe('boolean cells that are not declared checkboxes', () => {
661
+ const boolCtx = (over: any = {}, columnDef: any = {}) =>
662
+ clickCtx({
663
+ allRows: [makeDataRow('r0', { done: true })],
664
+ allColumns: [makeColumn('done', { field: 'done', ...columnDef })],
665
+ ...over,
666
+ })
667
+
668
+ it('do not toggle (or write the row) while inline editing is off', () => {
669
+ const ctx = boolCtx({ editingEnabled: false })
670
+ createSelection(ctx).onCellClick(0, 0)
671
+ expect(ctx.toggleBooleanCell).not.toHaveBeenCalled()
672
+ expect(ctx.selectionRange.anchor).toEqual({ rowIndex: 0, colIndex: 0 })
673
+ })
674
+
675
+ it('toggle when inline editing is on and the cell is editable', () => {
676
+ const ctx = boolCtx({ editingEnabled: true, isCellEditableAt: () => true })
677
+ createSelection(ctx).onCellClick(0, 0)
678
+ expect(ctx.toggleBooleanCell).toHaveBeenCalledWith(0, 0)
679
+ })
680
+
681
+ it('do not toggle when the column has its own cell renderer', () => {
682
+ const ctx = boolCtx({ editingEnabled: true, isCellEditableAt: () => true }, { cell: () => 'flag' })
683
+ createSelection(ctx).onCellClick(0, 0)
684
+ expect(ctx.toggleBooleanCell).not.toHaveBeenCalled()
685
+ })
686
+ })
687
+
688
+ it('a declared checkbox that is not editable stays put', () => {
689
+ const ctx = clickCtx({
690
+ allColumns: [makeColumn('c0', { editorType: 'checkbox', editable: false })],
691
+ isCellEditableAt: () => false,
692
+ })
693
+ createSelection(ctx).onCellClick(0, 0)
694
+ expect(ctx.toggleBooleanCell).not.toHaveBeenCalled()
695
+ })
696
+
660
697
  it('enters edit mode when clicking the previously-active editable cell', () => {
661
698
  const ctx = clickCtx({
662
699
  editingEnabled: true,
package/src/selection.ts CHANGED
@@ -655,12 +655,22 @@ export function createSelection<
655
655
  row: row.original as TData,
656
656
  });
657
657
 
658
- const isCheckboxColumn =
659
- column.columnDef.editorType === "checkbox" ||
660
- typeof cellValue === "boolean";
661
-
662
- if (isCheckboxColumn) {
663
- ctx.toggleBooleanCell(rowIndex, colIndex);
658
+ // A declared checkbox editor toggles on click unless the cell is not
659
+ // editable. A value that only HAPPENS to be boolean is drawn as a read-only
660
+ // checkbox, so it toggles only when inline editing is on - it used to flip
661
+ // and write the row on any click, in grids with editing switched off. A
662
+ // column with its own `cell` renderer draws (and handles) the value itself.
663
+ const declaredCheckbox = column.columnDef.editorType === "checkbox";
664
+ const inferredCheckbox =
665
+ !declaredCheckbox &&
666
+ typeof cellValue === "boolean" &&
667
+ column.columnDef.cell == null;
668
+
669
+ if (declaredCheckbox || inferredCheckbox) {
670
+ const editable = ctx.isCellEditableAt?.(rowIndex, colIndex) ?? true;
671
+ if (editable && (declaredCheckbox || ctx.editingEnabled)) {
672
+ ctx.toggleBooleanCell(rowIndex, colIndex);
673
+ }
664
674
  setActiveCell(rowIndex, colIndex);
665
675
  setSelection(rowIndex, colIndex);
666
676
  ctx.editingCell = null;
@@ -6,7 +6,7 @@
6
6
  * selection actually changed.
7
7
  */
8
8
  import { describe, expect, it } from 'vitest'
9
- import { subscribeGrid, subscribeSvGrid } from './subscribe'
9
+ import { subscribeGrid, subscribeSvGrid } from './subscribe.svelte'
10
10
 
11
11
  type Listener = () => void
12
12
 
@@ -23,7 +23,11 @@ export function subscribeGrid<
23
23
  grid: SvGrid<TData>,
24
24
  selector: (state: Record<string, any>) => TSelected,
25
25
  ) {
26
- let current = selector(grid.store.state)
26
+ // $state, not a plain variable: core.ts keeps a deliberately framework-free
27
+ // store, so this Svelte adapter is where reactivity belongs. Without it a
28
+ // `$derived` reading .current never re-runs and the docs' headless examples
29
+ // render their seeded state forever.
30
+ let current = $state(selector(grid.store.state))
27
31
  grid.store.subscribe(() => {
28
32
  const next = selector(grid.store.state)
29
33
  if (!shallowCompare(current, next)) current = next
@@ -620,6 +620,35 @@ describe('SvGrid - custom snippet cell', () => {
620
620
  destroy()
621
621
  }
622
622
  })
623
+
624
+ it('a boolean column with its own cell renderer draws the renderer, not the built-in checkbox', async () => {
625
+ // The checkbox branch used to run before the `cell` branch, so any boolean
626
+ // value came out as a read-only checkbox and the renderer was never called.
627
+ const flagCols: ColumnDef<typeof fullFeatures, Person>[] = personColumns.map((c) =>
628
+ c.field === 'active'
629
+ ? { ...c, cell: (ctx: any) => ((ctx.row.original as Person).active ? 'ON' : 'OFF') }
630
+ : c,
631
+ )
632
+ const { target, destroy } = await mountGrid({ columns: flagCols, showRowSelection: false })
633
+ try {
634
+ await tick()
635
+ expect(target.querySelectorAll('.sv-grid-checkbox-readonly').length).toBe(0)
636
+ expect(target.textContent).toContain('ON')
637
+ expect(target.textContent).toContain('OFF')
638
+ } finally {
639
+ destroy()
640
+ }
641
+ })
642
+
643
+ it('a boolean column without a renderer still draws the read-only checkbox', async () => {
644
+ const { target, destroy } = await mountGrid({ showRowSelection: false })
645
+ try {
646
+ await tick()
647
+ expect(target.querySelectorAll('.sv-grid-checkbox-readonly').length).toBeGreaterThan(0)
648
+ } finally {
649
+ destroy()
650
+ }
651
+ })
623
652
  })
624
653
 
625
654
  describe('SvGrid - filterMode shortcuts', () => {
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Pushing onto a `$state` array passed as `data`.
3
+ *
4
+ * docs/getting-started/3-data-and-columns.md teaches exactly this: declare
5
+ * `let rows = $state([...])`, hand it to `data`, and an "Add row" button that
6
+ * does nothing but `rows.push(...)`. The grid is supposed to follow.
7
+ *
8
+ * `svgrid.live-update-paths.svelte.test.ts` covers the two MUTATION paths
9
+ * (field write, element replacement) against a sorted grid. This file covers
10
+ * the LENGTH path on the plainest possible grid, because a row appearing is a
11
+ * different question from a cell changing: nothing in the template is reading
12
+ * the new row yet, so the row model has to notice the array grew.
13
+ *
14
+ * Lives in a `.svelte.test.ts` so `$state` is available.
15
+ */
16
+ import { describe, expect, it } from 'vitest'
17
+ import { mount, unmount, flushSync } from 'svelte'
18
+ import SvGrid from './SvGrid.svelte'
19
+
20
+ type Staff = { id: string; firstName: string; age: number }
21
+
22
+ const staffColumns = [
23
+ { field: 'firstName', header: 'First name', width: 180 },
24
+ { field: 'age', header: 'Age', width: 90 },
25
+ ]
26
+
27
+ /** The props object is the `$state` proxy itself; a spread would snapshot it. */
28
+ function pushProps(rows: Staff[]) {
29
+ const props = $state({
30
+ data: rows,
31
+ columns: staffColumns,
32
+ containerHeight: 400,
33
+ virtualization: false,
34
+ })
35
+ return props
36
+ }
37
+
38
+ function mountGrid(props: ReturnType<typeof pushProps>) {
39
+ const target = document.createElement('div')
40
+ document.body.appendChild(target)
41
+ const app = mount(SvGrid, { target, props: props as never })
42
+ flushSync()
43
+ const names = () =>
44
+ Array.from(
45
+ target.querySelectorAll('tbody.sv-grid-body tr.sv-grid-row:not(.sv-grid-row-spacer)'),
46
+ ).map((tr) => tr.querySelector('.sv-grid-cell:not(.sv-grid-selection-cell)')?.textContent?.trim())
47
+ return {
48
+ names,
49
+ done() {
50
+ unmount(app)
51
+ target.remove()
52
+ },
53
+ }
54
+ }
55
+
56
+ describe('data array length changes', () => {
57
+ it('renders the rows it was given', () => {
58
+ const rows = $state<Staff[]>([
59
+ { id: '1', firstName: 'Ada', age: 36 },
60
+ { id: '2', firstName: 'Linus', age: 54 },
61
+ ])
62
+ const g = mountGrid(pushProps(rows))
63
+ expect(g.names()).toEqual(['Ada', 'Linus'])
64
+ g.done()
65
+ })
66
+
67
+ it('push onto the $state array adds the row (the docs "Add row" button)', () => {
68
+ const rows = $state<Staff[]>([
69
+ { id: '1', firstName: 'Ada', age: 36 },
70
+ { id: '2', firstName: 'Linus', age: 54 },
71
+ ])
72
+ const g = mountGrid(pushProps(rows))
73
+
74
+ rows.push({ id: '3', firstName: 'New', age: 0 })
75
+ flushSync()
76
+
77
+ expect(g.names()).toEqual(['Ada', 'Linus', 'New'])
78
+ g.done()
79
+ })
80
+
81
+ it('splice off the $state array removes the row', () => {
82
+ const rows = $state<Staff[]>([
83
+ { id: '1', firstName: 'Ada', age: 36 },
84
+ { id: '2', firstName: 'Linus', age: 54 },
85
+ ])
86
+ const g = mountGrid(pushProps(rows))
87
+
88
+ rows.splice(0, 1)
89
+ flushSync()
90
+
91
+ expect(g.names()).toEqual(['Linus'])
92
+ g.done()
93
+ })
94
+ })
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * The two ways a live feed can change a row, and what each one reaches.
3
3
  *
4
- * A `$state` array passed as `data` has two update paths and they are not
5
- * interchangeable, which is the whole design problem of a ticking grid:
4
+ * A `$state` array passed as `data` has two update paths for a row's VALUES,
5
+ * and they are not interchangeable, which is the whole design problem of a
6
+ * ticking grid. (Changing the row COUNT is a third path, tracked on its own;
7
+ * it lives in `svgrid.data-push.svelte.test.ts`.)
6
8
  *
7
9
  * 1. Mutate a field through the proxy (`rows[i].price = x`). The visible
8
10
  * cell re-renders because its template reads the field through the
@@ -58,6 +58,22 @@ describe('unknown field', () => {
58
58
  expect(run({ data: [...loading, ...data], columns: [{ field: 'naem' }] })).toHaveLength(1)
59
59
  })
60
60
 
61
+ it('does not walk a million placeholders looking for the sample', () => {
62
+ // A row model scrolled to its middle: placeholders up to the loaded block.
63
+ const placeholder = createRowPlaceholder('loading', { index: 0 })
64
+ const big = new Array(1_000_000).fill(placeholder)
65
+ big.push({ name: 'x' })
66
+ let reads = 0
67
+ const counted = new Proxy(big, {
68
+ get(target, key, receiver) {
69
+ if (typeof key === 'string' && /^\d+$/.test(key)) reads += 1
70
+ return Reflect.get(target, key, receiver)
71
+ },
72
+ })
73
+ expect(run({ data: counted, columns: [{ field: 'naem' }] })).toEqual([])
74
+ expect(reads).toBeLessThanOrEqual(1000)
75
+ })
76
+
61
77
  it('looks through group columns', () => {
62
78
  const msgs = run({ columns: [{ header: 'Group', columns: [{ field: 'nope' }] }] })
63
79
  expect(msgs[0]).toContain('"nope" does not exist')
package/src/validate.ts CHANGED
@@ -24,6 +24,8 @@ import { rowPlaceholderState } from './server-block-cache'
24
24
 
25
25
  /** How many rows to sample when deciding whether a `field` exists. */
26
26
  const FIELD_SAMPLE_ROWS = 10
27
+ /** How many rows to look through for that sample, placeholders included. */
28
+ const FIELD_SCAN_ROWS = 1000
27
29
 
28
30
  const DOCS = 'https://svgrid.com/docs/getting-started/3-data-and-columns/'
29
31
 
@@ -79,9 +81,15 @@ export function validateGridConfig<
79
81
  // no data keys at all and say nothing about the columns.
80
82
  // Stops at the sample size: this runs on every data change, and a filter
81
83
  // over all the rows first was O(n) per tick on a live feed.
84
+ // The scan is bounded too: a row model scrolled to the middle of a million
85
+ // rows, or just purged by a sort, hands over a list that is placeholders
86
+ // up to the loaded block, and walking all of them for ten real rows was
87
+ // O(n) per block landing. Finding none means nothing to judge by.
82
88
  const sample: TData[] = []
83
- for (const row of input.data ?? []) {
84
- if (sample.length >= FIELD_SAMPLE_ROWS) break
89
+ const data = input.data ?? []
90
+ const scan = Math.min(data.length, FIELD_SCAN_ROWS)
91
+ for (let i = 0; i < scan && sample.length < FIELD_SAMPLE_ROWS; i += 1) {
92
+ const row = data[i]!
85
93
  if (!rowPlaceholderState(row)) sample.push(row)
86
94
  }
87
95
  // Under server-side grouping the first rows are group rows, which carry
@@ -14,6 +14,7 @@ export function createSvelteVirtualizer(options: VirtualizerOptions) {
14
14
  return version
15
15
  },
16
16
  setOptions: virtualizer.setOptions,
17
+ measure: virtualizer.measure,
17
18
  setScrollOffset: virtualizer.setScrollOffset,
18
19
  setViewportHeight: virtualizer.setViewportHeight,
19
20
  scrollToIndex: virtualizer.scrollToIndex,
@@ -44,4 +44,48 @@ describe('virtualizer', () => {
44
44
  v.scrollToIndex(9_999)
45
45
  expect(v.getState().scrollOffset).toBe(2_700)
46
46
  })
47
+
48
+ it('re-measures a stable size function passed back through setOptions (#102)', () => {
49
+ const heights = new Map<number, number>()
50
+ const estimateSize = (i: number) => heights.get(i) ?? 20
51
+ const v = createVirtualizer({ count: 10, estimateSize, viewportHeight: 100 })
52
+ expect(v.getTotalSize()).toBe(200)
53
+
54
+ heights.set(0, 120)
55
+ v.setOptions({ estimateSize })
56
+ expect(v.getSizeForIndex(0)).toBe(120)
57
+ expect(v.getTotalSize()).toBe(300) // 9 x 20 + 120
58
+ expect(v.getOffsetForIndex(5)).toBe(200) // 120 + 4 x 20
59
+ })
60
+
61
+ it('measure() re-reads sizes and notifies subscribers', () => {
62
+ const heights = new Map<number, number>()
63
+ const v = createVirtualizer({
64
+ count: 10,
65
+ estimateSize: (i: number) => heights.get(i) ?? 20,
66
+ viewportHeight: 100,
67
+ })
68
+ let notified = 0
69
+ v.subscribe(() => (notified += 1))
70
+
71
+ heights.set(3, 50)
72
+ v.measure()
73
+ expect(v.getTotalSize()).toBe(230)
74
+ expect(v.getOffsetForIndex(4)).toBe(110)
75
+ expect(notified).toBe(1)
76
+ })
77
+
78
+ it('keeps the offset cache across scrolls', () => {
79
+ let calls = 0
80
+ const v = createVirtualizer({
81
+ count: 1000,
82
+ estimateSize: () => (calls++, 20),
83
+ viewportHeight: 100,
84
+ })
85
+ const afterBuild = calls
86
+ v.setScrollOffset(500)
87
+ v.setScrollOffset(900)
88
+ v.setViewportHeight(200)
89
+ expect(calls).toBe(afterBuild)
90
+ })
47
91
  })
@@ -261,8 +261,20 @@ export function createVirtualizer(initial: VirtualizerOptions) {
261
261
  return {
262
262
  setOptions(next: Partial<VirtualizerOptions>) {
263
263
  const merged = { ...options, ...next }
264
- if (sameOptions(options, merged)) return
264
+ // Passing a size function means "sizes may have changed", even when it is
265
+ // the same reference: the documented variable-height pattern is a stable
266
+ // function reading a mutable map, and keying the cache on identity alone
267
+ // left the offsets and total stale after a row grew (#102).
268
+ const remeasure = typeof next.estimateSize === 'function'
269
+ if (!remeasure && sameOptions(options, merged)) return
265
270
  options = merged
271
+ if (remeasure) offsetCache = null
272
+ recalc()
273
+ },
274
+ /** Drop the cached offsets and re-read every size from `estimateSize`.
275
+ * Call after the sizes a stable size function reports have changed. */
276
+ measure() {
277
+ offsetCache = null
266
278
  recalc()
267
279
  },
268
280
  setScrollOffset(scrollOffset: number) {
@@ -1,80 +0,0 @@
1
- import { r as e } from "./server-block-cache-DIqb3VZn.js";
2
- //#region src/validate.ts
3
- var t = 10, n = "https://svgrid.com/docs/getting-started/3-data-and-columns/";
4
- function r(e, t = []) {
5
- for (let n of e) n.columns?.length ? r(n.columns, t) : t.push(n);
6
- return t;
7
- }
8
- function i(i) {
9
- let o = [], s = r(i.columns ?? []), c = [];
10
- for (let n of i.data ?? []) {
11
- if (c.length >= t) break;
12
- e(n) || c.push(n);
13
- }
14
- if (c.length && !i.serverGroup) {
15
- let e = /* @__PURE__ */ new Set();
16
- for (let t of c) if (t && typeof t == "object") for (let n of Object.keys(t)) e.add(n);
17
- for (let t of s) {
18
- let r = t.field;
19
- if (!r || t.fieldFn || e.has(r)) continue;
20
- let i = a(r, [...e]);
21
- o.push(`[svgrid] Column field "${r}" does not exist on your row data, so that column renders empty.${i ? ` Did you mean "${i}"?` : ""} Available keys: ${[...e].slice(0, 12).join(", ")}. See ${n}`);
22
- }
23
- }
24
- let l = /* @__PURE__ */ new Map();
25
- for (let e of s) {
26
- let t = e.id ?? e.field;
27
- t && l.set(t, (l.get(t) ?? 0) + 1);
28
- }
29
- for (let [e, t] of l) t > 1 && o.push(`[svgrid] ${t} columns share the id "${e}". Column state (sorting, filtering, pinning, width) is keyed by id, so they will act as one. Give each an explicit \`id\`. See ${n}`);
30
- let u = i.pageable === void 0 && i.showPagination === void 0;
31
- i.pageSize !== void 0 && u && o.push("[svgrid] `pageSize` is set but pagination was never turned on, so it has no effect and every row renders. Add `pageable` to switch the pager on."), !(i.sortable === !0 || i.features?.rowSortingFeature) && s.some((e) => e.sortable === !0) && o.push("[svgrid] A column sets `sortable: true`, but sorting is not enabled on the grid, so its header does nothing. Add `sortable` to <SvGrid> (it registers the sorting feature for you).");
32
- let d = /* @__PURE__ */ new Set();
33
- for (let e of s) {
34
- let t = e.id ?? e.field;
35
- t && d.add(t);
36
- }
37
- for (let e of i.groupBy ?? []) {
38
- if (d.has(e)) continue;
39
- let t = a(e, [...d]);
40
- o.push(`[svgrid] \`groupBy\` refers to column "${e}", which does not exist, so that grouping level is ignored.${t ? ` Did you mean "${t}"?` : ""} Column ids: ${[...d].slice(0, 12).join(", ")}`);
41
- }
42
- let f = i.treeData?.column;
43
- if (f && !d.has(f) && o.push(`[svgrid] \`treeData.column\` refers to column "${f}", which does not exist, so the expander falls back to the first visible column.`), c.length && i.treeData) {
44
- let e = /* @__PURE__ */ new Set();
45
- for (let t of c) if (t && typeof t == "object") for (let n of Object.keys(t)) e.add(n);
46
- let t = i.treeData.parentField;
47
- if (t && !e.has(t)) {
48
- let n = a(t, [...e]);
49
- o.push(`[svgrid] \`treeData.parentField\` is "${t}", which is not on your row data, so every row becomes a root and no hierarchy appears.${n ? ` Did you mean "${n}"?` : ""}`);
50
- }
51
- let n = i.treeData.idField;
52
- n && !e.has(n) && o.push(`[svgrid] \`treeData.idField\` is "${n}", which is not on your row data, so parent lookups cannot match and the tree stays flat.`);
53
- }
54
- return i.gantt && i.treeData && o.push("[svgrid] `gantt` and `treeData` are both set. The Gantt builds its own work-breakdown tree from `gantt.parentField` and needs every descendant row, but `treeData` hides collapsed children from the view, so summary bars will under-report. Remove `treeData` and set `gantt.parentField` instead."), (i.initialColumnPinning?.left?.length ?? 0) + (i.initialColumnPinning?.right?.length ?? 0) > 0 && i.columnVirtualization !== !1 && o.push("[svgrid] `initialColumnPinning` is set while column virtualization is on (its default), so the pinned columns will not stick - the virtualizer recycles column nodes. Add `columnVirtualization={false}`."), i.externalPagination === !0 && i.rowCount === void 0 && o.push("[svgrid] `externalPagination` is on but `rowCount` is not set, so the pager cannot know how many pages exist. Pass the server total as `rowCount`."), i.externalSort === !0 && !i.onSortingChange && o.push("[svgrid] `externalSort` is on but there is no `onSortingChange` handler, so clicking a header changes nothing - the grid stopped sorting and nobody is listening. Add `onSortingChange` and re-fetch in your handler."), i.externalFilter === !0 && !i.onFiltersChange && o.push("[svgrid] `externalFilter` is on but there is no `onFiltersChange` handler, so filtering the grid changes nothing. Add `onFiltersChange` and re-fetch in your handler."), o;
55
- }
56
- function a(e, t) {
57
- let n, r = Infinity;
58
- for (let i of t) {
59
- let t = o(e.toLowerCase(), i.toLowerCase());
60
- t < r && (r = t, n = i);
61
- }
62
- let i = Math.min(3, Math.floor(e.length / 3) + 1);
63
- return r <= i ? n : void 0;
64
- }
65
- function o(e, t) {
66
- if (e === t) return 0;
67
- let n = Array(t.length + 1);
68
- for (let e = 0; e <= t.length; e++) n[e] = e;
69
- for (let r = 1; r <= e.length; r++) {
70
- let i = n[0];
71
- n[0] = r;
72
- for (let a = 1; a <= t.length; a++) {
73
- let o = Math.min(n[a] + 1, n[a - 1] + 1, i + (e[r - 1] === t[a - 1] ? 0 : 1));
74
- i = n[a], n[a] = o;
75
- }
76
- }
77
- return n[t.length];
78
- }
79
- //#endregion
80
- export { i as validateGridConfig };
File without changes