@svgrid/grid 2.2.31 → 2.2.33

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 (196) hide show
  1. package/README.md +1 -1
  2. package/dist/DockNodeView.svelte +1 -1
  3. package/dist/GridFooter.svelte +0 -40
  4. package/dist/GridMenus.svelte +0 -77
  5. package/dist/SvAvatar.svelte +1 -1
  6. package/dist/SvCheckBox.svelte +2 -1
  7. package/dist/SvCircularProgress.svelte +1 -1
  8. package/dist/SvDateRangeInput.svelte +1 -1
  9. package/dist/SvGrid.controller.svelte.d.ts +20 -0
  10. package/dist/SvGrid.controller.svelte.js +358 -18
  11. package/dist/SvGrid.css +103 -3
  12. package/dist/SvGrid.svelte +164 -35
  13. package/dist/SvGrid.types.d.ts +81 -0
  14. package/dist/SvGridChart.svelte +2 -10
  15. package/dist/SvGridDropdown.svelte +38 -6
  16. package/dist/SvGridDropdown.svelte.d.ts +4 -0
  17. package/dist/SvGridSelect.svelte +12 -5
  18. package/dist/SvGridSelect.svelte.d.ts +6 -0
  19. package/dist/SvGroupCell.svelte +9 -2
  20. package/dist/SvKbd.svelte +1 -1
  21. package/dist/SvList.svelte +1 -1
  22. package/dist/SvMenubar.svelte +1 -1
  23. package/dist/SvMultiSelect.svelte +12 -4
  24. package/dist/SvMultiSelect.svelte.d.ts +6 -0
  25. package/dist/SvOtpInput.svelte +1 -1
  26. package/dist/SvPasswordInput.svelte +1 -1
  27. package/dist/SvProgress.svelte +1 -1
  28. package/dist/SvRating.svelte +1 -1
  29. package/dist/SvResult.svelte +1 -1
  30. package/dist/SvRichText.svelte +35 -16
  31. package/dist/SvRichText.svelte.d.ts +9 -0
  32. package/dist/SvSwitchButton.svelte +2 -1
  33. package/dist/SvToaster.svelte +1 -1
  34. package/dist/SvTour.svelte +4 -0
  35. package/dist/SvTree.svelte +9 -2
  36. package/dist/SvTree.svelte.d.ts +5 -0
  37. package/dist/SvTreeSelect.svelte +6 -4
  38. package/dist/build-api.js +16 -0
  39. package/dist/cdn/{GridMenus-Ds3OpzZT.js → GridMenus-B7XhhqdN.js} +34 -36
  40. package/dist/cdn/{GridMenus-Da_p7SKS.js → GridMenus-BxmIvCJh.js} +35 -37
  41. package/dist/cdn/{SvDateTimePicker-DB0aIEk8.js → SvDateTimePicker-ClqtVIj3.js} +2 -1
  42. package/dist/cdn/{SvDateTimePicker-B8GopjhC.js → SvDateTimePicker-o94wfSUa.js} +3 -2
  43. package/dist/cdn/{SvGridChart-BcLCYvw9.js → SvGridChart-Cr_90pPD.js} +1 -1
  44. package/dist/cdn/{SvGridChartPanel-DvjwxLvK.js → SvGridChartPanel-ICbxiUvK.js} +4 -4
  45. package/dist/cdn/{SvGridChartView-PwqI4s02.js → SvGridChartView-Bx42PMvf.js} +2 -2
  46. package/dist/cdn/{SvGridDropdown-hpnHvOGt.js → SvGridDropdown-CUOdm7YA.js} +93 -88
  47. package/dist/cdn/{SvGridDropdown-DjALc_4f.js → SvGridDropdown-D0VdjeR8.js} +129 -123
  48. package/dist/cdn/{disclose-version-DoD9AFD_.js → disclose-version-DljhZohK.js} +45 -45
  49. package/dist/cdn/{src-DYDCiC0D.js → src-erjBRYfX.js} +5952 -5615
  50. package/dist/cdn/{src-BLJfdyL0.js → src-ktbQVOk-.js} +4190 -3851
  51. package/dist/cdn/svgrid.js +9 -9
  52. package/dist/cdn/svgrid.svelte-external.js +8 -8
  53. package/dist/cell-render.d.ts +1 -0
  54. package/dist/cell-render.js +115 -1
  55. package/dist/cell-values.js +5 -0
  56. package/dist/chart.d.ts +2 -0
  57. package/dist/chart.js +1 -4
  58. package/dist/core.d.ts +80 -9
  59. package/dist/core.js +89 -0
  60. package/dist/createMenu.svelte.js +0 -1
  61. package/dist/createPopoverSelect.svelte.d.ts +2 -2
  62. package/dist/editing.js +1 -1
  63. package/dist/editor-contract.d.ts +12 -5
  64. package/dist/editor-registry.d.ts +18 -7
  65. package/dist/editor-registry.js +10 -1
  66. package/dist/export-format.js +3 -0
  67. package/dist/group-display.d.ts +39 -0
  68. package/dist/group-display.js +52 -0
  69. package/dist/index.d.ts +2 -2
  70. package/dist/index.js +2 -2
  71. package/dist/keyboard-handlers.js +16 -0
  72. package/dist/recurrence.js +3 -2
  73. package/dist/selection.js +0 -1
  74. package/dist/svgrid-wrapper.types.d.ts +8 -2
  75. package/dist/themes/index.d.ts +10 -0
  76. package/dist/themes/index.js +26 -0
  77. package/package.json +3 -2
  78. package/src/DockNodeView.svelte +1 -1
  79. package/src/GridFooter.svelte +0 -40
  80. package/src/GridMenus.svelte +0 -77
  81. package/src/SvAccordion.test.ts +5 -5
  82. package/src/SvAvatar.svelte +1 -1
  83. package/src/SvButtonGroup.test.ts +6 -6
  84. package/src/SvCalendar.test.ts +1 -1
  85. package/src/SvCheckBox.svelte +2 -1
  86. package/src/SvCircularProgress.svelte +1 -1
  87. package/src/SvDateRangeInput.svelte +1 -1
  88. package/src/SvFileUpload.test.ts +1 -1
  89. package/src/SvGrid.controller.svelte.ts +367 -22
  90. package/src/SvGrid.css +103 -3
  91. package/src/SvGrid.svelte +164 -35
  92. package/src/SvGrid.types.ts +81 -1
  93. package/src/SvGridChart.svelte +2 -10
  94. package/src/SvGridDropdown.svelte +38 -6
  95. package/src/SvGridSelect.svelte +12 -5
  96. package/src/SvGroupCell.svelte +9 -2
  97. package/src/SvKbd.svelte +1 -1
  98. package/src/SvList.svelte +1 -1
  99. package/src/SvMenubar.svelte +1 -1
  100. package/src/SvMultiSelect.svelte +12 -4
  101. package/src/SvOtpInput.svelte +1 -1
  102. package/src/SvPasswordInput.svelte +1 -1
  103. package/src/SvProgress.svelte +1 -1
  104. package/src/SvRating.svelte +1 -1
  105. package/src/SvResult.svelte +1 -1
  106. package/src/SvRichText.svelte +35 -16
  107. package/src/SvSwitchButton.svelte +2 -1
  108. package/src/SvToaster.svelte +1 -1
  109. package/src/SvTour.svelte +4 -0
  110. package/src/SvTree.svelte +9 -2
  111. package/src/SvTreeSelect.svelte +6 -4
  112. package/src/ai.test.ts +1 -1
  113. package/src/build-api.ts +15 -105
  114. package/src/builtin-editors.test.ts +3 -1
  115. package/src/cell-formatting.test.ts +1 -1
  116. package/src/cell-render.test.ts +27 -3
  117. package/src/cell-render.ts +122 -95
  118. package/src/cell-values.ts +4 -0
  119. package/src/chart-export.test.ts +2 -2
  120. package/src/chart.ts +1 -2
  121. package/src/clipboard.test.ts +0 -2
  122. package/src/clipboard.ts +0 -102
  123. package/src/columns.ts +0 -115
  124. package/src/core.ts +157 -8
  125. package/src/createMenu.svelte.ts +0 -1
  126. package/src/editing.test.ts +1 -1
  127. package/src/editing.ts +2 -105
  128. package/src/editor-contract.ts +12 -5
  129. package/src/editor-registry.grid.test.ts +48 -2
  130. package/src/editor-registry.test.ts +3 -1
  131. package/src/editor-registry.ts +23 -7
  132. package/src/export-format.ts +3 -0
  133. package/src/facet-buckets.test.ts +33 -33
  134. package/src/features.ts +0 -90
  135. package/src/group-display.test.ts +91 -1
  136. package/src/group-display.ts +83 -0
  137. package/src/index.ts +9 -0
  138. package/src/keyboard-handlers.coverage.test.ts +1 -1
  139. package/src/keyboard-handlers.ts +17 -120
  140. package/src/list-power.test.ts +2 -2
  141. package/src/menus.test.ts +4 -5
  142. package/src/menus.ts +0 -112
  143. package/src/named-views.coverage.test.ts +3 -3
  144. package/src/new-features.test.ts +3 -1
  145. package/src/recurrence.ts +3 -2
  146. package/src/row-drag.test.ts +1 -1
  147. package/src/row-resize.test.ts +41 -42
  148. package/src/scheduler-model.ts +1 -1
  149. package/src/scroll-sync.test.ts +4 -7
  150. package/src/scroll-sync.ts +0 -122
  151. package/src/selection.ts +0 -109
  152. package/src/server-data-source.coverage.test.ts +1 -1
  153. package/src/server-group-model.test.ts +8 -8
  154. package/src/spreadsheet.test.ts +0 -1
  155. package/src/summaries.ts +0 -112
  156. package/src/svgrid-wrapper.types.ts +11 -9
  157. package/src/svgrid.async-editor-options.test.ts +271 -0
  158. package/src/svgrid.auto-row-height.test.ts +204 -0
  159. package/src/svgrid.behavior.test.ts +0 -1
  160. package/src/svgrid.charting.test.ts +4 -4
  161. package/src/svgrid.column-reorder.test.ts +0 -8
  162. package/src/svgrid.comments-autocomplete.test.ts +8 -2
  163. package/src/svgrid.context-menu.test.ts +25 -9
  164. package/src/svgrid.filter-depth.test.ts +163 -0
  165. package/src/svgrid.filter-menu-listbox.svelte.test.ts +16 -2
  166. package/src/svgrid.filter-menu-scroll.test.ts +10 -2
  167. package/src/svgrid.grand-total.test.ts +188 -0
  168. package/src/svgrid.group-display-mode.test.ts +171 -0
  169. package/src/svgrid.group-footers.test.ts +121 -0
  170. package/src/svgrid.group-pagination.test.ts +153 -0
  171. package/src/svgrid.tree-data.test.ts +186 -0
  172. package/src/svgriddropdown.async-panel.svelte.test.ts +195 -0
  173. package/src/themes/index.ts +42 -0
  174. package/src/tree-edit.test.ts +4 -4
  175. package/src/tree-row-model.test.ts +168 -0
  176. package/src/ui-localization.test.ts +113 -0
  177. package/themes/ag-alpine.css +22 -0
  178. package/themes/antd.css +22 -0
  179. package/themes/atlassian.css +22 -0
  180. package/themes/bootstrap.css +22 -0
  181. package/themes/carbon.css +22 -0
  182. package/themes/catppuccin.css +22 -0
  183. package/themes/dracula.css +22 -0
  184. package/themes/ember.css +22 -0
  185. package/themes/excel.css +22 -0
  186. package/themes/fluent.css +22 -0
  187. package/themes/github.css +22 -0
  188. package/themes/linear.css +22 -0
  189. package/themes/material.css +22 -0
  190. package/themes/nord.css +22 -0
  191. package/themes/notion.css +22 -0
  192. package/themes/salesforce.css +22 -0
  193. package/themes/sap.css +22 -0
  194. package/themes/shadcn.css +22 -0
  195. package/themes/tailwind.css +22 -0
  196. package/themes/vercel.css +22 -0
