@svgrid/grid 2.2.31 → 2.2.32

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 (145) hide show
  1. package/README.md +1 -1
  2. package/dist/SvAvatar.svelte +1 -1
  3. package/dist/SvCheckBox.svelte +2 -1
  4. package/dist/SvCircularProgress.svelte +1 -1
  5. package/dist/SvGrid.controller.svelte.d.ts +20 -0
  6. package/dist/SvGrid.controller.svelte.js +358 -18
  7. package/dist/SvGrid.css +103 -3
  8. package/dist/SvGrid.svelte +162 -10
  9. package/dist/SvGrid.types.d.ts +81 -0
  10. package/dist/SvGridDropdown.svelte +38 -6
  11. package/dist/SvGridDropdown.svelte.d.ts +4 -0
  12. package/dist/SvGridSelect.svelte +12 -3
  13. package/dist/SvGridSelect.svelte.d.ts +6 -0
  14. package/dist/SvGroupCell.svelte +9 -2
  15. package/dist/SvMultiSelect.svelte +12 -3
  16. package/dist/SvMultiSelect.svelte.d.ts +6 -0
  17. package/dist/SvPasswordInput.svelte +1 -1
  18. package/dist/SvProgress.svelte +1 -1
  19. package/dist/SvRating.svelte +1 -1
  20. package/dist/SvResult.svelte +1 -1
  21. package/dist/SvRichText.svelte +31 -16
  22. package/dist/SvRichText.svelte.d.ts +9 -0
  23. package/dist/SvSwitchButton.svelte +2 -1
  24. package/dist/SvToaster.svelte +1 -1
  25. package/dist/SvTree.svelte +9 -2
  26. package/dist/SvTree.svelte.d.ts +5 -0
  27. package/dist/SvTreeSelect.svelte +6 -2
  28. package/dist/build-api.js +16 -0
  29. package/dist/cdn/{GridMenus-Ds3OpzZT.js → GridMenus-C6ixSGWW.js} +33 -33
  30. package/dist/cdn/{GridMenus-Da_p7SKS.js → GridMenus-Cyk3qpJ7.js} +33 -33
  31. package/dist/cdn/{SvDateTimePicker-B8GopjhC.js → SvDateTimePicker-CdqaP61H.js} +2 -1
  32. package/dist/cdn/{SvDateTimePicker-DB0aIEk8.js → SvDateTimePicker-ClqtVIj3.js} +2 -1
  33. package/dist/cdn/{SvGridDropdown-DjALc_4f.js → SvGridDropdown-D0VdjeR8.js} +129 -123
  34. package/dist/cdn/{SvGridDropdown-hpnHvOGt.js → SvGridDropdown-Dh-2ej8j.js} +92 -87
  35. package/dist/cdn/{src-BLJfdyL0.js → src-BgHjWF0Q.js} +4193 -3837
  36. package/dist/cdn/{src-DYDCiC0D.js → src-CgD-wiuS.js} +5956 -5602
  37. package/dist/cdn/svgrid.js +8 -8
  38. package/dist/cdn/svgrid.svelte-external.js +8 -8
  39. package/dist/cell-render.d.ts +1 -0
  40. package/dist/cell-render.js +115 -1
  41. package/dist/cell-values.js +5 -0
  42. package/dist/core.d.ts +80 -9
  43. package/dist/core.js +89 -0
  44. package/dist/editor-contract.d.ts +12 -5
  45. package/dist/editor-registry.d.ts +18 -7
  46. package/dist/editor-registry.js +10 -1
  47. package/dist/group-display.d.ts +39 -0
  48. package/dist/group-display.js +52 -0
  49. package/dist/index.d.ts +2 -2
  50. package/dist/index.js +2 -2
  51. package/dist/keyboard-handlers.js +16 -0
  52. package/dist/recurrence.js +3 -2
  53. package/dist/svgrid-wrapper.types.d.ts +8 -2
  54. package/dist/themes/index.d.ts +10 -0
  55. package/dist/themes/index.js +26 -0
  56. package/package.json +1 -1
  57. package/src/SvAccordion.test.ts +5 -5
  58. package/src/SvAvatar.svelte +1 -1
  59. package/src/SvButtonGroup.test.ts +6 -6
  60. package/src/SvCheckBox.svelte +2 -1
  61. package/src/SvCircularProgress.svelte +1 -1
  62. package/src/SvFileUpload.test.ts +1 -1
  63. package/src/SvGrid.controller.svelte.ts +367 -17
  64. package/src/SvGrid.css +103 -3
  65. package/src/SvGrid.svelte +162 -10
  66. package/src/SvGrid.types.ts +81 -0
  67. package/src/SvGridDropdown.svelte +38 -6
  68. package/src/SvGridSelect.svelte +12 -3
  69. package/src/SvGroupCell.svelte +9 -2
  70. package/src/SvMultiSelect.svelte +12 -3
  71. package/src/SvPasswordInput.svelte +1 -1
  72. package/src/SvProgress.svelte +1 -1
  73. package/src/SvRating.svelte +1 -1
  74. package/src/SvResult.svelte +1 -1
  75. package/src/SvRichText.svelte +31 -16
  76. package/src/SvSwitchButton.svelte +2 -1
  77. package/src/SvToaster.svelte +1 -1
  78. package/src/SvTree.svelte +9 -2
  79. package/src/SvTreeSelect.svelte +6 -2
  80. package/src/build-api.ts +15 -0
  81. package/src/builtin-editors.test.ts +3 -1
  82. package/src/cell-render.test.ts +27 -3
  83. package/src/cell-render.ts +122 -1
  84. package/src/cell-values.ts +4 -0
  85. package/src/chart-export.test.ts +2 -2
  86. package/src/clipboard.test.ts +0 -2
  87. package/src/core.ts +157 -8
  88. package/src/editing.test.ts +1 -1
  89. package/src/editor-contract.ts +12 -5
  90. package/src/editor-registry.grid.test.ts +48 -0
  91. package/src/editor-registry.test.ts +3 -1
  92. package/src/editor-registry.ts +23 -7
  93. package/src/facet-buckets.test.ts +33 -33
  94. package/src/group-display.test.ts +91 -1
  95. package/src/group-display.ts +83 -0
  96. package/src/index.ts +9 -0
  97. package/src/keyboard-handlers.ts +17 -0
  98. package/src/list-power.test.ts +2 -2
  99. package/src/menus.test.ts +4 -4
  100. package/src/named-views.coverage.test.ts +3 -3
  101. package/src/recurrence.ts +3 -2
  102. package/src/row-resize.test.ts +40 -40
  103. package/src/scheduler-model.ts +1 -1
  104. package/src/server-data-source.coverage.test.ts +1 -1
  105. package/src/server-group-model.test.ts +6 -6
  106. package/src/spreadsheet.test.ts +0 -1
  107. package/src/svgrid-wrapper.types.ts +11 -9
  108. package/src/svgrid.async-editor-options.test.ts +271 -0
  109. package/src/svgrid.auto-row-height.test.ts +204 -0
  110. package/src/svgrid.charting.test.ts +4 -4
  111. package/src/svgrid.comments-autocomplete.test.ts +8 -2
  112. package/src/svgrid.context-menu.test.ts +25 -9
  113. package/src/svgrid.filter-depth.test.ts +163 -0
  114. package/src/svgrid.filter-menu-listbox.svelte.test.ts +16 -2
  115. package/src/svgrid.filter-menu-scroll.test.ts +10 -2
  116. package/src/svgrid.grand-total.test.ts +188 -0
  117. package/src/svgrid.group-display-mode.test.ts +171 -0
  118. package/src/svgrid.group-footers.test.ts +121 -0
  119. package/src/svgrid.group-pagination.test.ts +153 -0
  120. package/src/svgrid.tree-data.test.ts +186 -0
  121. package/src/svgriddropdown.async-panel.svelte.test.ts +195 -0
  122. package/src/themes/index.ts +42 -0
  123. package/src/tree-edit.test.ts +4 -4
  124. package/src/tree-row-model.test.ts +169 -0
  125. package/src/ui-localization.test.ts +113 -0
  126. package/themes/ag-alpine.css +22 -0
  127. package/themes/antd.css +22 -0
  128. package/themes/atlassian.css +22 -0
  129. package/themes/bootstrap.css +22 -0
  130. package/themes/carbon.css +22 -0
  131. package/themes/catppuccin.css +22 -0
  132. package/themes/dracula.css +22 -0
  133. package/themes/ember.css +22 -0
  134. package/themes/excel.css +22 -0
  135. package/themes/fluent.css +22 -0
  136. package/themes/github.css +22 -0
  137. package/themes/linear.css +22 -0
  138. package/themes/material.css +22 -0
  139. package/themes/nord.css +22 -0
  140. package/themes/notion.css +22 -0
  141. package/themes/salesforce.css +22 -0
  142. package/themes/sap.css +22 -0
  143. package/themes/shadcn.css +22 -0
  144. package/themes/tailwind.css +22 -0
  145. package/themes/vercel.css +22 -0
