@svgrid/grid 2.6.20 → 2.6.22
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/CHANGELOG.md +62 -0
- package/README.md +22 -0
- package/dist/GridMenus.svelte +17 -12
- package/dist/SvGrid.controller.svelte.d.ts +14 -8
- package/dist/SvGrid.controller.svelte.js +214 -74
- package/dist/SvGrid.css +1 -1
- package/dist/SvGrid.svelte +112 -57
- package/dist/SvGrid.types.d.ts +191 -1
- package/dist/ai.d.ts +28 -0
- package/dist/ai.js +6 -0
- package/dist/cdn/{GridMenus-B0F9iBrG.js → GridMenus-BuoBPqxx.js} +137 -132
- package/dist/cdn/GridMenus-n4llxoOI.js +494 -0
- package/dist/cdn/column-resize-DsfNXMom.js +102 -0
- package/dist/cdn/row-resize-BRcimkUT.js +95 -0
- package/dist/cdn/{src-Cd0tearp.js → src-C9Hihx1W.js} +3497 -3487
- package/dist/cdn/{src-B1TdiyS8.js → src-D1lXwq1l.js} +8258 -8248
- package/dist/cdn/svgrid.js +10 -8
- package/dist/cdn/svgrid.svelte-external.js +10 -8
- package/dist/cdn/validate-_CDJzgIo.js +75 -0
- package/dist/cell-formatting.d.ts +2 -0
- package/dist/cell-formatting.js +2 -0
- package/dist/chart-export.d.ts +1 -0
- package/dist/chart.d.ts +31 -5
- package/dist/chart.js +9 -3
- package/dist/column-groups.js +1 -1
- package/dist/column-resize.d.ts +46 -0
- package/dist/column-resize.js +205 -0
- package/dist/columns.d.ts +0 -3
- package/dist/columns.js +0 -57
- package/dist/core.d.ts +216 -4
- package/dist/core.js +532 -119
- package/dist/createTree.svelte.d.ts +3 -0
- package/dist/createTree.svelte.js +1 -0
- package/dist/datetime/date-core.d.ts +2 -0
- package/dist/datetime/date-restrict.d.ts +1 -0
- package/dist/datetime/timezone.d.ts +1 -0
- package/dist/dock-manager-model.d.ts +3 -0
- package/dist/dock-manager-model.js +1 -0
- package/dist/dock-model.d.ts +6 -0
- package/dist/dock-model.js +3 -0
- package/dist/editor-contract.d.ts +1 -0
- package/dist/filtering/excel-filters.js +28 -0
- package/dist/group-display.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -0
- package/dist/list-option.d.ts +1 -0
- package/dist/menus.js +1 -1
- package/dist/positioning.d.ts +2 -0
- package/dist/row-resize.d.ts +11 -0
- package/dist/row-resize.js +7 -1
- package/dist/scheduler-ical.d.ts +1 -0
- package/dist/scheduler-model.d.ts +1 -0
- package/dist/selection.js +9 -0
- package/dist/spreadsheet.d.ts +1 -1
- package/dist/spreadsheet.js +1 -1
- package/dist/summaries.js +22 -0
- package/dist/svgrid-wrapper.types.d.ts +5 -0
- package/dist/toast-store.svelte.d.ts +4 -0
- package/dist/validate.d.ts +50 -0
- package/dist/validate.js +187 -0
- package/package.json +4 -1
- package/src/GridMenus.svelte +17 -12
- package/src/SvGrid.controller.svelte.ts +223 -76
- package/src/SvGrid.css +1 -1
- package/src/SvGrid.svelte +112 -57
- package/src/SvGrid.types.ts +191 -1
- package/src/ai.ts +28 -0
- package/src/cell-formatting.ts +2 -0
- package/src/chart-export.ts +1 -0
- package/src/chart.ts +31 -5
- package/src/column-groups.ts +1 -1
- package/src/column-resize.test.ts +381 -0
- package/src/column-resize.ts +227 -0
- package/src/columns.test.ts +0 -103
- package/src/columns.ts +0 -58
- package/src/core.aggregate.test.ts +134 -0
- package/src/core.filter.test.ts +156 -0
- package/src/core.grouping.test.ts +146 -0
- package/src/core.row-shape.test.ts +119 -0
- package/src/core.rowmodel-cache.test.ts +121 -0
- package/src/core.sort.test.ts +293 -0
- package/src/core.ts +723 -119
- package/src/createTree.svelte.ts +3 -0
- package/src/datetime/date-core.ts +2 -0
- package/src/datetime/date-restrict.ts +1 -0
- package/src/datetime/timezone.ts +1 -0
- package/src/dock-manager-model.ts +3 -0
- package/src/dock-model.ts +6 -0
- package/src/editor-contract.ts +1 -0
- package/src/filtering/excel-filters.ts +30 -0
- package/src/filtering/normalize-fast-path.test.ts +104 -0
- package/src/group-display.ts +1 -1
- package/src/index.ts +12 -1
- package/src/list-option.ts +1 -0
- package/src/menus.ts +1 -1
- package/src/positioning.ts +2 -0
- package/src/resize-props.test.ts +361 -0
- package/src/row-resize.test.ts +31 -0
- package/src/row-resize.ts +21 -3
- package/src/scheduler-ical.ts +1 -0
- package/src/scheduler-model.ts +1 -0
- package/src/selection.ts +9 -0
- package/src/spreadsheet.ts +1 -1
- package/src/summaries.ts +21 -0
- package/src/svgrid-wrapper.types.ts +5 -0
- package/src/svgrid.summaries.test.ts +217 -0
- package/src/toast-store.svelte.ts +4 -0
- package/src/validate.test.ts +207 -0
- package/src/validate.ts +269 -0
- package/dist/cdn/GridMenus-IHK_l7m6.js +0 -489
package/dist/validate.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/** How many rows to sample when deciding whether a `field` exists. */
|
|
2
|
+
const FIELD_SAMPLE_ROWS = 10;
|
|
3
|
+
const DOCS = 'https://svgrid.com/docs/getting-started/3-data-and-columns/';
|
|
4
|
+
/** Every leaf column, flattened through group columns. */
|
|
5
|
+
function leaves(columns, out = []) {
|
|
6
|
+
for (const col of columns) {
|
|
7
|
+
if (col.columns?.length)
|
|
8
|
+
leaves(col.columns, out);
|
|
9
|
+
else
|
|
10
|
+
out.push(col);
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
export function validateGridConfig(input) {
|
|
15
|
+
const messages = [];
|
|
16
|
+
const cols = leaves(input.columns ?? []);
|
|
17
|
+
// ---- 1. A `field` that does not exist on the data -------------------------
|
|
18
|
+
// The single most expensive silent failure: the column renders, and every
|
|
19
|
+
// cell in it is blank. Sampled across several rows so genuinely sparse data
|
|
20
|
+
// (a key absent from row 0 but present later) does not trip it.
|
|
21
|
+
const sample = (input.data ?? []).slice(0, FIELD_SAMPLE_ROWS);
|
|
22
|
+
if (sample.length) {
|
|
23
|
+
const known = new Set();
|
|
24
|
+
for (const row of sample) {
|
|
25
|
+
if (row && typeof row === 'object')
|
|
26
|
+
for (const k of Object.keys(row))
|
|
27
|
+
known.add(k);
|
|
28
|
+
}
|
|
29
|
+
for (const col of cols) {
|
|
30
|
+
const field = col.field;
|
|
31
|
+
// `fieldFn` computes its value, and an id-only column (actions, buttons)
|
|
32
|
+
// never reads the row - neither needs a matching key.
|
|
33
|
+
if (!field || col.fieldFn)
|
|
34
|
+
continue;
|
|
35
|
+
if (known.has(field))
|
|
36
|
+
continue;
|
|
37
|
+
const guess = nearest(field, [...known]);
|
|
38
|
+
messages.push(`[svgrid] Column field "${field}" does not exist on your row data, so that ` +
|
|
39
|
+
`column renders empty.${guess ? ` Did you mean "${guess}"?` : ''} ` +
|
|
40
|
+
`Available keys: ${[...known].slice(0, 12).join(', ')}. See ${DOCS}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// ---- 2. Duplicate column ids ---------------------------------------------
|
|
44
|
+
// Two columns resolving to the same id makes selection, sorting and column
|
|
45
|
+
// state address the wrong one, with no error anywhere.
|
|
46
|
+
const seen = new Map();
|
|
47
|
+
for (const col of cols) {
|
|
48
|
+
const id = (col.id ?? col.field);
|
|
49
|
+
if (!id)
|
|
50
|
+
continue;
|
|
51
|
+
seen.set(id, (seen.get(id) ?? 0) + 1);
|
|
52
|
+
}
|
|
53
|
+
for (const [id, n] of seen) {
|
|
54
|
+
if (n > 1) {
|
|
55
|
+
messages.push(`[svgrid] ${n} columns share the id "${id}". Column state (sorting, ` +
|
|
56
|
+
`filtering, pinning, width) is keyed by id, so they will act as one. ` +
|
|
57
|
+
`Give each an explicit \`id\`. See ${DOCS}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// ---- 3. `pageSize` with pagination never asked for ------------------------
|
|
61
|
+
// Only when BOTH pagination props are absent. `pageable={false}` is a
|
|
62
|
+
// deliberate statement - often a bound toggle that flips on later - whereas
|
|
63
|
+
// an unset prop means the author never considered it. Warning on the former
|
|
64
|
+
// fired on our own shortcut-config demo, which is exactly the kind of noise
|
|
65
|
+
// that teaches people to ignore warnings.
|
|
66
|
+
const paginationUnset = input.pageable === undefined && input.showPagination === undefined;
|
|
67
|
+
if (input.pageSize !== undefined && paginationUnset) {
|
|
68
|
+
messages.push('[svgrid] `pageSize` is set but pagination was never turned on, so it has ' +
|
|
69
|
+
'no effect and every row renders. Add `pageable` to switch the pager on.');
|
|
70
|
+
}
|
|
71
|
+
// ---- 4. A column asks to sort, but nothing enables sorting ----------------
|
|
72
|
+
// `sortable` on the grid injects rowSortingFeature, so only warn when neither
|
|
73
|
+
// the shortcut nor an explicitly registered feature is present.
|
|
74
|
+
const sortingEnabled = input.sortable === true || Boolean(input.features?.rowSortingFeature);
|
|
75
|
+
if (!sortingEnabled && cols.some((c) => c.sortable === true)) {
|
|
76
|
+
messages.push('[svgrid] A column sets `sortable: true`, but sorting is not enabled on the ' +
|
|
77
|
+
'grid, so its header does nothing. Add `sortable` to <SvGrid> (it registers ' +
|
|
78
|
+
'the sorting feature for you).');
|
|
79
|
+
}
|
|
80
|
+
// ---- 5. Column ids referenced by other props ------------------------------
|
|
81
|
+
// `groupBy` and `treeData.column` address columns by id. A name that matches
|
|
82
|
+
// nothing is silently ignored, so grouping or the tree expander just never
|
|
83
|
+
// appears and there is nothing to debug against.
|
|
84
|
+
const columnIds = new Set();
|
|
85
|
+
for (const col of cols) {
|
|
86
|
+
const id = (col.id ?? col.field);
|
|
87
|
+
if (id)
|
|
88
|
+
columnIds.add(id);
|
|
89
|
+
}
|
|
90
|
+
for (const id of input.groupBy ?? []) {
|
|
91
|
+
if (columnIds.has(id))
|
|
92
|
+
continue;
|
|
93
|
+
const guess = nearest(id, [...columnIds]);
|
|
94
|
+
messages.push(`[svgrid] \`groupBy\` refers to column "${id}", which does not exist, so ` +
|
|
95
|
+
`that grouping level is ignored.${guess ? ` Did you mean "${guess}"?` : ''} ` +
|
|
96
|
+
`Column ids: ${[...columnIds].slice(0, 12).join(', ')}`);
|
|
97
|
+
}
|
|
98
|
+
const treeColumn = input.treeData?.column;
|
|
99
|
+
if (treeColumn && !columnIds.has(treeColumn)) {
|
|
100
|
+
messages.push(`[svgrid] \`treeData.column\` refers to column "${treeColumn}", which does ` +
|
|
101
|
+
`not exist, so the expander falls back to the first visible column.`);
|
|
102
|
+
}
|
|
103
|
+
// ---- 6. treeData pointing at fields the data does not have ----------------
|
|
104
|
+
if (sample.length && input.treeData) {
|
|
105
|
+
const known = new Set();
|
|
106
|
+
for (const row of sample) {
|
|
107
|
+
if (row && typeof row === 'object')
|
|
108
|
+
for (const k of Object.keys(row))
|
|
109
|
+
known.add(k);
|
|
110
|
+
}
|
|
111
|
+
const parentField = input.treeData.parentField;
|
|
112
|
+
if (parentField && !known.has(parentField)) {
|
|
113
|
+
const guess = nearest(parentField, [...known]);
|
|
114
|
+
messages.push(`[svgrid] \`treeData.parentField\` is "${parentField}", which is not on ` +
|
|
115
|
+
`your row data, so every row becomes a root and no hierarchy appears.` +
|
|
116
|
+
`${guess ? ` Did you mean "${guess}"?` : ''}`);
|
|
117
|
+
}
|
|
118
|
+
// `idField` defaults to 'id'; only check what was asked for explicitly.
|
|
119
|
+
const idField = input.treeData.idField;
|
|
120
|
+
if (idField && !known.has(idField)) {
|
|
121
|
+
messages.push(`[svgrid] \`treeData.idField\` is "${idField}", which is not on your row ` +
|
|
122
|
+
`data, so parent lookups cannot match and the tree stays flat.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// ---- 7. Pinning that column virtualization will hide ----------------------
|
|
126
|
+
// Documented incompatibility: the virtualizer recycles column DOM nodes, so
|
|
127
|
+
// sticky pinning cannot survive it. `columnVirtualization` defaults to ON,
|
|
128
|
+
// which means the natural way to write this silently does nothing.
|
|
129
|
+
const pinned = (input.initialColumnPinning?.left?.length ?? 0) +
|
|
130
|
+
(input.initialColumnPinning?.right?.length ?? 0);
|
|
131
|
+
if (pinned > 0 && input.columnVirtualization !== false) {
|
|
132
|
+
messages.push('[svgrid] `initialColumnPinning` is set while column virtualization is on ' +
|
|
133
|
+
'(its default), so the pinned columns will not stick - the virtualizer ' +
|
|
134
|
+
'recycles column nodes. Add `columnVirtualization={false}`.');
|
|
135
|
+
}
|
|
136
|
+
// ---- 8. Server-mode contracts left half-wired -----------------------------
|
|
137
|
+
// Each of these makes the grid hand control to the consumer. Miss the other
|
|
138
|
+
// half and the feature looks broken rather than unconfigured.
|
|
139
|
+
if (input.externalPagination === true && input.rowCount === undefined) {
|
|
140
|
+
messages.push('[svgrid] `externalPagination` is on but `rowCount` is not set, so the pager ' +
|
|
141
|
+
'cannot know how many pages exist. Pass the server total as `rowCount`.');
|
|
142
|
+
}
|
|
143
|
+
if (input.externalSort === true && !input.onSortingChange) {
|
|
144
|
+
messages.push('[svgrid] `externalSort` is on but there is no `onSortingChange` handler, so ' +
|
|
145
|
+
'clicking a header changes nothing - the grid stopped sorting and nobody ' +
|
|
146
|
+
'is listening. Add `onSortingChange` and re-fetch in your handler.');
|
|
147
|
+
}
|
|
148
|
+
if (input.externalFilter === true && !input.onFiltersChange) {
|
|
149
|
+
messages.push('[svgrid] `externalFilter` is on but there is no `onFiltersChange` handler, ' +
|
|
150
|
+
'so filtering the grid changes nothing. Add `onFiltersChange` and re-fetch ' +
|
|
151
|
+
'in your handler.');
|
|
152
|
+
}
|
|
153
|
+
return messages;
|
|
154
|
+
}
|
|
155
|
+
/** Closest key by edit distance, for a "did you mean" hint. Undefined if none is close. */
|
|
156
|
+
function nearest(target, candidates) {
|
|
157
|
+
let best;
|
|
158
|
+
let bestScore = Infinity;
|
|
159
|
+
for (const c of candidates) {
|
|
160
|
+
const d = distance(target.toLowerCase(), c.toLowerCase());
|
|
161
|
+
if (d < bestScore) {
|
|
162
|
+
bestScore = d;
|
|
163
|
+
best = c;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Only suggest a genuinely near miss - a third of the length, at most 3 edits.
|
|
167
|
+
const limit = Math.min(3, Math.floor(target.length / 3) + 1);
|
|
168
|
+
return bestScore <= limit ? best : undefined;
|
|
169
|
+
}
|
|
170
|
+
/** Levenshtein distance, iterative single-row. Inputs here are identifier-length. */
|
|
171
|
+
function distance(a, b) {
|
|
172
|
+
if (a === b)
|
|
173
|
+
return 0;
|
|
174
|
+
const prev = new Array(b.length + 1);
|
|
175
|
+
for (let j = 0; j <= b.length; j++)
|
|
176
|
+
prev[j] = j;
|
|
177
|
+
for (let i = 1; i <= a.length; i++) {
|
|
178
|
+
let carry = prev[0];
|
|
179
|
+
prev[0] = i;
|
|
180
|
+
for (let j = 1; j <= b.length; j++) {
|
|
181
|
+
const next = Math.min(prev[j] + 1, prev[j - 1] + 1, carry + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
182
|
+
carry = prev[j];
|
|
183
|
+
prev[j] = next;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return prev[b.length];
|
|
187
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svgrid/grid",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.22",
|
|
4
4
|
"description": "Svelte 5-native data grid and data table. Headless-first engine with virtual scrolling for 100k+ rows, Excel-style filtering, inline editing, grouping, tree data, pivot, and server-side data. Drop-in <SvGrid> component. TypeScript, SSR-ready.",
|
|
5
5
|
"author": "jQWidgets <boikom@jqwidgets.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -138,5 +138,8 @@
|
|
|
138
138
|
},
|
|
139
139
|
"peerDependencies": {
|
|
140
140
|
"svelte": "^5.0.0"
|
|
141
|
+
},
|
|
142
|
+
"dependencies": {
|
|
143
|
+
"esm-env": "^1.2.2"
|
|
141
144
|
}
|
|
142
145
|
}
|
package/src/GridMenus.svelte
CHANGED
|
@@ -450,18 +450,23 @@
|
|
|
450
450
|
</button>
|
|
451
451
|
{/if}
|
|
452
452
|
<div class="sv-grid-menu-sep"></div>
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
453
|
+
<!-- `resizable: false` means the width is part of the layout, so the
|
|
454
|
+
menu must not offer to change it either - otherwise removing the
|
|
455
|
+
drag handle just moves the same action one click away. -->
|
|
456
|
+
{#if ctrl.columnResizable(menuColumnId)}
|
|
457
|
+
<button
|
|
458
|
+
type="button"
|
|
459
|
+
class="sv-grid-menu-item"
|
|
460
|
+
role="menuitem"
|
|
461
|
+
onclick={() => {
|
|
462
|
+
autosizeColumn(menuColumnId);
|
|
463
|
+
closeMenus();
|
|
464
|
+
}}
|
|
465
|
+
>
|
|
466
|
+
<span class="sv-grid-header-icon">{@render icon("autosize")}</span> Autosize
|
|
467
|
+
this column
|
|
468
|
+
</button>
|
|
469
|
+
{/if}
|
|
465
470
|
<button
|
|
466
471
|
type="button"
|
|
467
472
|
class="sv-grid-menu-item"
|