@@ -9,16 +9,35 @@ import { createCellRender } from './cell-render'
9
9
  // shape that mirrors the @svgrid/grid runtime closely enough to exercise
10
10
  // every branch.
11
11
 
12
+ // Only `id` / `original` / `getCellValueByColumnId` are read by the functions
13
+ // under test, but the stub carries the full `Row` shape so it is assignable
14
+ // without a cast at every call site.
12
15
  type AnyRow = {
13
16
  id: string
17
+ index: number
14
18
  original: Record<string, unknown>
19
+ depth: number
20
+ getCanExpand: () => boolean
21
+ getIsExpanded: () => boolean
22
+ toggleExpanded: () => void
23
+ getIsSelected: () => boolean
24
+ toggleSelected: () => void
25
+ getAllCells: () => never[]
15
26
  getCellValueByColumnId: (colId: string) => unknown
16
27
  }
17
28
 
18
29
  function makeRow(id: string, data: Record<string, unknown>): AnyRow {
19
30
  return {
20
31
  id,
32
+ index: 0,
21
33
  original: data,
34
+ depth: 0,
35
+ getCanExpand: () => false,
36
+ getIsExpanded: () => false,
37
+ toggleExpanded: () => {},
38
+ getIsSelected: () => false,
39
+ toggleSelected: () => {},
40
+ getAllCells: () => [],
22
41
  getCellValueByColumnId: (colId: string) => data[colId],
23
42
  }
24
43
  }
