@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/selection.ts
CHANGED
|
@@ -2,125 +2,17 @@
|
|
|
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
5
|
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
6
|
type RowData,
|
|
39
|
-
type SvGridApi,
|
|
40
7
|
type TableFeatures,
|
|
41
8
|
} from "./index";
|
|
42
9
|
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
10
|
import type {
|
|
65
|
-
Props,
|
|
66
|
-
SelectionPoint,
|
|
67
11
|
SelectionRange,
|
|
68
|
-
CellEditState,
|
|
69
|
-
FilterOperator,
|
|
70
|
-
FilterOption,
|
|
71
|
-
MenuPosition,
|
|
72
12
|
} 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 { createColumns } from "./columns";
|
|
95
|
-
import { createGridApi } from "./build-api";
|
|
96
|
-
import { createClipboard } from "./clipboard";
|
|
97
|
-
import {
|
|
98
|
-
filterOperatorOptions,
|
|
99
|
-
fallbackOperatorOption,
|
|
100
|
-
TEXT_OPERATORS,
|
|
101
|
-
NUMBER_OPERATORS,
|
|
102
|
-
DATE_OPERATORS,
|
|
103
|
-
CHECKBOX_OPERATORS,
|
|
104
|
-
operatorOption,
|
|
105
|
-
operatorsForColumn,
|
|
106
|
-
defaultOperatorFor,
|
|
107
|
-
operatorLabelFor,
|
|
108
|
-
} from "./filter-operators";
|
|
109
|
-
import {
|
|
110
|
-
type FacetBucket,
|
|
111
|
-
isBucketableColumn,
|
|
112
|
-
buildBuckets,
|
|
113
|
-
isInBucket,
|
|
114
|
-
} from "./facet-buckets";
|
|
115
13
|
import {
|
|
116
14
|
getColumnBaseValue,
|
|
117
15
|
isGroupRow,
|
|
118
|
-
toolPanelHeaderLabel,
|
|
119
|
-
formatSummaryNumeric,
|
|
120
|
-
getColumnAlign,
|
|
121
|
-
getPinnedCellValue,
|
|
122
|
-
getColumnAccessorValue,
|
|
123
|
-
columnDefMatchesId,
|
|
124
16
|
} from "./cell-values";
|
|
125
17
|
|
|
126
18
|
export function createSelection<
|
|
@@ -244,7 +136,6 @@ export function createSelection<
|
|
|
244
136
|
`td[data-svgrid-row="${rowIndex}"][data-svgrid-col="${colIndex}"]`,
|
|
245
137
|
);
|
|
246
138
|
if (cellEl) {
|
|
247
|
-
const headerHeight = ctx.theadEl?.offsetHeight ?? 0;
|
|
248
139
|
const containerRect = ctx.scrollContainer.getBoundingClientRect();
|
|
249
140
|
const cellRect = cellEl.getBoundingClientRect();
|
|
250
141
|
const cellTopInView = cellRect.top - containerRect.top;
|
|
@@ -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
|
|
26
|
+
req.groupKeys!.every((k, i) => String((r as Record<string, unknown>)[req.groupBy![i]!]) === k),
|
|
27
27
|
)
|
|
28
|
-
const level = req.groupKeys
|
|
29
|
-
if (level < req.groupBy
|
|
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
|
}
|
|
@@ -114,10 +114,10 @@ describe('createServerGroupModel', () => {
|
|
|
114
114
|
|
|
115
115
|
it('collapse removes the children again', async () => {
|
|
116
116
|
const src = makeGroupSource(DATA)
|
|
117
|
-
|
|
117
|
+
// Reads go through ctl.getState() below, so the change callback is a no-op.
|
|
118
118
|
const ctl = createServerGroupModel<Row>(src, {
|
|
119
119
|
groupBy: ['country', 'city'],
|
|
120
|
-
onChange: (
|
|
120
|
+
onChange: () => {},
|
|
121
121
|
})
|
|
122
122
|
ctl.refresh()
|
|
123
123
|
await flush()
|
|
@@ -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
|
|
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
|
},
|
package/src/spreadsheet.test.ts
CHANGED
|
@@ -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
|
|
package/src/summaries.ts
CHANGED
|
@@ -2,129 +2,17 @@
|
|
|
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
5
|
type Column,
|
|
36
|
-
type ColumnDef,
|
|
37
6
|
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
11
|
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
12
|
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
13
|
} from "./SvGrid.helpers";
|
|
94
|
-
import { createMenus } from "./menus";
|
|
95
|
-
import { createCellRender } from "./cell-render";
|
|
96
|
-
import { createEditing } from "./editing";
|
|
97
|
-
import { createSelection } from "./selection";
|
|
98
|
-
import { createColumns } from "./columns";
|
|
99
|
-
import { createGridApi } from "./build-api";
|
|
100
|
-
import { createClipboard } from "./clipboard";
|
|
101
|
-
import {
|
|
102
|
-
filterOperatorOptions,
|
|
103
|
-
fallbackOperatorOption,
|
|
104
|
-
TEXT_OPERATORS,
|
|
105
|
-
NUMBER_OPERATORS,
|
|
106
|
-
DATE_OPERATORS,
|
|
107
|
-
CHECKBOX_OPERATORS,
|
|
108
|
-
operatorOption,
|
|
109
|
-
operatorsForColumn,
|
|
110
|
-
defaultOperatorFor,
|
|
111
|
-
operatorLabelFor,
|
|
112
|
-
} from "./filter-operators";
|
|
113
|
-
import {
|
|
114
|
-
type FacetBucket,
|
|
115
|
-
isBucketableColumn,
|
|
116
|
-
buildBuckets,
|
|
117
|
-
isInBucket,
|
|
118
|
-
} from "./facet-buckets";
|
|
119
14
|
import {
|
|
120
|
-
getColumnBaseValue,
|
|
121
|
-
isGroupRow,
|
|
122
|
-
toolPanelHeaderLabel,
|
|
123
15
|
formatSummaryNumeric,
|
|
124
|
-
getColumnAlign,
|
|
125
|
-
getPinnedCellValue,
|
|
126
|
-
getColumnAccessorValue,
|
|
127
|
-
columnDefMatchesId,
|
|
128
16
|
} from "./cell-values";
|
|
129
17
|
|
|
130
18
|
export function createSummaries<
|
|
@@ -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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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.
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async `editorOptions` (#18): an option list that arrives from the server.
|
|
3
|
+
*
|
|
4
|
+
* Render paths call the option resolver synchronously and often, so an async
|
|
5
|
+
* source cannot block. The first call starts the fetch and returns an empty
|
|
6
|
+
* list; the resolved value lands in reactive state and re-renders the open
|
|
7
|
+
* editor. In-flight keys are tracked so repeated renders do not refire the
|
|
8
|
+
* request, and so the dropdown can say "Loading…" rather than "No options",
|
|
9
|
+
* which would read as "nothing to pick".
|
|
10
|
+
*/
|
|
11
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
12
|
+
import { mount, unmount } from 'svelte'
|
|
13
|
+
import SvGrid from './SvGrid.svelte'
|
|
14
|
+
import {
|
|
15
|
+
createCoreRowModel,
|
|
16
|
+
createFilteredRowModel,
|
|
17
|
+
createPaginatedRowModel,
|
|
18
|
+
createSortedRowModel,
|
|
19
|
+
sortFns,
|
|
20
|
+
tableFeatures,
|
|
21
|
+
rowSortingFeature,
|
|
22
|
+
} from './index'
|
|
23
|
+
|
|
24
|
+
type Row = { id: number; country: string; city: string }
|
|
25
|
+
|
|
26
|
+
const data: Row[] = [
|
|
27
|
+
{ id: 1, country: 'FR', city: 'Paris' },
|
|
28
|
+
{ id: 2, country: 'JP', city: 'Osaka' },
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
const tick = () => new Promise<void>((r) => queueMicrotask(r))
|
|
32
|
+
// The list editors lazy-load `SvGridDropdown` with a dynamic import, so
|
|
33
|
+
// microtasks alone never get it mounted - the settle has to yield the macrotask
|
|
34
|
+
// queue too, and then again for the resolved options to re-render.
|
|
35
|
+
const settle = async () => {
|
|
36
|
+
for (let i = 0; i < 4; i += 1) {
|
|
37
|
+
await new Promise((r) => setTimeout(r, 0))
|
|
38
|
+
await tick()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function mountGrid(columns: unknown[]) {
|
|
43
|
+
const target = document.createElement('div')
|
|
44
|
+
document.body.appendChild(target)
|
|
45
|
+
let api: never = null as never
|
|
46
|
+
const app = mount(SvGrid, {
|
|
47
|
+
target,
|
|
48
|
+
props: {
|
|
49
|
+
data,
|
|
50
|
+
columns,
|
|
51
|
+
features: tableFeatures({ rowSortingFeature }),
|
|
52
|
+
editable: true,
|
|
53
|
+
onApiReady: (a: never) => { api = a },
|
|
54
|
+
_rowModels: {
|
|
55
|
+
coreRowModel: createCoreRowModel(),
|
|
56
|
+
filteredRowModel: createFilteredRowModel(),
|
|
57
|
+
sortedRowModel: createSortedRowModel(sortFns),
|
|
58
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
59
|
+
},
|
|
60
|
+
containerHeight: 400,
|
|
61
|
+
virtualization: false,
|
|
62
|
+
} as never,
|
|
63
|
+
})
|
|
64
|
+
return { target, getApi: () => api, destroy: () => { unmount(app); target.remove() } }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let cleanup: (() => void) | null = null
|
|
68
|
+
afterEach(() => { cleanup?.(); cleanup = null })
|
|
69
|
+
|
|
70
|
+
/** Open the editor for row 0 of `columnId` through the public api. */
|
|
71
|
+
async function openEditor(api: never, columnId: string) {
|
|
72
|
+
;(api as never as { startEditing: (r: number, c: string) => boolean }).startEditing(0, columnId)
|
|
73
|
+
await settle()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe('async editorOptions (#18)', () => {
|
|
77
|
+
it('resolves a promised option list and renders it', async () => {
|
|
78
|
+
const columns = [
|
|
79
|
+
{ field: 'country', header: 'Country', width: 120 },
|
|
80
|
+
{
|
|
81
|
+
field: 'city',
|
|
82
|
+
header: 'City',
|
|
83
|
+
width: 160,
|
|
84
|
+
editorType: 'select',
|
|
85
|
+
editorOptions: Promise.resolve(['Paris', 'Lyon', 'Osaka']),
|
|
86
|
+
},
|
|
87
|
+
]
|
|
88
|
+
const g = mountGrid(columns)
|
|
89
|
+
cleanup = g.destroy
|
|
90
|
+
await settle()
|
|
91
|
+
await openEditor(g.getApi(), 'city')
|
|
92
|
+
expect(document.body.textContent).toContain('Lyon')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('fetches once no matter how many times render asks', async () => {
|
|
96
|
+
const source = vi.fn(async () => ['a', 'b'])
|
|
97
|
+
const columns = [
|
|
98
|
+
{ field: 'country', header: 'Country', width: 120 },
|
|
99
|
+
{
|
|
100
|
+
field: 'city',
|
|
101
|
+
header: 'City',
|
|
102
|
+
width: 160,
|
|
103
|
+
editorType: 'select',
|
|
104
|
+
editorOptions: source(),
|
|
105
|
+
},
|
|
106
|
+
]
|
|
107
|
+
const g = mountGrid(columns)
|
|
108
|
+
cleanup = g.destroy
|
|
109
|
+
await settle()
|
|
110
|
+
await openEditor(g.getApi(), 'city')
|
|
111
|
+
await settle()
|
|
112
|
+
// The promise was created once; the resolver must not re-await per render.
|
|
113
|
+
expect(source).toHaveBeenCalledTimes(1)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('calls a per-row async source ONCE per row, not once per render', async () => {
|
|
117
|
+
// The resolver runs inside the template, so calling an async source on
|
|
118
|
+
// every render fires a real request per render - and any state the source
|
|
119
|
+
// touches gets mutated mid-render, which Svelte rejects outright. Reported
|
|
120
|
+
// from the demo as `state_unsafe_mutation`.
|
|
121
|
+
const calls: number[] = []
|
|
122
|
+
const columns = [
|
|
123
|
+
{ field: 'country', header: 'Country', width: 120 },
|
|
124
|
+
{
|
|
125
|
+
field: 'city',
|
|
126
|
+
header: 'City',
|
|
127
|
+
width: 160,
|
|
128
|
+
editorType: 'select',
|
|
129
|
+
editorOptions: (row: Row) => {
|
|
130
|
+
calls.push(row.id)
|
|
131
|
+
return Promise.resolve(['Paris', 'Lyon'])
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
]
|
|
135
|
+
const g = mountGrid(columns)
|
|
136
|
+
cleanup = g.destroy
|
|
137
|
+
await settle()
|
|
138
|
+
await openEditor(g.getApi(), 'city')
|
|
139
|
+
await settle()
|
|
140
|
+
const afterOpen = calls.length
|
|
141
|
+
expect(afterOpen).toBeGreaterThan(0)
|
|
142
|
+
// Force real re-render passes while the editor is open. Each one used to
|
|
143
|
+
// re-invoke the source and start another request, which in a browser
|
|
144
|
+
// becomes an endless fetch loop as every resolution re-renders.
|
|
145
|
+
const api = g.getApi() as never as {
|
|
146
|
+
setOption: (k: string, v: unknown) => void
|
|
147
|
+
}
|
|
148
|
+
for (const zebra of [true, false, true]) {
|
|
149
|
+
api.setOption('zebraRows', zebra)
|
|
150
|
+
await settle()
|
|
151
|
+
}
|
|
152
|
+
expect(calls.length).toBe(afterOpen)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('refetches a cascade when the row it depends on changes', async () => {
|
|
156
|
+
// A cascade is a function of the row's OTHER cells. Keying the cache by row
|
|
157
|
+
// id alone made the first result permanent - changing Country left the old
|
|
158
|
+
// city list in place forever.
|
|
159
|
+
const asked: string[] = []
|
|
160
|
+
const columns = [
|
|
161
|
+
{ field: 'country', header: 'Country', width: 120 },
|
|
162
|
+
{
|
|
163
|
+
field: 'city',
|
|
164
|
+
header: 'City',
|
|
165
|
+
width: 160,
|
|
166
|
+
editorType: 'select',
|
|
167
|
+
editorOptions: (row: Row) => {
|
|
168
|
+
asked.push(row.country)
|
|
169
|
+
return Promise.resolve(row.country === 'FR' ? ['Paris', 'Lyon'] : ['Osaka', 'Kyoto'])
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
]
|
|
173
|
+
const g = mountGrid(columns)
|
|
174
|
+
cleanup = g.destroy
|
|
175
|
+
await settle()
|
|
176
|
+
const api = g.getApi() as never as {
|
|
177
|
+
startEditing: (r: number, c: string) => boolean
|
|
178
|
+
stopEditing: (cancel?: boolean) => boolean
|
|
179
|
+
setCellValue: (r: number, c: string, v: unknown) => void
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
await openEditor(api as never, 'city')
|
|
183
|
+
expect(document.body.textContent).toContain('Lyon')
|
|
184
|
+
api.stopEditing(true)
|
|
185
|
+
await settle()
|
|
186
|
+
|
|
187
|
+
// Row 0 moves from FR to JP.
|
|
188
|
+
api.setCellValue(0, 'country', 'JP')
|
|
189
|
+
await settle()
|
|
190
|
+
|
|
191
|
+
await openEditor(api as never, 'city')
|
|
192
|
+
expect(asked).toContain('JP')
|
|
193
|
+
expect(document.body.textContent).toContain('Kyoto')
|
|
194
|
+
expect(document.body.textContent).not.toContain('Lyon')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('resolves a per-row (cascading) async source', async () => {
|
|
198
|
+
const columns = [
|
|
199
|
+
{ field: 'country', header: 'Country', width: 120 },
|
|
200
|
+
{
|
|
201
|
+
field: 'city',
|
|
202
|
+
header: 'City',
|
|
203
|
+
width: 160,
|
|
204
|
+
editorType: 'select',
|
|
205
|
+
editorOptions: (row: Row) =>
|
|
206
|
+
Promise.resolve(row.country === 'FR' ? ['Paris', 'Lyon'] : ['Osaka', 'Kyoto']),
|
|
207
|
+
},
|
|
208
|
+
]
|
|
209
|
+
const g = mountGrid(columns)
|
|
210
|
+
cleanup = g.destroy
|
|
211
|
+
await settle()
|
|
212
|
+
await openEditor(g.getApi(), 'city')
|
|
213
|
+
// Row 1 is FR - it must not be offered the JP list.
|
|
214
|
+
expect(document.body.textContent).toContain('Lyon')
|
|
215
|
+
expect(document.body.textContent).not.toContain('Kyoto')
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('falls back to an empty list when the source rejects', async () => {
|
|
219
|
+
const rejected = Promise.reject(new Error('offline'))
|
|
220
|
+
rejected.catch(() => {}) // keep the reporter clean; the grid attaches its own handler
|
|
221
|
+
const columns = [
|
|
222
|
+
{ field: 'country', header: 'Country', width: 120 },
|
|
223
|
+
{
|
|
224
|
+
field: 'city',
|
|
225
|
+
header: 'City',
|
|
226
|
+
width: 160,
|
|
227
|
+
editorType: 'select',
|
|
228
|
+
editorOptions: rejected,
|
|
229
|
+
},
|
|
230
|
+
]
|
|
231
|
+
const g = mountGrid(columns)
|
|
232
|
+
cleanup = g.destroy
|
|
233
|
+
await settle()
|
|
234
|
+
await openEditor(g.getApi(), 'city')
|
|
235
|
+
await settle()
|
|
236
|
+
// The editor must settle on "No options", never spin forever.
|
|
237
|
+
expect(document.body.textContent).toContain('No options')
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('exposes refreshEditorOptions on the api', async () => {
|
|
241
|
+
const columns = [
|
|
242
|
+
{ field: 'country', header: 'Country', width: 120 },
|
|
243
|
+
{ field: 'city', header: 'City', width: 160 },
|
|
244
|
+
]
|
|
245
|
+
const g = mountGrid(columns)
|
|
246
|
+
cleanup = g.destroy
|
|
247
|
+
await settle()
|
|
248
|
+
const api = g.getApi() as never as { refreshEditorOptions: (c?: string) => void }
|
|
249
|
+
expect(typeof api.refreshEditorOptions).toBe('function')
|
|
250
|
+
expect(() => api.refreshEditorOptions('city')).not.toThrow()
|
|
251
|
+
expect(() => api.refreshEditorOptions()).not.toThrow()
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('leaves a plain array source synchronous', async () => {
|
|
255
|
+
const columns = [
|
|
256
|
+
{ field: 'country', header: 'Country', width: 120 },
|
|
257
|
+
{
|
|
258
|
+
field: 'city',
|
|
259
|
+
header: 'City',
|
|
260
|
+
width: 160,
|
|
261
|
+
editorType: 'select',
|
|
262
|
+
editorOptions: ['Paris', 'Lyon'],
|
|
263
|
+
},
|
|
264
|
+
]
|
|
265
|
+
const g = mountGrid(columns)
|
|
266
|
+
cleanup = g.destroy
|
|
267
|
+
await settle()
|
|
268
|
+
await openEditor(g.getApi(), 'city')
|
|
269
|
+
expect(document.body.textContent).toContain('Lyon')
|
|
270
|
+
})
|
|
271
|
+
})
|