@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.
- package/README.md +1 -1
- package/dist/DockNodeView.svelte +1 -1
- package/dist/GridFooter.svelte +0 -40
- package/dist/GridMenus.svelte +0 -77
- package/dist/SvAvatar.svelte +1 -1
- package/dist/SvCheckBox.svelte +2 -1
- package/dist/SvCircularProgress.svelte +1 -1
- package/dist/SvDateRangeInput.svelte +1 -1
- package/dist/SvGrid.controller.svelte.d.ts +20 -0
- package/dist/SvGrid.controller.svelte.js +358 -18
- package/dist/SvGrid.css +103 -3
- package/dist/SvGrid.svelte +164 -35
- package/dist/SvGrid.types.d.ts +81 -0
- package/dist/SvGridChart.svelte +2 -10
- package/dist/SvGridDropdown.svelte +38 -6
- package/dist/SvGridDropdown.svelte.d.ts +4 -0
- package/dist/SvGridSelect.svelte +12 -5
- package/dist/SvGridSelect.svelte.d.ts +6 -0
- package/dist/SvGroupCell.svelte +9 -2
- package/dist/SvKbd.svelte +1 -1
- package/dist/SvList.svelte +1 -1
- package/dist/SvMenubar.svelte +1 -1
- package/dist/SvMultiSelect.svelte +12 -4
- package/dist/SvMultiSelect.svelte.d.ts +6 -0
- package/dist/SvOtpInput.svelte +1 -1
- package/dist/SvPasswordInput.svelte +1 -1
- package/dist/SvProgress.svelte +1 -1
- package/dist/SvRating.svelte +1 -1
- package/dist/SvResult.svelte +1 -1
- package/dist/SvRichText.svelte +35 -16
- package/dist/SvRichText.svelte.d.ts +9 -0
- package/dist/SvSwitchButton.svelte +2 -1
- package/dist/SvToaster.svelte +1 -1
- package/dist/SvTour.svelte +4 -0
- package/dist/SvTree.svelte +9 -2
- package/dist/SvTree.svelte.d.ts +5 -0
- package/dist/SvTreeSelect.svelte +6 -4
- package/dist/build-api.js +16 -0
- package/dist/cdn/{GridMenus-Ds3OpzZT.js → GridMenus-B7XhhqdN.js} +34 -36
- package/dist/cdn/{GridMenus-Da_p7SKS.js → GridMenus-BxmIvCJh.js} +35 -37
- package/dist/cdn/{SvDateTimePicker-DB0aIEk8.js → SvDateTimePicker-ClqtVIj3.js} +2 -1
- package/dist/cdn/{SvDateTimePicker-B8GopjhC.js → SvDateTimePicker-o94wfSUa.js} +3 -2
- package/dist/cdn/{SvGridChart-BcLCYvw9.js → SvGridChart-Cr_90pPD.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-DvjwxLvK.js → SvGridChartPanel-ICbxiUvK.js} +4 -4
- package/dist/cdn/{SvGridChartView-PwqI4s02.js → SvGridChartView-Bx42PMvf.js} +2 -2
- package/dist/cdn/{SvGridDropdown-hpnHvOGt.js → SvGridDropdown-CUOdm7YA.js} +93 -88
- package/dist/cdn/{SvGridDropdown-DjALc_4f.js → SvGridDropdown-D0VdjeR8.js} +129 -123
- package/dist/cdn/{disclose-version-DoD9AFD_.js → disclose-version-DljhZohK.js} +45 -45
- package/dist/cdn/{src-DYDCiC0D.js → src-erjBRYfX.js} +5952 -5615
- package/dist/cdn/{src-BLJfdyL0.js → src-ktbQVOk-.js} +4190 -3851
- package/dist/cdn/svgrid.js +9 -9
- package/dist/cdn/svgrid.svelte-external.js +8 -8
- package/dist/cell-render.d.ts +1 -0
- package/dist/cell-render.js +115 -1
- package/dist/cell-values.js +5 -0
- package/dist/chart.d.ts +2 -0
- package/dist/chart.js +1 -4
- package/dist/core.d.ts +80 -9
- package/dist/core.js +89 -0
- package/dist/createMenu.svelte.js +0 -1
- package/dist/createPopoverSelect.svelte.d.ts +2 -2
- package/dist/editing.js +1 -1
- package/dist/editor-contract.d.ts +12 -5
- package/dist/editor-registry.d.ts +18 -7
- package/dist/editor-registry.js +10 -1
- package/dist/export-format.js +3 -0
- package/dist/group-display.d.ts +39 -0
- package/dist/group-display.js +52 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/keyboard-handlers.js +16 -0
- package/dist/recurrence.js +3 -2
- package/dist/selection.js +0 -1
- package/dist/svgrid-wrapper.types.d.ts +8 -2
- package/dist/themes/index.d.ts +10 -0
- package/dist/themes/index.js +26 -0
- package/package.json +3 -2
- package/src/DockNodeView.svelte +1 -1
- package/src/GridFooter.svelte +0 -40
- package/src/GridMenus.svelte +0 -77
- package/src/SvAccordion.test.ts +5 -5
- package/src/SvAvatar.svelte +1 -1
- package/src/SvButtonGroup.test.ts +6 -6
- package/src/SvCalendar.test.ts +1 -1
- package/src/SvCheckBox.svelte +2 -1
- package/src/SvCircularProgress.svelte +1 -1
- package/src/SvDateRangeInput.svelte +1 -1
- package/src/SvFileUpload.test.ts +1 -1
- package/src/SvGrid.controller.svelte.ts +367 -22
- package/src/SvGrid.css +103 -3
- package/src/SvGrid.svelte +164 -35
- package/src/SvGrid.types.ts +81 -1
- package/src/SvGridChart.svelte +2 -10
- package/src/SvGridDropdown.svelte +38 -6
- package/src/SvGridSelect.svelte +12 -5
- package/src/SvGroupCell.svelte +9 -2
- package/src/SvKbd.svelte +1 -1
- package/src/SvList.svelte +1 -1
- package/src/SvMenubar.svelte +1 -1
- package/src/SvMultiSelect.svelte +12 -4
- package/src/SvOtpInput.svelte +1 -1
- package/src/SvPasswordInput.svelte +1 -1
- package/src/SvProgress.svelte +1 -1
- package/src/SvRating.svelte +1 -1
- package/src/SvResult.svelte +1 -1
- package/src/SvRichText.svelte +35 -16
- package/src/SvSwitchButton.svelte +2 -1
- package/src/SvToaster.svelte +1 -1
- package/src/SvTour.svelte +4 -0
- package/src/SvTree.svelte +9 -2
- package/src/SvTreeSelect.svelte +6 -4
- package/src/ai.test.ts +1 -1
- package/src/build-api.ts +15 -105
- package/src/builtin-editors.test.ts +3 -1
- package/src/cell-formatting.test.ts +1 -1
- package/src/cell-render.test.ts +27 -3
- package/src/cell-render.ts +122 -95
- package/src/cell-values.ts +4 -0
- package/src/chart-export.test.ts +2 -2
- package/src/chart.ts +1 -2
- package/src/clipboard.test.ts +0 -2
- package/src/clipboard.ts +0 -102
- package/src/columns.ts +0 -115
- package/src/core.ts +157 -8
- package/src/createMenu.svelte.ts +0 -1
- package/src/editing.test.ts +1 -1
- package/src/editing.ts +2 -105
- package/src/editor-contract.ts +12 -5
- package/src/editor-registry.grid.test.ts +48 -2
- package/src/editor-registry.test.ts +3 -1
- package/src/editor-registry.ts +23 -7
- package/src/export-format.ts +3 -0
- package/src/facet-buckets.test.ts +33 -33
- package/src/features.ts +0 -90
- package/src/group-display.test.ts +91 -1
- package/src/group-display.ts +83 -0
- package/src/index.ts +9 -0
- package/src/keyboard-handlers.coverage.test.ts +1 -1
- package/src/keyboard-handlers.ts +17 -120
- package/src/list-power.test.ts +2 -2
- package/src/menus.test.ts +4 -5
- package/src/menus.ts +0 -112
- package/src/named-views.coverage.test.ts +3 -3
- package/src/new-features.test.ts +3 -1
- package/src/recurrence.ts +3 -2
- package/src/row-drag.test.ts +1 -1
- package/src/row-resize.test.ts +41 -42
- package/src/scheduler-model.ts +1 -1
- package/src/scroll-sync.test.ts +4 -7
- package/src/scroll-sync.ts +0 -122
- package/src/selection.ts +0 -109
- package/src/server-data-source.coverage.test.ts +1 -1
- package/src/server-group-model.test.ts +8 -8
- package/src/spreadsheet.test.ts +0 -1
- package/src/summaries.ts +0 -112
- package/src/svgrid-wrapper.types.ts +11 -9
- package/src/svgrid.async-editor-options.test.ts +271 -0
- package/src/svgrid.auto-row-height.test.ts +204 -0
- package/src/svgrid.behavior.test.ts +0 -1
- package/src/svgrid.charting.test.ts +4 -4
- package/src/svgrid.column-reorder.test.ts +0 -8
- package/src/svgrid.comments-autocomplete.test.ts +8 -2
- package/src/svgrid.context-menu.test.ts +25 -9
- package/src/svgrid.filter-depth.test.ts +163 -0
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +16 -2
- package/src/svgrid.filter-menu-scroll.test.ts +10 -2
- package/src/svgrid.grand-total.test.ts +188 -0
- package/src/svgrid.group-display-mode.test.ts +171 -0
- package/src/svgrid.group-footers.test.ts +121 -0
- package/src/svgrid.group-pagination.test.ts +153 -0
- package/src/svgrid.tree-data.test.ts +186 -0
- package/src/svgriddropdown.async-panel.svelte.test.ts +195 -0
- package/src/themes/index.ts +42 -0
- package/src/tree-edit.test.ts +4 -4
- package/src/tree-row-model.test.ts +168 -0
- package/src/ui-localization.test.ts +113 -0
- package/themes/ag-alpine.css +22 -0
- package/themes/antd.css +22 -0
- package/themes/atlassian.css +22 -0
- package/themes/bootstrap.css +22 -0
- package/themes/carbon.css +22 -0
- package/themes/catppuccin.css +22 -0
- package/themes/dracula.css +22 -0
- package/themes/ember.css +22 -0
- package/themes/excel.css +22 -0
- package/themes/fluent.css +22 -0
- package/themes/github.css +22 -0
- package/themes/linear.css +22 -0
- package/themes/material.css +22 -0
- package/themes/nord.css +22 -0
- package/themes/notion.css +22 -0
- package/themes/salesforce.css +22 -0
- package/themes/sap.css +22 -0
- package/themes/shadcn.css +22 -0
- package/themes/tailwind.css +22 -0
- package/themes/vercel.css +22 -0
package/src/core.ts
CHANGED
|
@@ -45,6 +45,11 @@ export type CellSpanParams<TData extends RowData = RowData> = {
|
|
|
45
45
|
value: unknown
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/** The raw option list a column's `editorOptions` can supply. */
|
|
49
|
+
export type EditorOptionSource = ReadonlyArray<
|
|
50
|
+
string | number | { value: string | number; label?: string; color?: string }
|
|
51
|
+
>
|
|
52
|
+
|
|
48
53
|
/** Params passed to a column's `valueParser(...)` on edit commit. */
|
|
49
54
|
export type ValueParserParams<TData extends RowData = RowData> = {
|
|
50
55
|
/** The value after built-in per-`editorType` coercion. */
|
|
@@ -172,6 +177,16 @@ export function applyGroupAggregate<TData extends RowData>(
|
|
|
172
177
|
}
|
|
173
178
|
}
|
|
174
179
|
|
|
180
|
+
/**
|
|
181
|
+
* A column definition.
|
|
182
|
+
*
|
|
183
|
+
* `TFeatures` is a phantom parameter - it is threaded through nested
|
|
184
|
+
* `columns` groups but no member depends on it, so `{}`, `TableFeatures` and
|
|
185
|
+
* `typeof features` are all interchangeable here. It is deliberately left
|
|
186
|
+
* WITHOUT a default: `ColumnDef<Row>` would otherwise bind `Row` to this slot
|
|
187
|
+
* and silently type your data as `RowData`, losing every field-name check.
|
|
188
|
+
* Prefer {@link GridColumns} / {@link GridColumnDef} for the common case.
|
|
189
|
+
*/
|
|
175
190
|
export type ColumnDef<TFeatures extends TableFeatures, TData extends RowData> = {
|
|
176
191
|
id?: string
|
|
177
192
|
field?: keyof TData & string
|
|
@@ -336,16 +351,20 @@ export type ColumnDef<TFeatures extends TableFeatures, TData extends RowData> =
|
|
|
336
351
|
*
|
|
337
352
|
* Pass a function `(row) => options` for row-dependent (cascading)
|
|
338
353
|
* options - e.g. City options that depend on Country in the same row.
|
|
354
|
+
*
|
|
355
|
+
* Either form may return a **Promise**, for options that come from the
|
|
356
|
+
* server. While it resolves, the editor shows a loading state and the cell
|
|
357
|
+
* renders its raw value.
|
|
358
|
+
*
|
|
359
|
+
* Results are cached so reopening an editor does not refetch: a static source
|
|
360
|
+
* per column, a per-row source per row AND per that row's data - so a cascade
|
|
361
|
+
* reloads by itself when the cell it depends on is edited. Call
|
|
362
|
+
* `api.refreshEditorOptions(columnId?)` when the list changes server-side.
|
|
339
363
|
*/
|
|
340
364
|
editorOptions?:
|
|
341
|
-
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
| ((
|
|
345
|
-
row: TData,
|
|
346
|
-
) => ReadonlyArray<
|
|
347
|
-
string | number | { value: string | number; label?: string; color?: string }
|
|
348
|
-
>)
|
|
365
|
+
| EditorOptionSource
|
|
366
|
+
| Promise<EditorOptionSource>
|
|
367
|
+
| ((row: TData) => EditorOptionSource | Promise<EditorOptionSource>)
|
|
349
368
|
/** When true, list/chips allow multiple selections. Cell value becomes an array. */
|
|
350
369
|
editorMultiple?: boolean
|
|
351
370
|
/** Separator used when joining array values for the readonly cell display. Defaults to ', '. */
|
|
@@ -409,6 +428,23 @@ export type ColumnDef<TFeatures extends TableFeatures, TData extends RowData> =
|
|
|
409
428
|
| ((ctx: CellContext<TData>) => string | ReadonlyArray<string> | Record<string, boolean> | undefined | null)
|
|
410
429
|
}
|
|
411
430
|
|
|
431
|
+
/**
|
|
432
|
+
* A column definition keyed only by your row type - the ergonomic form of
|
|
433
|
+
* {@link ColumnDef}, whose first parameter is a phantom feature bag that is
|
|
434
|
+
* almost always `{}`.
|
|
435
|
+
*
|
|
436
|
+
* ```ts
|
|
437
|
+
* const columns: GridColumns<Person> = [{ field: 'firstName', header: 'Name' }]
|
|
438
|
+
* ```
|
|
439
|
+
*
|
|
440
|
+
* Interchangeable with `ColumnDef<{}, TData>` and `ColumnDef<typeof features,
|
|
441
|
+
* TData>` in both directions, so it mixes freely with existing code.
|
|
442
|
+
*/
|
|
443
|
+
export type GridColumnDef<TData extends RowData = RowData> = ColumnDef<TableFeatures, TData>
|
|
444
|
+
|
|
445
|
+
/** An array of {@link GridColumnDef} - what you pass to `<SvGrid columns={...}>`. */
|
|
446
|
+
export type GridColumns<TData extends RowData = RowData> = Array<GridColumnDef<TData>>
|
|
447
|
+
|
|
412
448
|
export type Column<TData extends RowData> = {
|
|
413
449
|
id: string
|
|
414
450
|
columnDef: ColumnDef<any, TData>
|
|
@@ -647,6 +683,119 @@ export function createGroupedRowModel<TData extends RowData>(): RowModelFactory<
|
|
|
647
683
|
return buildGroups(rows, 0, 0, 'group')
|
|
648
684
|
}
|
|
649
685
|
}
|
|
686
|
+
export type TreeRowModelOptions = {
|
|
687
|
+
/** Field holding each row's parent id. Rows with no parent are roots. */
|
|
688
|
+
parentField: string
|
|
689
|
+
/** Field holding the row's own id. Defaults to `'id'`. */
|
|
690
|
+
idField?: string
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Client-side tree data: nest the grid's own flat rows into a parent/child
|
|
695
|
+
* hierarchy that `createExpandedRowModel` then walks.
|
|
696
|
+
*
|
|
697
|
+
* This works on the rows the grid already built rather than on raw data, so
|
|
698
|
+
* tree rows keep their cells, editing, selection and formatting - they are real
|
|
699
|
+
* data rows that happen to have children, not synthetic banners like grouping's.
|
|
700
|
+
* That is also why the model is parent-id based: nested source arrays never
|
|
701
|
+
* become rows (the grid only builds rows for `data`), so nested input is
|
|
702
|
+
* flattened first with {@link flattenTreeData}. One code path, no duplicated
|
|
703
|
+
* row construction.
|
|
704
|
+
*
|
|
705
|
+
* Rows are tagged `__treeRow` so `isGroupRow` does not mistake an expandable
|
|
706
|
+
* data row for a full-width group banner.
|
|
707
|
+
*/
|
|
708
|
+
export function createTreeRowModel<TData extends RowData>(
|
|
709
|
+
options: TreeRowModelOptions,
|
|
710
|
+
): RowModelFactory<TData> {
|
|
711
|
+
const { parentField, idField = 'id' } = options
|
|
712
|
+
return ({ table, rows }) => {
|
|
713
|
+
if (!rows.length) return rows
|
|
714
|
+
const keyOf = (row: Row<TData>) => (row.original as any)?.[idField]
|
|
715
|
+
const parentOf = (row: Row<TData>) => (row.original as any)?.[parentField]
|
|
716
|
+
|
|
717
|
+
const present = new Set<unknown>()
|
|
718
|
+
for (const row of rows) present.add(keyOf(row))
|
|
719
|
+
|
|
720
|
+
const childrenByParent = new Map<unknown, Array<Row<TData>>>()
|
|
721
|
+
const roots: Array<Row<TData>> = []
|
|
722
|
+
for (const row of rows) {
|
|
723
|
+
const parent = parentOf(row)
|
|
724
|
+
// A row whose parent is absent (filtered out, or never existed) becomes a
|
|
725
|
+
// root rather than disappearing - silently dropping rows is worse than a
|
|
726
|
+
// shallower tree. Self-parenting is treated the same way.
|
|
727
|
+
if (parent == null || parent === keyOf(row) || !present.has(parent)) {
|
|
728
|
+
roots.push(row)
|
|
729
|
+
continue
|
|
730
|
+
}
|
|
731
|
+
const list = childrenByParent.get(parent) ?? []
|
|
732
|
+
list.push(row)
|
|
733
|
+
childrenByParent.set(parent, list)
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Guards a cycle in the parent chain from recursing forever.
|
|
737
|
+
const seen = new Set<unknown>()
|
|
738
|
+
const build = (row: Row<TData>, depth: number): Row<TData> => {
|
|
739
|
+
const key = keyOf(row)
|
|
740
|
+
const id = row.id
|
|
741
|
+
if (seen.has(key)) {
|
|
742
|
+
return { ...row, depth, subRows: [], getCanExpand: () => false } as Row<TData>
|
|
743
|
+
}
|
|
744
|
+
seen.add(key)
|
|
745
|
+
const subRows = (childrenByParent.get(key) ?? []).map((child) => build(child, depth + 1))
|
|
746
|
+
return {
|
|
747
|
+
...row,
|
|
748
|
+
depth,
|
|
749
|
+
subRows,
|
|
750
|
+
leafCount: subRows.reduce((n, sub) => n + 1 + (sub.leafCount ?? 0), 0),
|
|
751
|
+
__treeRow: true,
|
|
752
|
+
getCanExpand: () => subRows.length > 0,
|
|
753
|
+
getIsExpanded: () => Boolean((table.getState().expanded ?? {})[id]),
|
|
754
|
+
toggleExpanded: () => {
|
|
755
|
+
table.setExpanded((prev) => ({ ...prev, [id]: !prev[id] }))
|
|
756
|
+
},
|
|
757
|
+
} as Row<TData>
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return roots.map((root) => build(root, 0))
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
export type FlattenTreeOptions = {
|
|
765
|
+
/** Field holding an array of child objects. */
|
|
766
|
+
childrenField: string
|
|
767
|
+
/** Field holding each object's id. Defaults to `'id'`. */
|
|
768
|
+
idField?: string
|
|
769
|
+
/** Field to WRITE the resolved parent id onto. Defaults to `'__parentId'`. */
|
|
770
|
+
parentField?: string
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Flatten nested tree data into the flat parent-id shape `createTreeRowModel`
|
|
775
|
+
* consumes, stamping each child with its parent's id.
|
|
776
|
+
*
|
|
777
|
+
* Children are emitted directly after their parent so the natural order already
|
|
778
|
+
* matches the rendered tree. The `childrenField` array is left on the objects
|
|
779
|
+
* (harmless, and callers often still want it); only the parent link is added.
|
|
780
|
+
*/
|
|
781
|
+
export function flattenTreeData<T extends RowData>(
|
|
782
|
+
data: ReadonlyArray<T>,
|
|
783
|
+
options: FlattenTreeOptions,
|
|
784
|
+
): T[] {
|
|
785
|
+
const { childrenField, idField = 'id', parentField = '__parentId' } = options
|
|
786
|
+
const out: T[] = []
|
|
787
|
+
const walk = (nodes: ReadonlyArray<T>, parentId: unknown) => {
|
|
788
|
+
for (const node of nodes) {
|
|
789
|
+
const flat = { ...node, [parentField]: parentId } as T
|
|
790
|
+
out.push(flat)
|
|
791
|
+
const kids = (node as any)[childrenField]
|
|
792
|
+
if (Array.isArray(kids) && kids.length) walk(kids as ReadonlyArray<T>, (node as any)[idField])
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
walk(data, null)
|
|
796
|
+
return out
|
|
797
|
+
}
|
|
798
|
+
|
|
650
799
|
export function createExpandedRowModel<TData extends RowData>(): RowModelFactory<TData> {
|
|
651
800
|
return ({ table, rows }) => {
|
|
652
801
|
const expanded: ExpandedState = table.getState().expanded ?? {}
|
package/src/createMenu.svelte.ts
CHANGED
package/src/editing.test.ts
CHANGED
|
@@ -154,7 +154,7 @@ describe('isCellEditable', () => {
|
|
|
154
154
|
data: [{ a: 'hello' }],
|
|
155
155
|
})
|
|
156
156
|
ed.isCellEditable(ctx.allColumns[0], ctx.allRows[0])
|
|
157
|
-
const cc = spy.mock.calls[0][0]
|
|
157
|
+
const cc = spy.mock.calls[0]![0]
|
|
158
158
|
expect(cc.getValue()).toBe('hello')
|
|
159
159
|
expect(cc.cell.getValue()).toBe('hello')
|
|
160
160
|
expect(cc.cell.getContext()).toBe(cc)
|
package/src/editing.ts
CHANGED
|
@@ -2,126 +2,23 @@
|
|
|
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
5
|
parseEditorValue,
|
|
23
|
-
normalizeEditorOptions,
|
|
24
|
-
sortFns,
|
|
25
|
-
tableFeatures,
|
|
26
|
-
rowSortingFeature,
|
|
27
|
-
columnFilteringFeature,
|
|
28
|
-
columnGroupingFeature,
|
|
29
6
|
type CellContext,
|
|
30
|
-
type EditorContext,
|
|
31
|
-
type CellEditorOption,
|
|
32
7
|
type CellEditorType,
|
|
33
|
-
type CellFormatter,
|
|
34
|
-
type CellFormatConfig,
|
|
35
8
|
type Column,
|
|
36
|
-
type ColumnDef,
|
|
37
9
|
type Row,
|
|
38
10
|
type RowData,
|
|
39
|
-
type SvGridApi,
|
|
40
11
|
type TableFeatures,
|
|
41
12
|
} from "./index";
|
|
42
13
|
import "./sv-grid-scrollbar";
|
|
43
|
-
import
|
|
44
|
-
import { getKeyboardIntent, getNextActiveCell } from "./keyboard";
|
|
14
|
+
import { getNextActiveCell } from "./keyboard";
|
|
45
15
|
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
16
|
getCellKey,
|
|
77
|
-
resolveClassList,
|
|
78
|
-
toDateInputValue,
|
|
79
|
-
toDateTimeLocalInputValue,
|
|
80
|
-
getEditableInputValue,
|
|
81
|
-
getEditorInputType,
|
|
82
17
|
toValueArray,
|
|
83
|
-
getOptionLabel,
|
|
84
|
-
getOptionColor,
|
|
85
|
-
colorfulChipStyle,
|
|
86
|
-
getEditorClass,
|
|
87
|
-
asDate,
|
|
88
|
-
clampMenuX,
|
|
89
|
-
cssEscape,
|
|
90
|
-
rawToNumber,
|
|
91
|
-
formatFacetNumber,
|
|
92
|
-
formatFacetDate,
|
|
93
18
|
} from "./SvGrid.helpers";
|
|
94
|
-
import { createSelection } from "./selection";
|
|
95
|
-
import { createColumns } from "./columns";
|
|
96
|
-
import { createGridApi } from "./build-api";
|
|
97
|
-
import { createClipboard } from "./clipboard";
|
|
98
|
-
import {
|
|
99
|
-
filterOperatorOptions,
|
|
100
|
-
fallbackOperatorOption,
|
|
101
|
-
TEXT_OPERATORS,
|
|
102
|
-
NUMBER_OPERATORS,
|
|
103
|
-
DATE_OPERATORS,
|
|
104
|
-
CHECKBOX_OPERATORS,
|
|
105
|
-
operatorOption,
|
|
106
|
-
operatorsForColumn,
|
|
107
|
-
defaultOperatorFor,
|
|
108
|
-
operatorLabelFor,
|
|
109
|
-
} from "./filter-operators";
|
|
110
|
-
import {
|
|
111
|
-
type FacetBucket,
|
|
112
|
-
isBucketableColumn,
|
|
113
|
-
buildBuckets,
|
|
114
|
-
isInBucket,
|
|
115
|
-
} from "./facet-buckets";
|
|
116
19
|
import {
|
|
117
20
|
getColumnBaseValue,
|
|
118
21
|
isGroupRow,
|
|
119
|
-
toolPanelHeaderLabel,
|
|
120
|
-
formatSummaryNumeric,
|
|
121
|
-
getColumnAlign,
|
|
122
|
-
getPinnedCellValue,
|
|
123
|
-
getColumnAccessorValue,
|
|
124
|
-
columnDefMatchesId,
|
|
125
22
|
} from "./cell-values";
|
|
126
23
|
|
|
127
24
|
type HistoryStep = {
|
|
@@ -224,7 +121,7 @@ export function createEditing<
|
|
|
224
121
|
) {
|
|
225
122
|
return false;
|
|
226
123
|
}
|
|
227
|
-
if (editorType === "number" && !/[0-9
|
|
124
|
+
if (editorType === "number" && !/[0-9.+-]/.test(char)) return false;
|
|
228
125
|
ctx.editorSelectAll = false;
|
|
229
126
|
ctx.editingCell = {
|
|
230
127
|
rowId: row.id,
|
package/src/editor-contract.ts
CHANGED
|
@@ -68,16 +68,23 @@ export type SvEditorProps = {
|
|
|
68
68
|
* The interaction surface an editor exposes when embedded as a grid cell editor.
|
|
69
69
|
* Standalone usage ignores these; the grid supplies them so every editor shares
|
|
70
70
|
* one commit / cancel / move contract (Enter commits, Escape cancels, Tab moves)
|
|
71
|
-
* instead of each cell-editor re-implementing key handling.
|
|
72
|
-
*
|
|
71
|
+
* instead of each cell-editor re-implementing key handling.
|
|
72
|
+
*
|
|
73
|
+
* `CellEditorContext` (editor-registry.ts) extends this type, so what the grid
|
|
74
|
+
* hands a registered editor and what this contract promises are the same thing
|
|
75
|
+
* by construction rather than by convention.
|
|
73
76
|
*/
|
|
74
77
|
export type EditorInteraction<V = unknown> = {
|
|
75
|
-
/**
|
|
76
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Commit the value and stop editing (Enter, option pick, blur-commit).
|
|
80
|
+
* The argument is optional: an editor that has been reporting through
|
|
81
|
+
* `onChange` can just call `onCommit()` to commit what the grid already has.
|
|
82
|
+
*/
|
|
83
|
+
onCommit?: (value?: V) => void
|
|
77
84
|
/** Abandon the edit and restore the previous value (Escape). */
|
|
78
85
|
onCancel?: () => void
|
|
79
86
|
/** Commit, then move to the adjacent cell (Tab = 1, Shift+Tab = -1). */
|
|
80
|
-
onCommitAndMove?: (value: V, direction: 1 | -1) => void
|
|
87
|
+
onCommitAndMove?: (value: V | undefined, direction: 1 | -1) => void
|
|
81
88
|
/** Ask the surrounding popover/panel to close without committing. */
|
|
82
89
|
onRequestClose?: () => void
|
|
83
90
|
/** True while mounted inside a grid cell (lets an editor tune autofocus etc.). */
|
|
@@ -73,7 +73,6 @@ afterEach(() => {
|
|
|
73
73
|
|
|
74
74
|
describe('registry wiring in SvGrid', () => {
|
|
75
75
|
it('mounts a registered component when a custom-editorType cell edits', async () => {
|
|
76
|
-
let committed: unknown = undefined
|
|
77
76
|
registerCellEditor('stars', {
|
|
78
77
|
component: SvRating,
|
|
79
78
|
props: (ctx) => ({
|
|
@@ -90,7 +89,54 @@ describe('registry wiring in SvGrid', () => {
|
|
|
90
89
|
// The registered SvRating editor is mounted in the editing overlay.
|
|
91
90
|
const rating = target.querySelector('.sv-rating')
|
|
92
91
|
expect(rating).not.toBeNull()
|
|
93
|
-
|
|
92
|
+
} finally {
|
|
93
|
+
destroy()
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('hands the editor the full EditorInteraction contract, not a subset', async () => {
|
|
98
|
+
// The contract type used to be documented but never supplied: the registry
|
|
99
|
+
// passed only value/onChange/onCommit/onCancel, so an editor written against
|
|
100
|
+
// `EditorInteraction` (Tab-to-move, dismiss-without-commit, in-cell tuning)
|
|
101
|
+
// silently got undefined for half of it.
|
|
102
|
+
let seen: Record<string, unknown> = {}
|
|
103
|
+
registerCellEditor('stars', {
|
|
104
|
+
component: SvRating,
|
|
105
|
+
props: (ctx) => {
|
|
106
|
+
seen = ctx as unknown as Record<string, unknown>
|
|
107
|
+
return { value: ctx.value }
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const { api, target, destroy } = await mountGrid()
|
|
112
|
+
try {
|
|
113
|
+
api.startEditing(0, 'score')
|
|
114
|
+
await tick()
|
|
115
|
+
await tick()
|
|
116
|
+
expect(typeof seen.onCommit).toBe('function')
|
|
117
|
+
expect(typeof seen.onCancel).toBe('function')
|
|
118
|
+
expect(typeof seen.onChange).toBe('function')
|
|
119
|
+
expect(typeof seen.onCommitAndMove).toBe('function')
|
|
120
|
+
expect(typeof seen.onRequestClose).toBe('function')
|
|
121
|
+
expect(seen.inCell).toBe(true)
|
|
122
|
+
expect(seen.rowId).toBeTruthy()
|
|
123
|
+
expect(seen.columnId).toBe('score')
|
|
124
|
+
void target
|
|
125
|
+
} finally {
|
|
126
|
+
destroy()
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('defaults to passing the whole contract when a registration has no props mapping', async () => {
|
|
131
|
+
// A registration with no `props` should still work for a contract-aware
|
|
132
|
+
// editor - that is the point of the default mapping.
|
|
133
|
+
registerCellEditor('stars', SvRating)
|
|
134
|
+
const { api, target, destroy } = await mountGrid()
|
|
135
|
+
try {
|
|
136
|
+
api.startEditing(0, 'score')
|
|
137
|
+
await tick()
|
|
138
|
+
await tick()
|
|
139
|
+
expect(target.querySelector('.sv-rating')).not.toBeNull()
|
|
94
140
|
} finally {
|
|
95
141
|
destroy()
|
|
96
142
|
}
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
const Fake = (() => {}) as unknown as Component<any>
|
|
16
16
|
const Fake2 = (() => {}) as unknown as Component<any>
|
|
17
17
|
|
|
18
|
+
// onCommitAndMove / onRequestClose / inCell are deliberately left off: the
|
|
19
|
+
// tests below assert that defaultEditorProps maps them to undefined.
|
|
18
20
|
function ctx(overrides: Partial<CellEditorContext> = {}): CellEditorContext {
|
|
19
21
|
return {
|
|
20
22
|
value: 42,
|
|
@@ -24,7 +26,7 @@ function ctx(overrides: Partial<CellEditorContext> = {}): CellEditorContext {
|
|
|
24
26
|
onCommit: () => {},
|
|
25
27
|
onCancel: () => {},
|
|
26
28
|
...overrides,
|
|
27
|
-
}
|
|
29
|
+
} as CellEditorContext
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
afterEach(() => {
|
package/src/editor-registry.ts
CHANGED
|
@@ -20,9 +20,20 @@
|
|
|
20
20
|
* mounting.
|
|
21
21
|
*/
|
|
22
22
|
import type { Component } from 'svelte'
|
|
23
|
+
import type { EditorInteraction } from './editor-contract'
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Context the grid hands a cell editor when it mounts one for an edit.
|
|
27
|
+
*
|
|
28
|
+
* Extends {@link EditorInteraction} - the shared commit / cancel / move contract
|
|
29
|
+
* the `Sv*` editors are written against - and narrows the parts the grid always
|
|
30
|
+
* supplies, so a registered component and a built-in editor speak the same
|
|
31
|
+
* language. `onCommitAndMove`, `onRequestClose` and `inCell` come from the
|
|
32
|
+
* contract and are always populated by the grid.
|
|
33
|
+
*/
|
|
34
|
+
export type CellEditorContext<T = unknown> = Required<
|
|
35
|
+
Pick<EditorInteraction<T>, 'onCommit' | 'onCancel' | 'onCommitAndMove' | 'onRequestClose' | 'inCell'>
|
|
36
|
+
> & {
|
|
26
37
|
/** The value currently being edited. */
|
|
27
38
|
value: T
|
|
28
39
|
/** Id of the row being edited. */
|
|
@@ -31,10 +42,6 @@ export type CellEditorContext<T = unknown> = {
|
|
|
31
42
|
columnId: string
|
|
32
43
|
/** Update the in-progress value WITHOUT ending the edit. */
|
|
33
44
|
onChange: (value: T) => void
|
|
34
|
-
/** Commit the value and stop editing (optionally pass a final value). */
|
|
35
|
-
onCommit: (value?: T) => void
|
|
36
|
-
/** Discard changes and stop editing (returns focus to the grid). */
|
|
37
|
-
onCancel: () => void
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
/** How a registered component is mounted and wired for a cell edit. */
|
|
@@ -87,13 +94,22 @@ export function registeredCellEditorTypes(): string[] {
|
|
|
87
94
|
return [...registry.keys()]
|
|
88
95
|
}
|
|
89
96
|
|
|
90
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* The default context → props mapping used when a registration has no `props`.
|
|
99
|
+
* Passes the whole {@link EditorInteraction} surface, so an editor written
|
|
100
|
+
* against the shared contract works when registered with no mapping at all.
|
|
101
|
+
* Svelte ignores props a component doesn't declare, so the extra keys are inert
|
|
102
|
+
* for a simple editor that only wants `value` + `onCommit`.
|
|
103
|
+
*/
|
|
91
104
|
export function defaultEditorProps(ctx: CellEditorContext): Record<string, unknown> {
|
|
92
105
|
return {
|
|
93
106
|
value: ctx.value,
|
|
94
107
|
onChange: ctx.onChange,
|
|
95
108
|
onCommit: ctx.onCommit,
|
|
96
109
|
onCancel: ctx.onCancel,
|
|
110
|
+
onCommitAndMove: ctx.onCommitAndMove,
|
|
111
|
+
onRequestClose: ctx.onRequestClose,
|
|
112
|
+
inCell: ctx.inCell,
|
|
97
113
|
}
|
|
98
114
|
}
|
|
99
115
|
|
package/src/export-format.ts
CHANGED
|
@@ -521,6 +521,9 @@ const XML_ESCAPE: Record<string, string> = {
|
|
|
521
521
|
}
|
|
522
522
|
function xmlText(value: unknown): string {
|
|
523
523
|
const s = value == null ? '' : value instanceof Date ? value.toISOString() : String(value)
|
|
524
|
+
// Stripping control characters is the point: XML 1.0 forbids them outright,
|
|
525
|
+
// so a stray \x00 from a data source would produce an unopenable file.
|
|
526
|
+
// eslint-disable-next-line no-control-regex
|
|
524
527
|
return s.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '').replace(/[&<>"']/g, (c) => XML_ESCAPE[c]!)
|
|
525
528
|
}
|
|
526
529
|
/** Coerce a field name into a valid XML element name. */
|