@@ -34,6 +53,11 @@ function makeCtx(overrides: Partial<any> = {}): any {
34
53
  conditionalColumnStats: new Map(),
35
54
  noteOverrides: {},
36
55
  editorOptionsCache: {},
56
+ // Async `editorOptions` bookkeeping: resolved lists (reactive in the real
57
+ // controller) plus two plain Sets used as dedupe guards.
58
+ asyncEditorOptions: {} as Record<string, unknown>,
59
+ asyncEditorOptionsPending: new Set<string>(),
60
+ asyncEditorColumns: new Set<string>(),
37
61
  ...overrides,
38
62
  }
39
63
  }
@@ -172,7 +196,7 @@ describe('createCellRender / computeCellClass', () => {
172
196
  const row = makeRow('r', { score: 9 })
173
197
  expect(cr.computeCellClass(row, makeColumn('score', { cellClass }))).toBe('big')
174
198
  expect(cellClass).toHaveBeenCalledTimes(1)
175
- const passed = cellClass.mock.calls[0][0]
199
+ const passed = cellClass.mock.calls[0]![0]
176
200
  expect(passed.row).toBe(row)
177
201
  expect(passed.getValue()).toBe(9)
178
202
  })
@@ -423,7 +447,7 @@ describe('createCellRender / formatPinnedValue', () => {
423
447
  const formatter = vi.fn(({ value, row }: any) => `${value}:${row}`)
424
448
  const col = makeColumn('a', { formatter })
425
449
  expect(cr.formatPinnedValue(col, 5)).toBe('5:null')
426
- expect(formatter.mock.calls[0][0].row).toBeNull()
450
+ expect(formatter.mock.calls[0]![0].row).toBeNull()
427
451
  })
