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