@@ -70,6 +70,89 @@ export function insertGroupFooters<R>(rows: ReadonlyArray<R>, opts: GroupFooterO
70
70
  return out
71
71
  }
72
72
 
73
+ export type GroupPaginationOptions<R> = {
74
+ /** Depth of a row (group rows and their descendants). */
75
+ getDepth: (row: R) => number
76
+ /** True when a row is a group (banner) row. */
77
+ isGroup: (row: R) => boolean
78
+ /**
79
+ * True when a group row is expanded. Defaults to treating every group as
80
+ * expanded. This matters: an EXPANDED group is a header, reprinted above its
81
+ * children and costing no page budget, while a COLLAPSED group is the visible
82
+ * unit itself and must consume a slot - otherwise a fully collapsed grid has
83
+ * no budget-consuming rows at all and every page comes back empty.
84
+ */
85
+ isExpanded?: (row: R) => boolean
86
+ pageIndex: number
87
+ pageSize: number
88
+ }
89
+
90
+ export type GroupPaginationResult<R> = {
91
+ /** The rows to render for this page: the page's budget-consuming rows, each
92
+ * preceded by whichever ancestor banners have not been printed yet. */
93
+ rows: R[]
94
+ /** Budget-consuming rows across every page - what the pager divides by.
95
+ * Data rows plus collapsed group rows; expanded banners do not count. */
96
+ dataRowCount: number
97
+ }
98
+
99
+ /**
100
+ * Paginate a grouped row list by DATA rows, repeating each page's ancestor
101
+ * group headers.
102
+ *
103
+ * Slicing the flat grouped list directly counts group banners against
104
+ * `pageSize`, so `pageSize: 10` over ten groups of two shows almost no data
105
+ * (#73). Here only non-group rows consume the page budget, and a page reprints
106
+ * the group chain its first rows sit under - the same thing a spreadsheet does
107
+ * when a grouped table breaks across pages.
108
+ *
109
+ * Ancestors are tracked with a depth stack rather than parent pointers, so this
110
+ * works on the already-flattened expanded model and needs nothing from the row
111
+ * objects but depth and group-ness.
112
+ */
113
+ export function paginateGroupedRows<R>(
114
+ rows: ReadonlyArray<R>,
115
+ opts: GroupPaginationOptions<R>,
116
+ ): GroupPaginationResult<R> {
117
+ const { getDepth, isGroup, isExpanded, pageIndex, pageSize } = opts
118
+ const expanded = isExpanded ?? (() => true)
119
+ const start = pageIndex * pageSize
120
+ const end = start + pageSize
121
+
122
+ const out: R[] = []
123
+ // Expanded banners currently open, outermost first. `printed` marks the ones
124
+ // this page has already emitted, so a group spanning rows 3-8 prints its
125
+ // banner once, not once per row.
126
+ const openGroups: Array<{ row: R; printed: boolean }> = []
127
+ let unitsSeen = 0
128
+
129
+ for (const row of rows) {
130
+ const depth = getDepth(row)
131
+ // A row at depth <= an open banner's depth closes it.
132
+ while (openGroups.length > 0 && getDepth(openGroups[openGroups.length - 1]!.row) >= depth) {
133
+ openGroups.pop()
134
+ }
135
+ if (isGroup(row) && expanded(row)) {
136
+ // A header for rows that follow: reprinted per page, costs no budget.
137
+ openGroups.push({ row, printed: false })
138
+ continue
139
+ }
140
+ // Everything else is a visible unit: a data row, or a collapsed group
141
+ // standing in for its whole subtree.
142
+ const index = unitsSeen
143
+ unitsSeen += 1
144
+ if (index < start || index >= end) continue
145
+ for (const g of openGroups) {
146
+ if (g.printed) continue
147
+ g.printed = true
148
+ out.push(g.row)
149
+ }
150
+ out.push(row)
151
+ }
152
+
153
+ return { rows: out, dataRowCount: unitsSeen }
154
+ }
155
+
73
156
  export type AutoGroupColumnSpec = {
74
157
  /** Synthetic column id (e.g. `__autoGroup` or `__group_<field>`). */
75
158
  id: string
package/src/index.ts CHANGED
@@ -32,6 +32,8 @@ export {
32
32
  createGroupedRowModel,
33
33
  createPaginatedRowModel,
34
34
  createSortedRowModel,
35
+ createTreeRowModel,
36
+ flattenTreeData,
35
37
  applyGroupAggregate,
36
38
  filterFns,
37
39
  rowExpandingFeature,
@@ -52,6 +54,8 @@ export {
52
54
  type Column,
53
55
  type ColumnDef,
54
56
  type ColumnDefTemplate,
57
+ type GridColumnDef,
58
+ type GridColumns,
55
59
  type GroupAggregator,
56
60
  type Header,
57
61
  type HeaderContext,
@@ -62,6 +66,8 @@ export {
62
66
  type SvGrid as SvGridInstance,
63
67
  type SvGridOptions,
64
68
  type TableFeatures,
69
+ type TreeRowModelOptions,
70
+ type FlattenTreeOptions,
65
71
  type Updater,
66
72
  } from './core'
67
73
 
@@ -671,10 +677,13 @@ export {
671
677
  export {
672
678
  insertGroupFooters,
673
679
  buildAutoGroupColumns,
680
+ paginateGroupedRows,
674
681
  type GroupDisplayType,
675
682
  type GroupFooterOptions,
676
683
  type AutoGroupColumnSpec,
677
684
  type AutoGroupResult,
685
+ type GroupPaginationOptions,
686
+ type GroupPaginationResult,
678
687
  } from './group-display'
679
688
  export { getKeyboardIntent, getNextActiveCell, type GridKeyboardIntent } from './keyboard'
680
689
  export { createVirtualizer } from './virtualization/virtualizer'
@@ -249,6 +249,23 @@ export function createKeyboard<
249
249
  // expands a collapsed group row, ArrowLeft collapses an expanded one,
250
250
  // instead of moving the active cell (treegrid-style). Leaves fall through
251
251
  // to normal navigation.
252
+ // Client tree data gets the same ArrowRight/ArrowLeft contract, driven off
253
+ // the row model rather than consumer callbacks.
254
+ if (ctx.props.treeData && (intent === "moveRight" || intent === "moveLeft")) {
255
+ const row = ctx.allRows[current.rowIndex];
256
+ if (row?.getCanExpand?.()) {
257
+ const expanded = row.getIsExpanded?.() ?? false;
258
+ if (intent === "moveRight" && !expanded) {
259
+ row.toggleExpanded?.();
260
+ return;
261
+ }
262
+ if (intent === "moveLeft" && expanded) {
263
+ row.toggleExpanded?.();
264
+ return;
265
+ }
266
+ }
267
+ }
268
+
252
269
  const serverGroup = ctx.props.serverGroup;
253
270
  if (serverGroup && (intent === "moveRight" || intent === "moveLeft")) {
254
271
  const data = ctx.allRows[current.rowIndex]?.original;
@@ -19,8 +19,8 @@ describe('groupOptions', () => {
19
19
  it('buckets by group in first-seen order and preserves the flat index', () => {
20
20
  const groups = groupOptions(opts)
21
21
  expect(groups.map((g) => g.group)).toEqual(['Americas', 'Europe'])
22
- expect(groups[0].options.map((o) => o.index)).toEqual([0, 1])
23
- expect(groups[1].options.map((o) => o.index)).toEqual([2, 3])
22
+ expect(groups[0]!.options.map((o) => o.index)).toEqual([0, 1])
23
+ expect(groups[1]!.options.map((o) => o.index)).toEqual([2, 3])
24
24
  expect(hasGroups(opts)).toBe(true)
25
25
  })
26
26
  it('returns a single null group when nothing is grouped', () => {
package/src/menus.test.ts CHANGED
@@ -557,8 +557,8 @@ describe('createMenus - contextMenuItems', () => {
557
557
  expect(labels).toContain('Remove row')
558
558
  expect(labels).toContain('Remove column')
559
559
  // no leading/trailing separator
560
- expect(items[0].separator).toBeFalsy()
561
- expect(items[items.length - 1].separator).toBeFalsy()
560
+ expect(items[0]!.separator).toBeFalsy()
561
+ expect(items[items.length - 1]!.separator).toBeFalsy()
562
562
  })
563
563
 
564
564
  it('built-in run wiring invokes clipboard helpers', () => {
@@ -627,8 +627,8 @@ describe('createMenus - contextMenuItems', () => {
627
627
  const m = createMenus(ctx)
628
628
  const items = m.contextMenuItems()
629
629
  expect(items.map((i) => i.key)).toEqual(['shown'])
630
- expect(items[0].disabled).toBe(true)
631
- items[0].run!()
630
+ expect(items[0]!.disabled).toBe(true)
631
+ items[0]!.run!()
632
632
  expect(action).toHaveBeenCalled()
633
633
  })
634
634
 
@@ -144,7 +144,7 @@ describe('attachAutoSavedView', () => {
144
144
  const off = attachAutoSavedView(host, views, { intervalMs: 500 })
145
145
  expect(views.has('__autosave')).toBe(false)
146
146
  // mutate the host state, then let the poller tick
147
- host.set({ sorting: [{ id: 'new', desc: false }], columnFilters: [] } as SvGridViewState)
147
+ host.set({ sorting: [{ id: 'new', desc: false }], columnFilters: [] } as unknown as SvGridViewState)
148
148
  vi.advanceTimersByTime(500)
149
149
  expect(views.has('__autosave')).toBe(true)
150
150
  off()
@@ -164,7 +164,7 @@ describe('attachAutoSavedView', () => {
164
164
  const host = fakeHost({ sorting: [] })
165
165
  const views = createNamedViews(host, { storage: memoryViews() })
166
166
  const off = attachAutoSavedView(host, views, { name: 'mySlot', intervalMs: 200 })
167
- host.set({ sorting: [{ id: 'q', desc: true }], columnFilters: [] } as SvGridViewState)
167
+ host.set({ sorting: [{ id: 'q', desc: true }], columnFilters: [] } as unknown as SvGridViewState)
168
168
  vi.advanceTimersByTime(200)
169
169
  expect(views.has('mySlot')).toBe(true)
170
170
  off()
@@ -185,7 +185,7 @@ describe('attachAutoSavedView', () => {
185
185
  const host = {
186
186
  getState: () => {
187
187
  if (throwing) throw new Error('boom')
188
- return { sorting: [], columnFilters: [] } as SvGridViewState
188
+ return { sorting: [], columnFilters: [] } as unknown as SvGridViewState
189
189
  },
190
190
  setState: vi.fn(),
191
191
  }
package/src/recurrence.ts CHANGED
@@ -189,8 +189,9 @@ const ORD: Record<number, string> = { 1: 'first', 2: 'second', 3: 'third', 4: 'f
189
189
 
190
190
  // Where in the month a monthly / yearly rule lands, as a phrase.
191
191
  function whereInMonth(r: RecurrenceRule, wd: ReadonlyArray<string>, ord: Record<number, string>): string {
192
- if (r.weekOfMonth != null && r.weekdays && r.weekdays.length) {
193
- return `the ${ord[r.weekOfMonth] ?? r.weekOfMonth} ${wd[r.weekdays[0]]}`
192
+ const firstWeekday = r.weekdays?.[0]
193
+ if (r.weekOfMonth != null && firstWeekday != null) {
194
+ return `the ${ord[r.weekOfMonth] ?? r.weekOfMonth} ${wd[firstWeekday]}`
194
195
  }
195
196
  if (r.day === -1) return 'the last day'
196
197
  if (r.day != null && r.day < 0) return `the ${-r.day} day(s) from the end`
@@ -97,8 +97,8 @@ describe('rowResize - strip injection', () => {
97
97
  expect(strip!.style.cursor).toBe('row-resize')
98
98
  }
99
99
  // Gutter cells become a positioning context with visible overflow.
100
- expect(gutterOf(rows[0]).style.position).toBe('relative')
101
- expect(gutterOf(rows[0]).style.overflow).toBe('visible')
100
+ expect(gutterOf(rows[0]!).style.position).toBe('relative')
101
+ expect(gutterOf(rows[0]!).style.overflow).toBe('visible')
102
102
  action.destroy()
103
103
  })
104
104
 
@@ -106,12 +106,12 @@ describe('rowResize - strip injection', () => {
106
106
  const { host, rows } = buildGrid([{}])
107
107
  track(host)
108
108
  const action = rowResize(host, { onResize: vi.fn() })
109
- expect(rows[0].querySelectorAll(`.${STRIP_CLASS}`).length).toBe(1)
109
+ expect(rows[0]!.querySelectorAll(`.${STRIP_CLASS}`).length).toBe(1)
110
110
  // Trigger the MutationObserver by appending an unrelated node.
111
- gutterOf(rows[0]).appendChild(document.createElement('span'))
111
+ gutterOf(rows[0]!).appendChild(document.createElement('span'))
112
112
  await Promise.resolve()
113
113
  await new Promise((r) => setTimeout(r, 0))
114
- expect(rows[0].querySelectorAll(`.${STRIP_CLASS}`).length).toBe(1)
114
+ expect(rows[0]!.querySelectorAll(`.${STRIP_CLASS}`).length).toBe(1)
115
115
  action.destroy()
116
116
  })
117
117
 
@@ -124,10 +124,10 @@ describe('rowResize - strip injection', () => {
124
124
  ])
125
125
  track(host)
126
126
  const action = rowResize(host, { onResize: vi.fn() })
127
- expect(stripOf(rows[0])).toBeNull()
128
- expect(stripOf(rows[1])).toBeNull()
129
- expect(stripOf(rows[2])).toBeNull()
130
- expect(stripOf(rows[3])).not.toBeNull()
127
+ expect(stripOf(rows[0]!)).toBeNull()
128
+ expect(stripOf(rows[1]!)).toBeNull()
129
+ expect(stripOf(rows[2]!)).toBeNull()
130
+ expect(stripOf(rows[3]!)).not.toBeNull()
131
131
  action.destroy()
132
132
  })
133
133
 
@@ -159,9 +159,9 @@ describe('rowResize - strip injection', () => {
159
159
  it('does not override an already-positioned gutter cell', () => {
160
160
  const { host, rows } = buildGrid([{}])
161
161
  track(host)
162
- gutterOf(rows[0]).style.position = 'sticky'
162
+ gutterOf(rows[0]!).style.position = 'sticky'
163
163
  const action = rowResize(host, { onResize: vi.fn() })
164
- expect(gutterOf(rows[0]).style.position).toBe('sticky')
164
+ expect(gutterOf(rows[0]!).style.position).toBe('sticky')
165
165
  action.destroy()
166
166
  })
167
167
 
@@ -169,8 +169,8 @@ describe('rowResize - strip injection', () => {
169
169
  const { host, rows } = buildGrid([{}, {}])
170
170
  track(host)
171
171
  const action = rowResize(host, { onResize: vi.fn(), disabled: true })
172
- expect(stripOf(rows[0])).toBeNull()
173
- expect(stripOf(rows[1])).toBeNull()
172
+ expect(stripOf(rows[0]!)).toBeNull()
173
+ expect(stripOf(rows[1]!)).toBeNull()
174
174
  action.destroy()
175
175
  })
176
176
  })
@@ -183,11 +183,11 @@ describe('rowResize - drag', () => {
183
183
  it('reports the new height on pointerup and fires move callbacks', () => {
184
184
  const { host, rows } = buildGrid([{ rowIndex: 0 }])
185
185
  track(host)
186
- mockRowHeight(rows[0], 32)
186
+ mockRowHeight(rows[0]!, 32)
187
187
  const onResize = vi.fn()
188
188
  const onResizeMove = vi.fn()
189
189
  const action = rowResize(host, { onResize, onResizeMove })
190
- const strip = stripOf(rows[0])!
190
+ const strip = stripOf(rows[0]!)!
191
191
 
192
192
  strip.dispatchEvent(pointer('pointerdown', { clientY: 100 }))
193
193
  expect(strip.classList.contains('is-resizing')).toBe(true)
@@ -196,7 +196,7 @@ describe('rowResize - drag', () => {
196
196
  // Drag down 20px -> 52.
197
197
  window.dispatchEvent(pointer('pointermove', { clientY: 120 }))
198
198
  expect(onResizeMove).toHaveBeenLastCalledWith(0, 52)
199
- expect(rows[0].style.height).toBe('52px')
199
+ expect(rows[0]!.style.height).toBe('52px')
200
200
 
201
201
  window.dispatchEvent(pointer('pointerup', { clientY: 120 }))
202
202
  expect(onResize).toHaveBeenCalledWith(0, 52)
@@ -208,10 +208,10 @@ describe('rowResize - drag', () => {
208
208
  it('clamps the height to the min bound', () => {
209
209
  const { host, rows } = buildGrid([{ rowIndex: 0 }])
210
210
  track(host)
211
- mockRowHeight(rows[0], 32)
211
+ mockRowHeight(rows[0]!, 32)
212
212
  const onResize = vi.fn()
213
213
  const action = rowResize(host, { onResize, min: 24 })
214
- const strip = stripOf(rows[0])!
214
+ const strip = stripOf(rows[0]!)!
215
215
  strip.dispatchEvent(pointer('pointerdown', { clientY: 100 }))
216
216
  // Drag far up -> would be negative, clamps to 24.
217
217
  window.dispatchEvent(pointer('pointerup', { clientY: 0 }))
@@ -222,10 +222,10 @@ describe('rowResize - drag', () => {
222
222
  it('clamps the height to the max bound', () => {
223
223
  const { host, rows } = buildGrid([{ rowIndex: 0 }])
224
224
  track(host)
225
- mockRowHeight(rows[0], 32)
225
+ mockRowHeight(rows[0]!, 32)
226
226
  const onResize = vi.fn()
227
227
  const action = rowResize(host, { onResize, max: 50 })
228
- const strip = stripOf(rows[0])!
228
+ const strip = stripOf(rows[0]!)!
229
229
  strip.dispatchEvent(pointer('pointerdown', { clientY: 100 }))
230
230
  window.dispatchEvent(pointer('pointerup', { clientY: 1000 }))
231
231
  expect(onResize).toHaveBeenCalledWith(0, 50)
@@ -235,10 +235,10 @@ describe('rowResize - drag', () => {
235
235
  it('works without an onResizeMove callback (optional)', () => {
236
236
  const { host, rows } = buildGrid([{ rowIndex: 0 }])
237
237
  track(host)
238
- mockRowHeight(rows[0], 30)
238
+ mockRowHeight(rows[0]!, 30)
239
239
  const onResize = vi.fn()
240
240
  const action = rowResize(host, { onResize })
241
- const strip = stripOf(rows[0])!
241
+ const strip = stripOf(rows[0]!)!
242
242
  strip.dispatchEvent(pointer('pointerdown', { clientY: 50 }))
243
243
  expect(() => window.dispatchEvent(pointer('pointermove', { clientY: 60 }))).not.toThrow()
244
244
  window.dispatchEvent(pointer('pointerup', { clientY: 60 }))
@@ -270,11 +270,11 @@ describe('rowResize - pointerdown guards', () => {
270
270
  it('ignores pointerdown when disabled', () => {
271
271
  const { host, rows } = buildGrid([{ rowIndex: 0 }])
272
272
  track(host)
273
- mockRowHeight(rows[0], 32)
273
+ mockRowHeight(rows[0]!, 32)
274
274
  const onResize = vi.fn()
275
275
  // Start enabled so a strip exists, then disable via update.
276
276
  const action = rowResize(host, { onResize })
277
- const strip = stripOf(rows[0])!
277
+ const strip = stripOf(rows[0]!)!
278
278
  action.update({ onResize, disabled: true })
279
279
  // update removes strips, but dispatch on the (now-detached) strip anyway:
280
280
  // even if it were still in the DOM, current.disabled short-circuits.
@@ -289,7 +289,7 @@ describe('rowResize - pointerdown guards', () => {
289
289
  track(host)
290
290
  const onResize = vi.fn()
291
291
  const action = rowResize(host, { onResize })
292
- gutterOf(rows[0]).dispatchEvent(pointer('pointerdown', { clientY: 10 }))
292
+ gutterOf(rows[0]!).dispatchEvent(pointer('pointerdown', { clientY: 10 }))
293
293
  expect(document.body.style.cursor).toBe('')
294
294
  action.destroy()
295
295
  })
@@ -298,10 +298,10 @@ describe('rowResize - pointerdown guards', () => {
298
298
  const { host, rows } = buildGrid([{}])
299
299
  track(host)
300
300
  // Remove the data-svgrid-row marker so rowIndexOf returns NaN.
301
- rows[0].querySelector('[data-svgrid-row]')!.removeAttribute('data-svgrid-row')
301
+ rows[0]!.querySelector('[data-svgrid-row]')!.removeAttribute('data-svgrid-row')
302
302
  const onResize = vi.fn()
303
303
  const action = rowResize(host, { onResize })
304
- const strip = stripOf(rows[0])!
304
+ const strip = stripOf(rows[0]!)!
305
305
  strip.dispatchEvent(pointer('pointerdown', { clientY: 10 }))
306
306
  expect(document.body.style.cursor).toBe('')
307
307
  expect(strip.classList.contains('is-resizing')).toBe(false)
@@ -333,14 +333,14 @@ describe('rowResize - update + destroy', () => {
333
333
  track(host)
334
334
  const onResize = vi.fn()
335
335
  const action = rowResize(host, { onResize, disabled: false })
336
- expect(stripOf(rows[0])).not.toBeNull()
336
+ expect(stripOf(rows[0]!)).not.toBeNull()
337
337
 
338
338
  action.update({ onResize, disabled: true })
339
- expect(stripOf(rows[0])).toBeNull()
340
- expect(stripOf(rows[1])).toBeNull()
339
+ expect(stripOf(rows[0]!)).toBeNull()
340
+ expect(stripOf(rows[1]!)).toBeNull()
341
341
 
342
342
  action.update({ onResize, disabled: false })
343
- expect(stripOf(rows[0])).not.toBeNull()
343
+ expect(stripOf(rows[0]!)).not.toBeNull()
344
344
  action.destroy()
345
345
  })
346
346
 
@@ -348,29 +348,29 @@ describe('rowResize - update + destroy', () => {
348
348
  const { host, rows } = buildGrid([{}])
349
349
  track(host)
350
350
  const action = rowResize(host, { onResize: vi.fn() })
351
- const firstStrip = stripOf(rows[0])
351
+ const firstStrip = stripOf(rows[0]!)
352
352
  // Same disabled value -> decorate is NOT called, strip stays the same node.
353
353
  action.update({ onResize: vi.fn() })
354
- expect(stripOf(rows[0])).toBe(firstStrip)
354
+ expect(stripOf(rows[0]!)).toBe(firstStrip)
355
355
  action.destroy()
356
356
  })
357
357
 
358
358
  it('destroy removes all strips and detaches listeners', () => {
359
359
  const { host, rows } = buildGrid([{ rowIndex: 0 }])
360
360
  track(host)
361
- mockRowHeight(rows[0], 32)
361
+ mockRowHeight(rows[0]!, 32)
362
362
  const onResize = vi.fn()
363
363
  const action = rowResize(host, { onResize })
364
- expect(stripOf(rows[0])).not.toBeNull()
364
+ expect(stripOf(rows[0]!)).not.toBeNull()
365
365
 
366
366
  action.destroy()
367
- expect(stripOf(rows[0])).toBeNull()
367
+ expect(stripOf(rows[0]!)).toBeNull()
368
368
 
369
369
  // After destroy the pointerdown listener is gone; re-create a strip
370
370
  // manually and confirm no drag starts.
371
371
  const strip = document.createElement('div')
372
372
  strip.className = STRIP_CLASS
373
- gutterOf(rows[0]).appendChild(strip)
373
+ gutterOf(rows[0]!).appendChild(strip)
374
374
  strip.dispatchEvent(pointer('pointerdown', { clientY: 10 }))
375
375
  expect(document.body.style.cursor).toBe('')
376
376
  })
@@ -378,14 +378,14 @@ describe('rowResize - update + destroy', () => {
378
378
  it('uses default min=20 / max=320 when bounds are omitted', () => {
379
379
  const { host, rows } = buildGrid([{ rowIndex: 0 }])
380
380
  track(host)
381
- mockRowHeight(rows[0], 100)
381
+ mockRowHeight(rows[0]!, 100)
382
382
  const onResize = vi.fn()
383
383
  const action = rowResize(host, { onResize })
384
- const strip = stripOf(rows[0])!
384
+ const strip = stripOf(rows[0]!)!
385
385
  strip.dispatchEvent(pointer('pointerdown', { clientY: 0 }))
386
386
  // Drag way past max -> clamps to 320.
387
387
  window.dispatchEvent(pointer('pointermove', { clientY: 10000 }))
388
- expect(rows[0].style.height).toBe('320px')
388
+ expect(rows[0]!.style.height).toBe('320px')
389
389
  // Drag way below min -> clamps to 20.
390
390
  window.dispatchEvent(pointer('pointerup', { clientY: -10000 }))
391
391
  expect(onResize).toHaveBeenCalledWith(0, 20)
@@ -797,7 +797,7 @@ export function timelineAxis(
797
797
  } else if (view === 'timelineYear') {
798
798
  const y = rangeStart.getFullYear()
799
799
  for (let m = 0; m < 12; m++) {
800
- ticks.push(mk(new Date(y, m, 1), new Date(y, m + 1, 1), TL_MO[m]))
800
+ ticks.push(mk(new Date(y, m, 1), new Date(y, m + 1, 1), TL_MO[m]!))
801
801
  }
802
802
  majors = groupMajors(
803
803
  ticks,
@@ -22,7 +22,7 @@ describe('createServerDataSource - request params', () => {
22
22
  })
23
23
 
24
24
  it('setFilter resets to page 0', async () => {
25
- const getRows = vi.fn(async () => ({ rows: [], rowCount: 100 }))
25
+ const getRows = vi.fn(async (_req: ServerRequest) => ({ rows: [], rowCount: 100 }))
26
26
  const ctl = createServerDataSource<Row>({ getRows }, { pageSize: 10, onChange: () => {} })
27
27
  ctl.setPage(3)
28
28
  await flush()
@@ -23,16 +23,16 @@ function makeGroupSource(data: Row[], delay = 0): ServerDataSource<Row> & { call
23
23
  this.calls += 1
24
24
  if (delay) await new Promise((r) => setTimeout(r, delay))
25
25
  const subset = data.filter((r) =>
26
- req.groupKeys.every((k, i) => String((r as Record<string, unknown>)[req.groupBy[i]!]) === k),
26
+ req.groupKeys!.every((k, i) => String((r as Record<string, unknown>)[req.groupBy![i]!]) === k),
27
27
  )
28
- const level = req.groupKeys.length
29
- if (level < req.groupBy.length) {
30
- const field = req.groupBy[level]!
28
+ const level = req.groupKeys!.length
29
+ if (level < req.groupBy!.length) {
30
+ const field = req.groupBy![level]!
31
31
  const map = new Map<string, Record<string, unknown>>()
32
32
  for (const r of subset) {
33
33
  const key = String((r as Record<string, unknown>)[field])
34
34
  const agg = map.get(key) ?? { [field]: (r as Record<string, unknown>)[field] }
35
- for (const a of req.aggregations) {
35
+ for (const a of req.aggregations!) {
36
36
  const cur = (agg[a.col] as number) ?? 0
37
37
  agg[a.col] = a.fn === 'count' ? cur + 1 : cur + ((r as Record<string, unknown>)[a.col] as number)
38
38
  }
@@ -241,7 +241,7 @@ function makeTreeSource(nodes: Node[]): ServerDataSource<Node> & { calls: number
241
241
  calls: 0,
242
242
  async getRows(req) {
243
243
  this.calls += 1
244
- const parent = req.groupKeys.length ? req.groupKeys[req.groupKeys.length - 1] : null
244
+ const parent = req.groupKeys!.length ? req.groupKeys![req.groupKeys!.length - 1] : null
245
245
  const children = nodes.filter((n) => (n.parentId ?? null) === (parent ?? null))
246
246
  return { rows: children, rowCount: children.length }
247
247
  },
@@ -9,7 +9,6 @@ if (typeof globalThis.CSS === 'undefined') {
9
9
  // @ts-expect-error - minimal shim for the test environment
10
10
  globalThis.CSS = { escape: (s: string) => s.replace(/["\\]/g, '\\$&') }
11
11
  } else if (typeof globalThis.CSS.escape !== 'function') {
12
- // @ts-expect-error - augment partial CSS object
13
12
  globalThis.CSS.escape = (s: string) => s.replace(/["\\]/g, '\\$&')
14
13
  }
15
14
 
@@ -1,18 +1,14 @@
1
1
  import type { CellFormatConfig, ColumnDef, RowData, SvGridOptions, TableFeatures } from './core'
2
2
  // Type-only import (the reverse of SvGrid.types importing SvGridApi); TS resolves the
3
3
  // type-level cycle. Backs the generic key/value typing of `setOption` / `getOption`.
4
- import type { Props } from './SvGrid.types'
4
+ import type { FilterOperator, Props } from './SvGrid.types'
5
5
  import type { GridExportOptions, GridClipboardOptions } from './export-format'
6
6
  import type { ChartSpec, ChartType } from './chart'
7
7
 
8
- export type SvGridFilterOperator =
9
- | 'contains'
10
- | 'equals'
11
- | 'startsWith'
12
- | 'greaterThan'
13
- | 'lessThan'
14
- | 'between'
15
- | 'isBlank'
8
+ // Aliased to the core union rather than restated: the API surfaces below hand
9
+ // back whatever the grid actually filtered with, so a hand-maintained subset
10
+ // here silently mistypes operators like 'endsWith' that reach callers at runtime.
11
+ export type SvGridFilterOperator = FilterOperator
16
12
 
17
13
  /**
18
14
  * A serializable snapshot of everything that makes up the current "view":
@@ -247,6 +243,12 @@ export type SvGridApi<
247
243
  */
248
244
  setFacetFilter(columnId: string, values: ReadonlyArray<string> | null): void
249
245
  clearFilter(columnId: string): void
246
+ /**
247
+ * Drop cached async `editorOptions` so the next render refetches them.
248
+ * Omit `columnId` to clear every column. Only affects columns whose
249
+ * `editorOptions` returns a Promise - static lists are never refetched.
250
+ */
251
+ refreshEditorOptions(columnId?: string): void
250
252
  /**
251
253
  * Clear every active column filter (menu, filter-row, set-list, and global).
252
254
  * Resets the grid to "no filtering" in a single call.