428
452
 
429
453
  it('masks password pinned values', () => {
@@ -478,7 +502,7 @@ describe('createCellRender / computePinnedCellClass', () => {
478
502
  field: 'a',
479
503
  })
480
504
  expect(cr.computePinnedCellClass({ a: 3 }, col)).toBe('pos')
481
- expect(cellClass.mock.calls[0][0].row).toBeNull()
505
+ expect(cellClass.mock.calls[0]![0].row).toBeNull()
482
506
  })
483
507
 
484
508
  it('coerces a falsy cellClass result to empty string', () => {
@@ -2,127 +2,33 @@
2
2
  // reading/writing controller state via the `ctx` handle; the reactive core
3
3
  // ($state/$derived/$effect) stays in the controller.
4
4
  import {
5
- applyExcelFilter,
6
- normalizeForFilter,
7
- createColumnVirtualizer,
8
- createCoreRowModel,
9
- createExpandedRowModel,
10
- createFilteredRowModel,
11
- createGroupedRowModel,
12
- createPaginatedRowModel,
13
- createSvelteVirtualizer,
14
- createSortedRowModel,
15
- createSvGrid,
16
- filterFns,
17
- getGridCellA11yProps,
18
- getGridCellDomId,
19
- getGridHeaderA11yProps,
20
- getGridRootA11yProps,
21
- getGridRowA11yProps,
22
- parseEditorValue,
23
5
  normalizeEditorOptions,
24
- sortFns,
25
- tableFeatures,
26
- rowSortingFeature,
27
- columnFilteringFeature,
28
- columnGroupingFeature,
29
- type CellContext,
30
- type EditorContext,
31
6
  type CellEditorOption,
32
- type CellEditorType,
33
7
  type CellFormatter,
34
8
  type CellFormatConfig,
35
9
  type Column,
36
- type ColumnDef,
37
10
  type Row,
38
11
  type RowData,
39
- type SvGridApi,
40
12
  type TableFeatures,
41
13
  } from "./index";
42
14
  import "./sv-grid-scrollbar";
43
- import type { Snippet } from "svelte";
44
- import { getKeyboardIntent, getNextActiveCell } from "./keyboard";
45
15
  import {
46
16
  formatNumericWithConfig,
47
17
  getDateFormatter,
48
18
  resolveDatePattern,
49
19
  } from "./cell-formatting";
50
- import {
51
- RenderSnippetConfig,
52
- RenderComponentConfig,
53
- } from "./render-component";
54
- import { buildFillPattern } from "./fill-patterns";
55
- import { buildSparkline, toSparklineValues } from "./sparkline";
56
20
  import {
57
21
  resolveCellFormat,
58
- computeColumnStat,
59
- formatsNeedingStats,
60
- type ColumnStat,
61
22
  type ResolvedCellFormat,
62
23
  } from "./conditional-formatting";
63
- import SvGridDropdown from "./SvGridDropdown.svelte";
64
- import type {
65
- Props,
66
- SelectionPoint,
67
- SelectionRange,
68
- CellEditState,
69
- FilterOperator,
70
- FilterOption,
71
- MenuPosition,
72
- } from "./SvGrid.types";
73
24
  import {
74
- cfTextStyle,
75
- fmtStat,
76
25
  getCellKey,
77
26
  resolveClassList,
78
- toDateInputValue,
79
- toDateTimeLocalInputValue,
80
- getEditableInputValue,
81
- getEditorInputType,
82
- toValueArray,
83
27
  getOptionLabel,
84
- getOptionColor,
85
- colorfulChipStyle,
86
- getEditorClass,
87
28
  asDate,
88
- clampMenuX,
89
- cssEscape,
90
- rawToNumber,
91
- formatFacetNumber,
92
- formatFacetDate,
93
29
  } from "./SvGrid.helpers";
94
- import { createEditing } from "./editing";
95
- import { createSelection } from "./selection";
96
- import { createColumns } from "./columns";
97
- import { createGridApi } from "./build-api";
98
- import { createClipboard } from "./clipboard";
99
- import {
100
- filterOperatorOptions,
101
- fallbackOperatorOption,
102
- TEXT_OPERATORS,
103
- NUMBER_OPERATORS,
104
- DATE_OPERATORS,
105
- CHECKBOX_OPERATORS,
106
- operatorOption,
107
- operatorsForColumn,
108
- defaultOperatorFor,
109
- operatorLabelFor,
110
- } from "./filter-operators";
111
30
  import {
112
- type FacetBucket,
113
- isBucketableColumn,
114
- buildBuckets,
115
- isInBucket,
116
- } from "./facet-buckets";
117
- import {
118
- getColumnBaseValue,
119
- isGroupRow,
120
- toolPanelHeaderLabel,
121
- formatSummaryNumeric,
122
- getColumnAlign,
123
31
  getPinnedCellValue,
124
- getColumnAccessorValue,
125
- columnDefMatchesId,
126
32
  } from "./cell-values";
127
33
 
128
34
  export function createCellRender<
@@ -263,6 +169,77 @@ export function createCellRender<
263
169
  return v && v.trim() ? v : null
264
170
  }
265
171
 
172
+ const isThenable = (v: unknown): v is Promise<unknown> =>
173
+ typeof (v as { then?: unknown } | null)?.then === "function";
174
+
175
+ /**
176
+ * Resolve a promised option list once per key, then keep it.
177
+ *
178
+ * Render paths call `getColumnEditorOptions` synchronously and often, so an
179
+ * async source cannot block: the first call starts the fetch and returns an
180
+ * empty list, and the resolved value lands in reactive state which re-renders
181
+ * the editor. `asyncEditorOptionsPending` tracks in-flight keys so repeated
182
+ * renders do not fire the request again.
183
+ */
184
+ function resolveAsyncOptions(
185
+ key: string,
186
+ promise: Promise<unknown>,
187
+ /** Drop other cached entries starting with this prefix when the result
188
+ * lands - used to evict a cascade's superseded signatures. */
189
+ prunePrefix?: string,
190
+ ): CellEditorOption[] {
191
+ const done = ctx.asyncEditorOptions[key];
192
+ if (done) return done;
193
+ const pending: Set<string> = ctx.asyncEditorOptionsPending;
194
+ if (!pending.has(key)) {
195
+ // Plain Set, not reactive state: this runs inside the template, and a
196
+ // $state write during render is a Svelte 5 error.
197
+ pending.add(key);
198
+ const store = (value: CellEditorOption[]) => {
199
+ const next: Record<string, CellEditorOption[]> = {};
200
+ for (const [k, v] of Object.entries(ctx.asyncEditorOptions as Record<string, CellEditorOption[]>)) {
201
+ // Keep everything except this cell's stale signatures.
202
+ if (prunePrefix && k !== key && k.startsWith(prunePrefix)) continue;
203
+ next[k] = v;
204
+ }
205
+ next[key] = value;
206
+ ctx.asyncEditorOptions = next;
207
+ };
208
+ promise
209
+ .then((value) => store(normalizeEditorOptions(value as never)))
210
+ // A failed lookup settles on "no options" rather than leaving the
211
+ // editor spinning forever.
212
+ .catch(() => store([]))
213
+ .finally(() => {
214
+ pending.delete(key);
215
+ });
216
+ }
217
+ return [];
218
+ }
219
+
220
+ /**
221
+ * Cache key for a per-row async source.
222
+ *
223
+ * A cascade depends on the row's OTHER cells - City is a function of Country -
224
+ * so keying by row id alone made the first result permanent: changing the
225
+ * country left the old city list cached forever. The row's own values are
226
+ * folded into the key, so editing the row naturally supersedes the entry.
227
+ *
228
+ * Only reached for columns already known to be async, so the stringify cost
229
+ * is bounded by the rows actually rendering such a column.
230
+ */
231
+ function asyncRowKey(column: Column<TData>, row: Row<TData>): string {
232
+ let signature: string;
233
+ try {
234
+ signature = JSON.stringify(row.original);
235
+ } catch {
236
+ // Circular / non-serializable row: fall back to identity, which at least
237
+ // invalidates when the consumer replaces the object immutably.
238
+ signature = String(row.index);
239
+ }
240
+ return `${column.id}::${row.id}::${signature}`;
241
+ }
242
+
266
243
  function getColumnEditorOptions(
267
244
  column: Column<TData>,
268
245
  row?: Row<TData> | null,
@@ -272,8 +249,36 @@ export function createCellRender<
272
249
  // Dynamic per-row: must be re-evaluated because the row's other
273
250
  // cells may have just changed (cascade).
274
251
  if (!row?.original) return [];
275
- return normalizeEditorOptions(def(row.original as TData));
252
+ // Once a column is known to hand back promises, answer from the cache
253
+ // WITHOUT calling the source again. Render reads options constantly, so
254
+ // calling an async source every time fires a real request per render -
255
+ // and any state the source touches is being mutated mid-render, which
256
+ // Svelte rejects outright. The key folds in the row's values, so editing
257
+ // the row supersedes the entry and the cascade refetches.
258
+ const known = (ctx.asyncEditorColumns as Set<string>).has(column.id);
259
+ if (known) {
260
+ const key = asyncRowKey(column, row);
261
+ const done = ctx.asyncEditorOptions[key];
262
+ if (done) return done;
263
+ if ((ctx.asyncEditorOptionsPending as Set<string>).has(key)) return [];
264
+ const produced = def(row.original as TData);
265
+ if (isThenable(produced)) {
266
+ return resolveAsyncOptions(key, produced, `${column.id}::${row.id}::`);
267
+ }
268
+ return normalizeEditorOptions(produced);
269
+ }
270
+ const produced = def(row.original as TData);
271
+ if (isThenable(produced)) {
272
+ (ctx.asyncEditorColumns as Set<string>).add(column.id);
273
+ return resolveAsyncOptions(
274
+ asyncRowKey(column, row),
275
+ produced,
276
+ `${column.id}::${row.id}::`,
277
+ );
278
+ }
279
+ return normalizeEditorOptions(produced);
276
280
  }
281
+ if (isThenable(def)) return resolveAsyncOptions(column.id, def);
277
282
  const id = column.id;
278
283
  if (
279
284
  !ctx.editorOptionsCache[id] ||
@@ -285,6 +290,27 @@ export function createCellRender<
285
290
  return ctx.editorOptionsCache[id];
286
291
  }
287
292
 
293
+ /**
294
+ * True while a column's (or cell's) async option list is still loading.
295
+ * Derived from "the source is async and no result has landed" rather than
296
+ * from the pending Set, so it stays reactive - `asyncEditorOptions` is the
297
+ * reactive half, the Set is only a dedupe guard.
298
+ */
299
+ function areEditorOptionsLoading(
300
+ column: Column<TData>,
301
+ row?: Row<TData> | null,
302
+ ): boolean {
303
+ const def = column.columnDef.editorOptions;
304
+ if (typeof def === "function") {
305
+ if (!row?.original) return false;
306
+ // Never call the source here - that would fire another request just to
307
+ // answer "are we loading?". The resolver records which columns are async.
308
+ if (!(ctx.asyncEditorColumns as Set<string>).has(column.id)) return false;
309
+ return !ctx.asyncEditorOptions[asyncRowKey(column, row)];
310
+ }
311
+ return isThenable(def) && !ctx.asyncEditorOptions[column.id];
312
+ }
313
+
288
314
  /** Joined display string for list/chips cells. */
289
315
  function formatListCellValue(
290
316
  column: Column<TData>,
@@ -461,6 +487,7 @@ export function createCellRender<
461
487
  computeCellValidity,
462
488
  computeCellNote,
463
489
  getColumnEditorOptions,
490
+ areEditorOptionsLoading,
464
491
  formatListCellValue,
465
492
  formatCellValue,
466
493
  formatPinnedValue,
@@ -19,6 +19,10 @@ export function getColumnBaseValue<TData extends RowData>(row: Row<TData>, colum
19
19
  }
20
20
 
21
21
  export function isGroupRow<TData extends RowData>(row: Row<TData>) {
22
+ // Tree rows are expandable too, but they are real data rows - rendering one as
23
+ // a full-width banner would swallow its cells. Only grouping's synthetic rows
24
+ // are banners.
25
+ if ((row as { __treeRow?: boolean }).__treeRow) return false;
22
26
  return typeof row.getCanExpand === "function" && row.getCanExpand();
23
27
  }
24
28
 
@@ -1,4 +1,4 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
1
+ import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from 'vitest'
2
2
  import {
3
3
  chartToSvgString,
4
4
  chartToPngBlob,
@@ -125,7 +125,7 @@ describe('chartToSvgString', () => {
125
125
  describe('downloadChartSvg', () => {
126
126
  let createSpy: ReturnType<typeof vi.spyOn>
127
127
  let revokeSpy: ReturnType<typeof vi.spyOn>
128
- let clickSpy: ReturnType<typeof vi.fn>
128
+ let clickSpy: Mock<() => void>
129
129
 
130
130
  beforeEach(() => {
131
131
  vi.useFakeTimers()
package/src/chart.ts CHANGED
@@ -38,7 +38,6 @@ export type SeriesOverlay = 'linear' | `sma:${number}` | `ema:${number}`
38
38
  export type SeriesPattern = 'solid' | 'stripe' | 'crosshatch' | 'dots' | 'diagonal'
39
39
  /** Cycle used when `ChartSpec.patternFallback` is true and a series has no
40
40
  * explicit `pattern` set. Skips `'solid'` so every series gets a texture. */
41
- const PATTERN_CYCLE: SeriesPattern[] = ['stripe', 'crosshatch', 'dots', 'diagonal']
42
41
 
43
42
  export type ChartSeries = {
44
43
  label: string
@@ -2254,7 +2253,7 @@ export function rowsToChartSpec<T extends Record<string, unknown>>(
2254
2253
  entries.reduce((sum, e) => sum + (Number.isFinite(e.values[i]!) ? e.values[i]! : 0), 0),
2255
2254
  )
2256
2255
  const sort = opts.sort ?? (opts.topN ? 'value-desc' : 'none')
2257
- let order = categories.map((_, i) => i)
2256
+ const order = categories.map((_, i) => i)
2258
2257
  if (sort === 'value-desc') order.sort((a, b) => totals[b]! - totals[a]!)
2259
2258
  else if (sort === 'value-asc') order.sort((a, b) => totals[a]! - totals[b]!)
2260
2259
  else if (sort === 'category') order.sort((a, b) => categories[a]!.localeCompare(categories[b]!))
@@ -614,7 +614,6 @@ describe('copySelectionToClipboard', () => {
614
614
  configurable: true,
615
615
  })
616
616
  const execCommand = vi.fn().mockReturnValue(true)
617
- // @ts-expect-error - jsdom lacks execCommand by default
618
617
  document.execCommand = execCommand
619
618
  const ctx = makeCtx()
620
619
  ctx.selectionRange = {
@@ -631,7 +630,6 @@ describe('copySelectionToClipboard', () => {
631
630
  value: undefined,
632
631
  configurable: true,
633
632
  })
634
- // @ts-expect-error - override execCommand to throw
635
633
  document.execCommand = () => {
636
634
  throw new Error('blocked')
637
635
  }
package/src/clipboard.ts CHANGED
@@ -2,122 +2,20 @@
2
2
  // that read/write controller state via the `ctx` handle; the reactive core
3
3
  // ($state/$derived/$effect) stays in the controller.
4
4
  import {
5
- applyExcelFilter,
6
- normalizeForFilter,
7
- createColumnVirtualizer,
8
- createCoreRowModel,
9
- createExpandedRowModel,
10
- createFilteredRowModel,
11
- createGroupedRowModel,
12
- createPaginatedRowModel,
13
- createSvelteVirtualizer,
14
- createSortedRowModel,
15
- createSvGrid,
16
- filterFns,
17
- getGridCellA11yProps,
18
- getGridCellDomId,
19
- getGridHeaderA11yProps,
20
- getGridRootA11yProps,
21
- getGridRowA11yProps,
22
5
  parseEditorValue,
23
- normalizeEditorOptions,
24
- sortFns,
25
- tableFeatures,
26
- rowSortingFeature,
27
- columnFilteringFeature,
28
- columnGroupingFeature,
29
- type CellContext,
30
- type EditorContext,
31
- type CellEditorOption,
32
6
  type CellEditorType,
33
- type CellFormatter,
34
- type CellFormatConfig,
35
- type Column,
36
- type ColumnDef,
37
- type Row,
38
7
  type RowData,
39
- type SvGridApi,
40
8
  type TableFeatures,
41
9
  } from "./index";
42
10
  import "./sv-grid-scrollbar";
43
- import type { Snippet } from "svelte";
44
- import { getKeyboardIntent, getNextActiveCell } from "./keyboard";
45
- import {
46
- formatNumericWithConfig,
47
- getDateFormatter,
48
- resolveDatePattern,
49
- } from "./cell-formatting";
50
- import {
51
- RenderSnippetConfig,
52
- RenderComponentConfig,
53
- } from "./render-component";
54
11
  import { buildFillPattern } from "./fill-patterns";
55
- import { buildSparkline, toSparklineValues } from "./sparkline";
56
- import {
57
- resolveCellFormat,
58
- computeColumnStat,
59
- formatsNeedingStats,
60
- type ColumnStat,
61
- type ResolvedCellFormat,
62
- } from "./conditional-formatting";
63
- import SvGridDropdown from "./SvGridDropdown.svelte";
64
- import type {
65
- Props,
66
- SelectionPoint,
67
- SelectionRange,
68
- CellEditState,
69
- FilterOperator,
70
- FilterOption,
71
- MenuPosition,
72
- } from "./SvGrid.types";
73
12
  import {
74
- cfTextStyle,
75
- fmtStat,
76
13
  getCellKey,
77
- resolveClassList,
78
- toDateInputValue,
79
- toDateTimeLocalInputValue,
80
- getEditableInputValue,
81
- getEditorInputType,
82
- toValueArray,
83
- getOptionLabel,
84
- getOptionColor,
85
- colorfulChipStyle,
86
- getEditorClass,
87
- asDate,
88
- clampMenuX,
89
- cssEscape,
90
- rawToNumber,
91
- formatFacetNumber,
92
- formatFacetDate,
93
14
  } from "./SvGrid.helpers";
94
- import {
95
- filterOperatorOptions,
96
- fallbackOperatorOption,
97
- TEXT_OPERATORS,
98
- NUMBER_OPERATORS,
99
- DATE_OPERATORS,
100
- CHECKBOX_OPERATORS,
101
- operatorOption,
102
- operatorsForColumn,
103
- defaultOperatorFor,
104
- operatorLabelFor,
105
- } from "./filter-operators";
106
- import {
107
- type FacetBucket,
108
- isBucketableColumn,
109
- buildBuckets,
110
- isInBucket,
111
- } from "./facet-buckets";
112
15
  import {
113
16
  getColumnBaseValue,
114
17
  isGroupRow,
115
18
  toolPanelHeaderLabel,
116
- formatSummaryNumeric,
117
- getColumnAlign,
118
- getPinnedCellValue,
119
- getColumnAccessorValue,
120
- columnDefMatchesId,
121
19
  } from "./cell-values";
122
20
 
123
21
  export function createClipboard<
package/src/columns.ts CHANGED
@@ -2,125 +2,10 @@
2
2
  // reading/writing controller state via the `ctx` handle; the reactive core
3
3
  // ($state/$derived/$effect) stays in the controller.
4
4
  import {
5
- applyExcelFilter,
6
- normalizeForFilter,
7
- createColumnVirtualizer,
8
- createCoreRowModel,
9
- createExpandedRowModel,
10
- createFilteredRowModel,
11
- createGroupedRowModel,
12
- createPaginatedRowModel,
13
- createSvelteVirtualizer,
14
- createSortedRowModel,
15
- createSvGrid,
16
- filterFns,
17
- getGridCellA11yProps,
18
- getGridCellDomId,
19
- getGridHeaderA11yProps,
20
- getGridRootA11yProps,
21
- getGridRowA11yProps,
22
- parseEditorValue,
23
- normalizeEditorOptions,
24
- sortFns,
25
- tableFeatures,
26
- rowSortingFeature,
27
- columnFilteringFeature,
28
- columnGroupingFeature,
29
- type CellContext,
30
- type EditorContext,
31
- type CellEditorOption,
32
- type CellEditorType,
33
- type CellFormatter,
34
- type CellFormatConfig,
35
- type Column,
36
- type ColumnDef,
37
- type Row,
38
5
  type RowData,
39
- type SvGridApi,
40
6
  type TableFeatures,
41
7
  } from "./index";
42
8
  import "./sv-grid-scrollbar";
43
- import type { Snippet } from "svelte";
44
- import { getKeyboardIntent, getNextActiveCell } from "./keyboard";
45
- import {
46
- formatNumericWithConfig,
47
- getDateFormatter,
48
- resolveDatePattern,
49
- } from "./cell-formatting";
50
- import {
51
- RenderSnippetConfig,
52
- RenderComponentConfig,
53
- } from "./render-component";
54
- import { buildFillPattern } from "./fill-patterns";
55
- import { buildSparkline, toSparklineValues } from "./sparkline";
56
- import {
57
- resolveCellFormat,
58
- computeColumnStat,
59
- formatsNeedingStats,
60
- type ColumnStat,
61
- type ResolvedCellFormat,
62
- } from "./conditional-formatting";
63
- import SvGridDropdown from "./SvGridDropdown.svelte";
64
- import type {
65
- Props,
66
- SelectionPoint,
67
- SelectionRange,
68
- CellEditState,
69
- FilterOperator,
70
- FilterOption,
71
- MenuPosition,
72
- } from "./SvGrid.types";
73
- import {
74
- cfTextStyle,
75
- fmtStat,
76
- getCellKey,
77
- resolveClassList,
78
- toDateInputValue,
79
- toDateTimeLocalInputValue,
80
- getEditableInputValue,
81
- getEditorInputType,
82
- toValueArray,
83
- getOptionLabel,
84
- getOptionColor,
85
- colorfulChipStyle,
86
- getEditorClass,
87
- asDate,
88
- clampMenuX,
89
- cssEscape,
90
- rawToNumber,
91
- formatFacetNumber,
92
- formatFacetDate,
93
- } from "./SvGrid.helpers";
94
- import { createGridApi } from "./build-api";
95
- import { createClipboard } from "./clipboard";
96
- import {
97
- filterOperatorOptions,
98
- fallbackOperatorOption,
99
- TEXT_OPERATORS,
100
- NUMBER_OPERATORS,
101
- DATE_OPERATORS,
102
- CHECKBOX_OPERATORS,
103
- operatorOption,
104
- operatorsForColumn,
105
- defaultOperatorFor,
106
- operatorLabelFor,
107
- } from "./filter-operators";
108
- import {
109
- type FacetBucket,
110
- isBucketableColumn,
111
- buildBuckets,
112
- isInBucket,
113
- } from "./facet-buckets";
114
- import {
115
- getColumnBaseValue,
116
- isGroupRow,
117
- toolPanelHeaderLabel,
118
- formatSummaryNumeric,
119
- getColumnAlign,
120
- getPinnedCellValue,
121
- getColumnAccessorValue,
122
- columnDefMatchesId,
123
- } from "./cell-values";
124
9
 
125
10
  export function createColumns<
126
11
  TFeatures extends TableFeatures = TableFeatures,