@svgrid/grid 1.0.2 → 1.1.1
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/LICENSE +1 -1
- package/README.md +137 -39
- package/dist/GridFooter.svelte +164 -0
- package/dist/GridFooter.svelte.d.ts +29 -0
- package/dist/GridMenus.svelte +648 -0
- package/dist/GridMenus.svelte.d.ts +31 -0
- package/dist/SvGrid.controller.svelte.d.ts +472 -0
- package/dist/SvGrid.controller.svelte.js +1917 -0
- package/dist/SvGrid.css +2012 -0
- package/dist/SvGrid.helpers.d.ts +35 -0
- package/dist/SvGrid.helpers.js +160 -0
- package/dist/SvGrid.helpers.test.d.ts +1 -0
- package/dist/SvGrid.helpers.test.js +298 -0
- package/dist/SvGrid.svelte +2346 -8742
- package/dist/SvGrid.svelte.d.ts +4 -357
- package/dist/SvGrid.types.d.ts +523 -0
- package/dist/SvGrid.types.js +1 -0
- package/dist/SvGridChart.svelte +1060 -23
- package/dist/SvGridChart.svelte.d.ts +17 -0
- package/dist/aligned-grids.d.ts +6 -0
- package/dist/aligned-grids.js +84 -0
- package/dist/aligned-grids.test.d.ts +1 -0
- package/dist/aligned-grids.test.js +75 -0
- package/dist/build-api.coverage.test.d.ts +20 -0
- package/dist/build-api.coverage.test.js +505 -0
- package/dist/build-api.d.ts +5 -0
- package/dist/build-api.js +557 -0
- package/dist/cell-render.d.ts +15 -0
- package/dist/cell-render.js +246 -0
- package/dist/cell-render.test.d.ts +1 -0
- package/dist/cell-render.test.js +338 -0
- package/dist/cell-values.d.ts +28 -0
- package/dist/cell-values.js +89 -0
- package/dist/chart-export.test.d.ts +1 -0
- package/dist/chart-export.test.js +302 -0
- package/dist/chart.coverage.test.d.ts +1 -0
- package/dist/chart.coverage.test.js +748 -0
- package/dist/chart.d.ts +370 -3
- package/dist/chart.js +1135 -42
- package/dist/clipboard.d.ts +15 -0
- package/dist/clipboard.js +420 -0
- package/dist/clipboard.test.d.ts +1 -0
- package/dist/clipboard.test.js +700 -0
- package/dist/collaboration.coverage.test.d.ts +1 -0
- package/dist/collaboration.coverage.test.js +200 -0
- package/dist/column-groups.d.ts +19 -0
- package/dist/column-groups.js +62 -0
- package/dist/column-groups.test.d.ts +1 -0
- package/dist/column-groups.test.js +56 -0
- package/dist/column-types.d.ts +10 -0
- package/dist/column-types.js +63 -0
- package/dist/column-types.test.d.ts +1 -0
- package/dist/column-types.test.js +62 -0
- package/dist/columns.d.ts +30 -0
- package/dist/columns.js +277 -0
- package/dist/columns.test.d.ts +1 -0
- package/dist/columns.test.js +625 -0
- package/dist/core.d.ts +86 -1
- package/dist/css.d.ts +3 -0
- package/dist/editing.d.ts +31 -0
- package/dist/editing.js +529 -0
- package/dist/editing.test.d.ts +1 -0
- package/dist/editing.test.js +732 -0
- package/dist/editors/cell-editors.coverage.test.d.ts +1 -0
- package/dist/editors/cell-editors.coverage.test.js +139 -0
- package/dist/editors/cell-editors.d.ts +1 -1
- package/dist/facet-buckets.d.ts +13 -0
- package/dist/facet-buckets.js +54 -0
- package/dist/facet-buckets.test.d.ts +1 -0
- package/dist/facet-buckets.test.js +296 -0
- package/dist/features.d.ts +5 -0
- package/dist/features.js +30 -0
- package/dist/filter-operators.d.ts +16 -0
- package/dist/filter-operators.js +69 -0
- package/dist/filter-operators.test.d.ts +1 -0
- package/dist/filter-operators.test.js +135 -0
- package/dist/hyperformula-adapter.d.ts +82 -0
- package/dist/hyperformula-adapter.js +73 -0
- package/dist/hyperformula-adapter.test.d.ts +1 -0
- package/dist/hyperformula-adapter.test.js +205 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.js +8 -2
- package/dist/keyboard-handlers.coverage.test.d.ts +1 -0
- package/dist/keyboard-handlers.coverage.test.js +495 -0
- package/dist/keyboard-handlers.d.ts +7 -0
- package/dist/keyboard-handlers.js +202 -0
- package/dist/menus.d.ts +40 -0
- package/dist/menus.js +390 -0
- package/dist/menus.test.d.ts +1 -0
- package/dist/menus.test.js +560 -0
- package/dist/named-views.coverage.test.d.ts +1 -0
- package/dist/named-views.coverage.test.js +180 -0
- package/dist/named-views.d.ts +27 -0
- package/dist/named-views.js +39 -0
- package/dist/row-drag.d.ts +49 -0
- package/dist/row-drag.js +221 -0
- package/dist/row-drag.test.d.ts +1 -0
- package/dist/row-drag.test.js +142 -0
- package/dist/row-resize.d.ts +43 -0
- package/dist/row-resize.js +158 -0
- package/dist/row-resize.test.d.ts +1 -0
- package/dist/row-resize.test.js +329 -0
- package/dist/scroll-sync.d.ts +9 -0
- package/dist/scroll-sync.js +89 -0
- package/dist/scroll-sync.test.d.ts +1 -0
- package/dist/scroll-sync.test.js +290 -0
- package/dist/selection.d.ts +32 -0
- package/dist/selection.js +427 -0
- package/dist/selection.multi-range.test.d.ts +1 -0
- package/dist/selection.multi-range.test.js +55 -0
- package/dist/selection.test.d.ts +1 -0
- package/dist/selection.test.js +647 -0
- package/dist/server-data-source.coverage.test.d.ts +1 -0
- package/dist/server-data-source.coverage.test.js +154 -0
- package/dist/spreadsheet.d.ts +110 -0
- package/dist/spreadsheet.js +242 -0
- package/dist/spreadsheet.test.d.ts +1 -0
- package/dist/spreadsheet.test.js +446 -0
- package/dist/summaries.d.ts +12 -0
- package/dist/summaries.js +65 -0
- package/dist/sv-grid-scrollbar.js +13 -1
- package/dist/svgrid-wrapper.types.d.ts +31 -1
- package/dist/svgrid.behavior.test.js +20 -0
- package/dist/svgrid.comments-autocomplete.test.d.ts +1 -0
- package/dist/svgrid.comments-autocomplete.test.js +96 -0
- package/dist/svgrid.context-menu.test.d.ts +1 -0
- package/dist/svgrid.context-menu.test.js +102 -0
- package/dist/svgrid.interaction.test.js +31 -0
- package/dist/svgrid.new-features.wrapper.test.js +64 -6
- package/dist/svgrid.wrapper.test.js +27 -1
- package/dist/test-setup.js +9 -3
- package/dist/virtualization/column-virtualizer.d.ts +2 -0
- package/dist/virtualization/scroll-scaling.d.ts +45 -0
- package/dist/virtualization/scroll-scaling.js +99 -0
- package/dist/virtualization/scroll-scaling.test.d.ts +1 -0
- package/dist/virtualization/scroll-scaling.test.js +127 -0
- package/dist/virtualization/svelte-virtualizer.svelte.d.ts +2 -0
- package/dist/virtualization/svelte-virtualizer.svelte.js +2 -0
- package/dist/virtualization/virtualizer.d.ts +7 -0
- package/dist/virtualization/virtualizer.js +30 -0
- package/package.json +2 -1
- package/src/GridFooter.svelte +164 -0
- package/src/GridMenus.svelte +648 -0
- package/src/SvGrid.controller.svelte.ts +2352 -0
- package/src/SvGrid.css +2012 -0
- package/src/SvGrid.helpers.test.ts +415 -0
- package/src/SvGrid.helpers.ts +185 -0
- package/src/SvGrid.svelte +2346 -8742
- package/src/SvGrid.types.ts +537 -0
- package/src/SvGridChart.svelte +1060 -23
- package/src/aligned-grids.test.ts +80 -0
- package/src/aligned-grids.ts +87 -0
- package/src/build-api.coverage.test.ts +532 -0
- package/src/build-api.ts +683 -0
- package/src/cell-render.test.ts +451 -0
- package/src/cell-render.ts +426 -0
- package/src/cell-values.ts +114 -0
- package/src/chart-export.test.ts +370 -0
- package/src/chart.coverage.test.ts +814 -0
- package/src/chart.ts +1352 -47
- package/src/clipboard.test.ts +780 -0
- package/src/clipboard.ts +552 -0
- package/src/collaboration.coverage.test.ts +220 -0
- package/src/column-groups.test.ts +59 -0
- package/src/column-groups.ts +80 -0
- package/src/column-types.test.ts +68 -0
- package/src/column-types.ts +82 -0
- package/src/columns.test.ts +702 -0
- package/src/columns.ts +419 -0
- package/src/core.ts +86 -0
- package/src/css.d.ts +3 -0
- package/src/editing.test.ts +837 -0
- package/src/editing.ts +669 -0
- package/src/editors/cell-editors.coverage.test.ts +156 -0
- package/src/editors/cell-editors.ts +1 -0
- package/src/facet-buckets.test.ts +353 -0
- package/src/facet-buckets.ts +67 -0
- package/src/features.ts +128 -0
- package/src/filter-operators.test.ts +174 -0
- package/src/filter-operators.ts +87 -0
- package/src/hyperformula-adapter.test.ts +256 -0
- package/src/hyperformula-adapter.ts +124 -0
- package/src/index.ts +49 -0
- package/src/keyboard-handlers.coverage.test.ts +560 -0
- package/src/keyboard-handlers.ts +353 -0
- package/src/keyboard.ts +97 -97
- package/src/menus.test.ts +620 -0
- package/src/menus.ts +555 -0
- package/src/named-views.coverage.test.ts +210 -0
- package/src/named-views.ts +48 -0
- package/src/row-drag.test.ts +168 -0
- package/src/row-drag.ts +255 -0
- package/src/row-resize.test.ts +369 -0
- package/src/row-resize.ts +171 -0
- package/src/scroll-sync.test.ts +330 -0
- package/src/scroll-sync.ts +218 -0
- package/src/selection.multi-range.test.ts +61 -0
- package/src/selection.test.ts +722 -0
- package/src/selection.ts +579 -0
- package/src/server-data-source.coverage.test.ts +180 -0
- package/src/spreadsheet.test.ts +489 -0
- package/src/spreadsheet.ts +304 -0
- package/src/summaries.ts +204 -0
- package/src/sv-grid-scrollbar.ts +13 -1
- package/src/svgrid-wrapper.types.ts +31 -1
- package/src/svgrid.behavior.test.ts +22 -0
- package/src/svgrid.comments-autocomplete.test.ts +112 -0
- package/src/svgrid.context-menu.test.ts +126 -0
- package/src/svgrid.interaction.test.ts +30 -0
- package/src/svgrid.new-features.wrapper.test.ts +67 -6
- package/src/svgrid.wrapper.test.ts +27 -1
- package/src/test-setup.ts +9 -6
- package/src/virtualization/scroll-scaling.test.ts +148 -0
- package/src/virtualization/scroll-scaling.ts +121 -0
- package/src/virtualization/svelte-virtualizer.svelte.ts +2 -0
- package/src/virtualization/virtualizer.ts +26 -0
package/src/columns.ts
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
// columns handlers extracted from the controller. Imperative event handlers
|
|
2
|
+
// reading/writing controller state via the `ctx` handle; the reactive core
|
|
3
|
+
// ($state/$derived/$effect) stays in the controller.
|
|
4
|
+
import {
|
|
5
|
+
applyExcelFilter,
|
|
6
|
+
normalizeForFilter,
|
|
7
|
+
createColumnVirtualizer,
|
|
8
|
+
createCoreRowModel,
|
|
9
|
+
createExpandedRowModel,
|
|
10
|
+
createFilteredRowModel,
|
|
11
|
+
createGroupedRowModel,
|
|
12
|
+
createPaginatedRowModel,
|
|
13
|
+
createSvelteVirtualizer,
|
|
14
|
+
createSortedRowModel,
|
|
15
|
+
createSvGrid,
|
|
16
|
+
filterFns,
|
|
17
|
+
getGridCellA11yProps,
|
|
18
|
+
getGridCellDomId,
|
|
19
|
+
getGridHeaderA11yProps,
|
|
20
|
+
getGridRootA11yProps,
|
|
21
|
+
getGridRowA11yProps,
|
|
22
|
+
parseEditorValue,
|
|
23
|
+
normalizeEditorOptions,
|
|
24
|
+
sortFns,
|
|
25
|
+
tableFeatures,
|
|
26
|
+
rowSortingFeature,
|
|
27
|
+
columnFilteringFeature,
|
|
28
|
+
columnGroupingFeature,
|
|
29
|
+
type CellContext,
|
|
30
|
+
type EditorContext,
|
|
31
|
+
type CellEditorOption,
|
|
32
|
+
type CellEditorType,
|
|
33
|
+
type CellFormatter,
|
|
34
|
+
type CellFormatConfig,
|
|
35
|
+
type Column,
|
|
36
|
+
type ColumnDef,
|
|
37
|
+
type Row,
|
|
38
|
+
type RowData,
|
|
39
|
+
type SvGridApi,
|
|
40
|
+
type TableFeatures,
|
|
41
|
+
} from "./index";
|
|
42
|
+
import "./sv-grid-scrollbar";
|
|
43
|
+
import type { Snippet } from "svelte";
|
|
44
|
+
import { getKeyboardIntent, getNextActiveCell } from "./keyboard";
|
|
45
|
+
import {
|
|
46
|
+
formatNumericWithConfig,
|
|
47
|
+
getDateFormatter,
|
|
48
|
+
resolveDatePattern,
|
|
49
|
+
} from "./cell-formatting";
|
|
50
|
+
import {
|
|
51
|
+
RenderSnippetConfig,
|
|
52
|
+
RenderComponentConfig,
|
|
53
|
+
} from "./render-component";
|
|
54
|
+
import { buildFillPattern } from "./fill-patterns";
|
|
55
|
+
import { buildSparkline, toSparklineValues } from "./sparkline";
|
|
56
|
+
import {
|
|
57
|
+
resolveCellFormat,
|
|
58
|
+
computeColumnStat,
|
|
59
|
+
formatsNeedingStats,
|
|
60
|
+
type ColumnStat,
|
|
61
|
+
type ResolvedCellFormat,
|
|
62
|
+
} from "./conditional-formatting";
|
|
63
|
+
import SvGridDropdown from "./SvGridDropdown.svelte";
|
|
64
|
+
import type {
|
|
65
|
+
Props,
|
|
66
|
+
SelectionPoint,
|
|
67
|
+
SelectionRange,
|
|
68
|
+
CellEditState,
|
|
69
|
+
FilterOperator,
|
|
70
|
+
FilterOption,
|
|
71
|
+
MenuPosition,
|
|
72
|
+
} from "./SvGrid.types";
|
|
73
|
+
import {
|
|
74
|
+
cfTextStyle,
|
|
75
|
+
fmtStat,
|
|
76
|
+
getCellKey,
|
|
77
|
+
resolveClassList,
|
|
78
|
+
toDateInputValue,
|
|
79
|
+
toDateTimeLocalInputValue,
|
|
80
|
+
getEditableInputValue,
|
|
81
|
+
getEditorInputType,
|
|
82
|
+
toValueArray,
|
|
83
|
+
getOptionLabel,
|
|
84
|
+
getOptionColor,
|
|
85
|
+
colorfulChipStyle,
|
|
86
|
+
getEditorClass,
|
|
87
|
+
asDate,
|
|
88
|
+
clampMenuX,
|
|
89
|
+
cssEscape,
|
|
90
|
+
rawToNumber,
|
|
91
|
+
formatFacetNumber,
|
|
92
|
+
formatFacetDate,
|
|
93
|
+
} from "./SvGrid.helpers";
|
|
94
|
+
import { createGridApi } from "./build-api";
|
|
95
|
+
import { createClipboard } from "./clipboard";
|
|
96
|
+
import {
|
|
97
|
+
filterOperatorOptions,
|
|
98
|
+
fallbackOperatorOption,
|
|
99
|
+
TEXT_OPERATORS,
|
|
100
|
+
NUMBER_OPERATORS,
|
|
101
|
+
DATE_OPERATORS,
|
|
102
|
+
CHECKBOX_OPERATORS,
|
|
103
|
+
operatorOption,
|
|
104
|
+
operatorsForColumn,
|
|
105
|
+
defaultOperatorFor,
|
|
106
|
+
operatorLabelFor,
|
|
107
|
+
} from "./filter-operators";
|
|
108
|
+
import {
|
|
109
|
+
type FacetBucket,
|
|
110
|
+
isBucketableColumn,
|
|
111
|
+
buildBuckets,
|
|
112
|
+
isInBucket,
|
|
113
|
+
} from "./facet-buckets";
|
|
114
|
+
import {
|
|
115
|
+
getColumnBaseValue,
|
|
116
|
+
isGroupRow,
|
|
117
|
+
toolPanelHeaderLabel,
|
|
118
|
+
formatSummaryNumeric,
|
|
119
|
+
getColumnAlign,
|
|
120
|
+
getPinnedCellValue,
|
|
121
|
+
getColumnAccessorValue,
|
|
122
|
+
columnDefMatchesId,
|
|
123
|
+
} from "./cell-values";
|
|
124
|
+
|
|
125
|
+
export function createColumns<
|
|
126
|
+
TFeatures extends TableFeatures = TableFeatures,
|
|
127
|
+
TData extends RowData = RowData,
|
|
128
|
+
>(ctx: any) {
|
|
129
|
+
function cellPinStyle(columnId: string): string {
|
|
130
|
+
// z-index 30 - higher than the active-cell / selection-range stacking
|
|
131
|
+
// context (z=20) so a selected cell in the scrollable middle never
|
|
132
|
+
// bleeds over the pinned columns during horizontal scroll. Still
|
|
133
|
+
// sits below dropdowns (z=900+) and the column menu.
|
|
134
|
+
const leftOffset = ctx.pinnedOffsets.left[columnId];
|
|
135
|
+
if (leftOffset !== undefined) {
|
|
136
|
+
return `position: sticky; left: ${leftOffset}px; z-index: 30;`;
|
|
137
|
+
}
|
|
138
|
+
const rightOffset = ctx.pinnedOffsets.right[columnId];
|
|
139
|
+
if (rightOffset !== undefined) {
|
|
140
|
+
return `position: sticky; right: ${rightOffset}px; z-index: 30;`;
|
|
141
|
+
}
|
|
142
|
+
return "";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isColumnPinned(columnId: string): "left" | "right" | null {
|
|
146
|
+
if (ctx.columnPinning.left.includes(columnId)) return "left";
|
|
147
|
+
if (ctx.columnPinning.right.includes(columnId)) return "right";
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Compute the current full column order as displayed (pinned-left
|
|
152
|
+
* first, unpinned in the middle, pinned-right last). The order
|
|
153
|
+
* emitted via `onColumnOrderChange` matches what the user sees. */
|
|
154
|
+
function getCurrentColumnOrder(): string[] {
|
|
155
|
+
return ctx.allColumns.map((c: any) => c.id);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function emitColumnOrder() {
|
|
159
|
+
ctx.props.onColumnOrderChange?.(getCurrentColumnOrder());
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function setColumnOrderInternal(ids: ReadonlyArray<string>) {
|
|
163
|
+
ctx.userColumnOrder = [...ids];
|
|
164
|
+
emitColumnOrder();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Drop `dragId` before / after `targetId` in the user order. We
|
|
168
|
+
* rebuild a clean order out of the currently-displayed columns so
|
|
169
|
+
* hidden columns are unaffected and pin groups stay logically
|
|
170
|
+
* separate (you can reorder freely within left-pinned / unpinned /
|
|
171
|
+
* right-pinned, but a drop across pin zones is allowed - the cell
|
|
172
|
+
* just lands in whichever zone the target belongs to). */
|
|
173
|
+
function applyColumnDrop(dragId: string, targetId: string, side: "before" | "after") {
|
|
174
|
+
if (dragId === targetId) return;
|
|
175
|
+
const current = getCurrentColumnOrder();
|
|
176
|
+
const without = current.filter((id: any) => id !== dragId);
|
|
177
|
+
let idx = without.indexOf(targetId);
|
|
178
|
+
if (idx < 0) return;
|
|
179
|
+
if (side === "after") idx++;
|
|
180
|
+
without.splice(idx, 0, dragId);
|
|
181
|
+
setColumnOrderInternal(without);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function onColumnHeaderDragStart(e: DragEvent, columnId: string) {
|
|
185
|
+
if (!(ctx.props.enableColumnReorder ?? false)) return;
|
|
186
|
+
ctx.colDragId = columnId;
|
|
187
|
+
e.dataTransfer?.setData("text/plain", columnId);
|
|
188
|
+
e.dataTransfer!.effectAllowed = "move";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function onColumnHeaderDragOver(e: DragEvent, columnId: string) {
|
|
192
|
+
if (!ctx.colDragId || ctx.colDragId === columnId) return;
|
|
193
|
+
e.preventDefault();
|
|
194
|
+
e.dataTransfer!.dropEffect = "move";
|
|
195
|
+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
196
|
+
ctx.colDropSide = e.clientX < rect.left + rect.width / 2 ? "before" : "after";
|
|
197
|
+
ctx.colDropOnId = columnId;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function onColumnHeaderDragLeave(columnId: string) {
|
|
201
|
+
if (ctx.colDropOnId === columnId) { ctx.colDropOnId = null; ctx.colDropSide = null; }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function onColumnHeaderDrop(e: DragEvent, columnId: string) {
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
if (ctx.colDragId && ctx.colDropSide) applyColumnDrop(ctx.colDragId, columnId, ctx.colDropSide);
|
|
207
|
+
ctx.colDragId = null; ctx.colDropOnId = null; ctx.colDropSide = null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function onColumnHeaderDragEnd() {
|
|
211
|
+
ctx.colDragId = null; ctx.colDropOnId = null; ctx.colDropSide = null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function pinColumnLeft(columnId: string) {
|
|
215
|
+
const left = ctx.columnPinning.left.filter((id: any) => id !== columnId);
|
|
216
|
+
const right = ctx.columnPinning.right.filter((id: any) => id !== columnId);
|
|
217
|
+
ctx.columnPinning = { left: [...left, columnId], right };
|
|
218
|
+
ctx.closeMenus();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function pinColumnRight(columnId: string) {
|
|
222
|
+
const left = ctx.columnPinning.left.filter((id: any) => id !== columnId);
|
|
223
|
+
const right = ctx.columnPinning.right.filter((id: any) => id !== columnId);
|
|
224
|
+
ctx.columnPinning = { left, right: [columnId, ...right] };
|
|
225
|
+
ctx.closeMenus();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function unpinColumn(columnId: string) {
|
|
229
|
+
ctx.columnPinning = {
|
|
230
|
+
left: ctx.columnPinning.left.filter((id: any) => id !== columnId),
|
|
231
|
+
right: ctx.columnPinning.right.filter((id: any) => id !== columnId),
|
|
232
|
+
};
|
|
233
|
+
ctx.closeMenus();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function toggleColumnVisibleInPanel(columnId: string) {
|
|
237
|
+
if (ctx.hiddenColumns[columnId]) {
|
|
238
|
+
const next = { ...ctx.hiddenColumns };
|
|
239
|
+
delete next[columnId];
|
|
240
|
+
ctx.hiddenColumns = next;
|
|
241
|
+
} else {
|
|
242
|
+
ctx.hiddenColumns = { ...ctx.hiddenColumns, [columnId]: true };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function moveColumnInPanel(columnId: string, dir: -1 | 1) {
|
|
247
|
+
const current = ctx.toolPanelColumns.map((c: any) => c.id);
|
|
248
|
+
const i = current.indexOf(columnId);
|
|
249
|
+
const j = i + dir;
|
|
250
|
+
if (i < 0 || j < 0 || j >= current.length) return;
|
|
251
|
+
[current[i], current[j]] = [current[j]!, current[i]!];
|
|
252
|
+
ctx.userColumnOrder = current;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function toggleGroupInPanel(columnId: string) {
|
|
256
|
+
const g = (ctx.grid.getState().grouping ?? []) as string[];
|
|
257
|
+
ctx.grid.setGrouping(
|
|
258
|
+
g.includes(columnId)
|
|
259
|
+
? g.filter((x) => x !== columnId)
|
|
260
|
+
: [...g, columnId],
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Width before fitColumns scaling - what the columnDef/user actually set. */
|
|
265
|
+
function getColumnBaseWidth(columnId: string) {
|
|
266
|
+
if (ctx.columnWidths[columnId] !== undefined) return ctx.columnWidths[columnId];
|
|
267
|
+
const column = ctx.grid.getAllColumns().find((c: any) => c.id === columnId);
|
|
268
|
+
if (column?.columnDef.width !== undefined) return column.columnDef.width;
|
|
269
|
+
return ctx.props.columnWidth ?? 140;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function getColumnWidth(columnId: string) {
|
|
273
|
+
const fitted = ctx.fittedColumnWidths?.[columnId];
|
|
274
|
+
if (fitted !== undefined && ctx.columnWidths[columnId] === undefined)
|
|
275
|
+
return fitted;
|
|
276
|
+
return getColumnBaseWidth(columnId);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function startColumnResize(event: PointerEvent, columnId: string) {
|
|
280
|
+
event.stopPropagation();
|
|
281
|
+
event.preventDefault();
|
|
282
|
+
ctx.resizingColumnId = columnId;
|
|
283
|
+
ctx.resizeStartX = event.clientX;
|
|
284
|
+
ctx.resizeStartWidth = getColumnWidth(columnId);
|
|
285
|
+
document.addEventListener("pointermove", onColumnResizeMove);
|
|
286
|
+
document.addEventListener("pointerup", endColumnResize);
|
|
287
|
+
document.addEventListener("pointercancel", endColumnResize);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function onColumnResizeMove(event: PointerEvent) {
|
|
291
|
+
if (!ctx.resizingColumnId) return;
|
|
292
|
+
// Coalesce updates onto the animation frame: pointermove can fire many
|
|
293
|
+
// times per frame, and each $state mutation triggers a full reactive
|
|
294
|
+
// recompute of the column-layout pipeline. Without rAF coalescing the
|
|
295
|
+
// grid stutters during the drag.
|
|
296
|
+
ctx.resizePendingWidth = Math.max(
|
|
297
|
+
ctx.MIN_COLUMN_WIDTH,
|
|
298
|
+
ctx.resizeStartWidth + (event.clientX - ctx.resizeStartX),
|
|
299
|
+
);
|
|
300
|
+
if (ctx.resizeRaf !== null) return;
|
|
301
|
+
ctx.resizeRaf = requestAnimationFrame(() => {
|
|
302
|
+
ctx.resizeRaf = null;
|
|
303
|
+
if (!ctx.resizingColumnId) return;
|
|
304
|
+
ctx.columnWidths = {
|
|
305
|
+
...ctx.columnWidths,
|
|
306
|
+
[ctx.resizingColumnId]: ctx.resizePendingWidth,
|
|
307
|
+
};
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function endColumnResize() {
|
|
312
|
+
if (ctx.resizeRaf !== null) {
|
|
313
|
+
cancelAnimationFrame(ctx.resizeRaf);
|
|
314
|
+
ctx.resizeRaf = null;
|
|
315
|
+
}
|
|
316
|
+
if (ctx.resizingColumnId && ctx.resizePendingWidth) {
|
|
317
|
+
// Make sure the final width is committed even if the last rAF was
|
|
318
|
+
// canceled mid-flight.
|
|
319
|
+
ctx.columnWidths = {
|
|
320
|
+
...ctx.columnWidths,
|
|
321
|
+
[ctx.resizingColumnId]: ctx.resizePendingWidth,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
ctx.resizingColumnId = null;
|
|
325
|
+
document.removeEventListener("pointermove", onColumnResizeMove);
|
|
326
|
+
document.removeEventListener("pointerup", endColumnResize);
|
|
327
|
+
document.removeEventListener("pointercancel", endColumnResize);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function measureText(text: string, font: string): number {
|
|
331
|
+
if (!text) return 0;
|
|
332
|
+
if (!ctx.measureCanvas) ctx.measureCanvas = document.createElement("canvas");
|
|
333
|
+
const c2d = ctx.measureCanvas.getContext("2d");
|
|
334
|
+
if (!c2d) return 0;
|
|
335
|
+
c2d.font = font;
|
|
336
|
+
return c2d.measureText(text).width;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Snap a column to the width of its widest visible cell (header + body). */
|
|
340
|
+
function autosizeColumn(columnId: string) {
|
|
341
|
+
if (!ctx.gridRootEl) return;
|
|
342
|
+
const sampleCell = (ctx.gridRootEl as HTMLElement).querySelector<HTMLElement>(
|
|
343
|
+
`[data-col-id="${CSS.escape(columnId)}"]`,
|
|
344
|
+
);
|
|
345
|
+
if (!sampleCell) return;
|
|
346
|
+
const cellFont = getComputedStyle(sampleCell).font;
|
|
347
|
+
|
|
348
|
+
let max = ctx.MIN_COLUMN_WIDTH;
|
|
349
|
+
const header = (ctx.gridRootEl as HTMLElement).querySelector<HTMLElement>(
|
|
350
|
+
`[data-svgrid-header-col="${CSS.escape(columnId)}"]`,
|
|
351
|
+
);
|
|
352
|
+
if (header) {
|
|
353
|
+
const labelEl = header.querySelector<HTMLElement>(
|
|
354
|
+
".sv-grid-header-label",
|
|
355
|
+
);
|
|
356
|
+
const target = labelEl ?? header;
|
|
357
|
+
const text = target.textContent ?? "";
|
|
358
|
+
// +70 reserves room for sort indicator + funnel + 3-dot menu button
|
|
359
|
+
max = Math.max(
|
|
360
|
+
max,
|
|
361
|
+
measureText(text, getComputedStyle(target).font) + 70,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const cells = (ctx.gridRootEl as HTMLElement).querySelectorAll<HTMLElement>(
|
|
366
|
+
`[data-col-id="${CSS.escape(columnId)}"]`,
|
|
367
|
+
);
|
|
368
|
+
cells.forEach((cell: any) => {
|
|
369
|
+
const text = cell.textContent ?? "";
|
|
370
|
+
const w = measureText(text, cellFont);
|
|
371
|
+
if (w > max) max = w;
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// cell horizontal padding (~16px) + 8px breathing room
|
|
375
|
+
ctx.columnWidths = {
|
|
376
|
+
...ctx.columnWidths,
|
|
377
|
+
[columnId]: Math.max(ctx.MIN_COLUMN_WIDTH, Math.ceil(max) + 24),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function autosizeAllColumns() {
|
|
382
|
+
for (const column of ctx.allColumns) autosizeColumn(column.id);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function resetColumns() {
|
|
386
|
+
ctx.columnWidths = {};
|
|
387
|
+
ctx.hiddenColumns = {};
|
|
388
|
+
ctx.columnPinning = { left: [], right: [] };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
cellPinStyle,
|
|
393
|
+
isColumnPinned,
|
|
394
|
+
getCurrentColumnOrder,
|
|
395
|
+
emitColumnOrder,
|
|
396
|
+
setColumnOrderInternal,
|
|
397
|
+
applyColumnDrop,
|
|
398
|
+
onColumnHeaderDragStart,
|
|
399
|
+
onColumnHeaderDragOver,
|
|
400
|
+
onColumnHeaderDragLeave,
|
|
401
|
+
onColumnHeaderDrop,
|
|
402
|
+
onColumnHeaderDragEnd,
|
|
403
|
+
pinColumnLeft,
|
|
404
|
+
pinColumnRight,
|
|
405
|
+
unpinColumn,
|
|
406
|
+
toggleColumnVisibleInPanel,
|
|
407
|
+
moveColumnInPanel,
|
|
408
|
+
toggleGroupInPanel,
|
|
409
|
+
getColumnBaseWidth,
|
|
410
|
+
getColumnWidth,
|
|
411
|
+
startColumnResize,
|
|
412
|
+
onColumnResizeMove,
|
|
413
|
+
endColumnResize,
|
|
414
|
+
measureText,
|
|
415
|
+
autosizeColumn,
|
|
416
|
+
autosizeAllColumns,
|
|
417
|
+
resetColumns,
|
|
418
|
+
};
|
|
419
|
+
}
|
package/src/core.ts
CHANGED
|
@@ -32,6 +32,32 @@ export type CellContext<TData extends RowData> = {
|
|
|
32
32
|
getValue: () => unknown
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/** Params passed to a column's `colSpan(...)` / `rowSpan(...)` callbacks. */
|
|
36
|
+
export type CellSpanParams<TData extends RowData = RowData> = {
|
|
37
|
+
/** The row's underlying data object. */
|
|
38
|
+
data: TData
|
|
39
|
+
/** Display-row index in the current (filtered/sorted) row set. */
|
|
40
|
+
rowIndex: number
|
|
41
|
+
/** The column's id. */
|
|
42
|
+
columnId: string
|
|
43
|
+
/** The cell's base value for this column. */
|
|
44
|
+
value: unknown
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Params passed to a column's `valueParser(...)` on edit commit. */
|
|
48
|
+
export type ValueParserParams<TData extends RowData = RowData> = {
|
|
49
|
+
/** The value after built-in per-`editorType` coercion. */
|
|
50
|
+
newValue: unknown
|
|
51
|
+
/** The cell's previous value. */
|
|
52
|
+
oldValue: unknown
|
|
53
|
+
/** The raw string the editor produced (pre-coercion). */
|
|
54
|
+
rawInput: string
|
|
55
|
+
/** The row's underlying data object. */
|
|
56
|
+
data: TData
|
|
57
|
+
/** The column's id. */
|
|
58
|
+
columnId: string
|
|
59
|
+
}
|
|
60
|
+
|
|
35
61
|
/**
|
|
36
62
|
* Context passed to a custom `cellEditor` snippet/component. Three write
|
|
37
63
|
* helpers cover the lifecycle:
|
|
@@ -153,6 +179,45 @@ export type ColumnDef<TFeatures extends TableFeatures, TData extends RowData> =
|
|
|
153
179
|
footer?: ColumnDefTemplate<HeaderContext<TData>>
|
|
154
180
|
cell?: ColumnDefTemplate<CellContext<TData>>
|
|
155
181
|
columns?: Array<ColumnDef<TFeatures, TData>>
|
|
182
|
+
/**
|
|
183
|
+
* Declarative cell spanning (merged cells). Return how many COLUMNS this
|
|
184
|
+
* cell spans to the right (1 = no span). Value-driven, AG-Grid-style. Feed
|
|
185
|
+
* `spansToMerges(rows, columns)` into `spreadsheetLayout` to apply - it uses
|
|
186
|
+
* the same real `colspan`/`rowspan` merge engine (no separate code path).
|
|
187
|
+
*/
|
|
188
|
+
colSpan?: (params: CellSpanParams<TData>) => number
|
|
189
|
+
/**
|
|
190
|
+
* Declarative cell spanning (merged cells). Return how many ROWS this cell
|
|
191
|
+
* spans downward (1 = no span). See `colSpan` for how to apply.
|
|
192
|
+
*/
|
|
193
|
+
rowSpan?: (params: CellSpanParams<TData>) => number
|
|
194
|
+
/**
|
|
195
|
+
* High-level data type for the column. A convenience that resolves to the
|
|
196
|
+
* right `editorType`, alignment, date `format`, and filter operators without
|
|
197
|
+
* setting each by hand:
|
|
198
|
+
* 'text' → text editor, left-aligned
|
|
199
|
+
* 'number' → number editor, right-aligned, numeric filter operators
|
|
200
|
+
* 'boolean' → checkbox editor, centered
|
|
201
|
+
* 'date' → date editor (Date values), right-aligned, `{ type: 'date' }` format
|
|
202
|
+
* 'dateString' → date editor for ISO date STRINGS (e.g. '2026-06-27')
|
|
203
|
+
* Anything you set explicitly (`editorType`, `align`, `format`) still wins -
|
|
204
|
+
* `cellDataType` only fills the gaps. Grid-level `inferColumnTypes` infers
|
|
205
|
+
* this from the first data row for columns that declare neither.
|
|
206
|
+
*/
|
|
207
|
+
cellDataType?: 'text' | 'number' | 'boolean' | 'date' | 'dateString'
|
|
208
|
+
/**
|
|
209
|
+
* For a column INSIDE a collapsible column group: `'open'` shows this column
|
|
210
|
+
* only while the group is expanded, `'closed'` only while collapsed. Omit to
|
|
211
|
+
* always show it. Setting it on any direct child gives the parent group a
|
|
212
|
+
* collapse toggle. Pair with `openByDefault` on the group.
|
|
213
|
+
*/
|
|
214
|
+
columnGroupShow?: 'open' | 'closed'
|
|
215
|
+
/**
|
|
216
|
+
* For a GROUP column (one with `columns: [...]`): start the group expanded.
|
|
217
|
+
* Defaults to `false` (collapsed), matching AG Grid - so only the always-on
|
|
218
|
+
* and `columnGroupShow: 'closed'` children show until the user expands it.
|
|
219
|
+
*/
|
|
220
|
+
openByDefault?: boolean
|
|
156
221
|
editorType?:
|
|
157
222
|
| 'text'
|
|
158
223
|
| 'number'
|
|
@@ -165,6 +230,7 @@ export type ColumnDef<TFeatures extends TableFeatures, TData extends RowData> =
|
|
|
165
230
|
| 'chips'
|
|
166
231
|
| 'select' // custom dropdown - single value, no typeahead
|
|
167
232
|
| 'rich-select' // custom dropdown with a typeahead search input
|
|
233
|
+
| 'autocomplete' // free-text input with a live-filtered suggestion list (accepts any value)
|
|
168
234
|
| 'textarea' // multi-line editor; Tab or Ctrl+Enter commits, plain Enter inserts a newline
|
|
169
235
|
| 'color' // native <input type="color"> swatch
|
|
170
236
|
| 'rating' // 5-star rating control
|
|
@@ -200,6 +266,19 @@ export type ColumnDef<TFeatures extends TableFeatures, TData extends RowData> =
|
|
|
200
266
|
* `false`.
|
|
201
267
|
*/
|
|
202
268
|
editable?: boolean | ((context: CellContext<TData>) => boolean)
|
|
269
|
+
/**
|
|
270
|
+
* Transform the committed edit value before it is written to the row.
|
|
271
|
+
* Runs after the built-in per-`editorType` coercion, so `newValue` is
|
|
272
|
+
* already type-parsed; return the final value to store (e.g. round a
|
|
273
|
+
* number, uppercase a code, look up an id). AG-Grid-style `valueParser`.
|
|
274
|
+
*/
|
|
275
|
+
valueParser?: (params: ValueParserParams<TData>) => unknown
|
|
276
|
+
/**
|
|
277
|
+
* Briefly flash / highlight this column's cell when its value changes
|
|
278
|
+
* (streaming feeds, edits, server pushes). `true` uses the default flash;
|
|
279
|
+
* pass `{ className }` to apply your own animation class instead.
|
|
280
|
+
*/
|
|
281
|
+
cellFlash?: boolean | { className?: string }
|
|
203
282
|
/**
|
|
204
283
|
* When `false`, this column never shows a sort indicator and clicking
|
|
205
284
|
* its header is a no-op - `api.setSort(thisColumn, ...)` is also
|
|
@@ -260,6 +339,13 @@ export type ColumnDef<TFeatures extends TableFeatures, TData extends RowData> =
|
|
|
260
339
|
sparkline?: SparklineConfig
|
|
261
340
|
/** Initial column width in pixels. Falls back to the grid's `columnWidth` prop. */
|
|
262
341
|
width?: number
|
|
342
|
+
/**
|
|
343
|
+
* Initial visibility. Set `false` to start the column hidden while still
|
|
344
|
+
* listing it in the Choose Columns UI for the user to re-enable. Applied
|
|
345
|
+
* once at mount; after that `api.setColumnVisible` / user toggles win.
|
|
346
|
+
* On a group column, `false` hides the whole group's leaf columns.
|
|
347
|
+
*/
|
|
348
|
+
visible?: boolean
|
|
263
349
|
/**
|
|
264
350
|
* Horizontal alignment for header and body cells. When omitted, the
|
|
265
351
|
* default is inferred from `editorType`:
|
package/src/css.d.ts
ADDED