@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.
Files changed (110) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +22 -0
  3. package/dist/GridMenus.svelte +17 -12
  4. package/dist/SvGrid.controller.svelte.d.ts +14 -8
  5. package/dist/SvGrid.controller.svelte.js +214 -74
  6. package/dist/SvGrid.css +1 -1
  7. package/dist/SvGrid.svelte +112 -57
  8. package/dist/SvGrid.types.d.ts +191 -1
  9. package/dist/ai.d.ts +28 -0
  10. package/dist/ai.js +6 -0
  11. package/dist/cdn/{GridMenus-B0F9iBrG.js → GridMenus-BuoBPqxx.js} +137 -132
  12. package/dist/cdn/GridMenus-n4llxoOI.js +494 -0
  13. package/dist/cdn/column-resize-DsfNXMom.js +102 -0
  14. package/dist/cdn/row-resize-BRcimkUT.js +95 -0
  15. package/dist/cdn/{src-Cd0tearp.js → src-C9Hihx1W.js} +3497 -3487
  16. package/dist/cdn/{src-B1TdiyS8.js → src-D1lXwq1l.js} +8258 -8248
  17. package/dist/cdn/svgrid.js +10 -8
  18. package/dist/cdn/svgrid.svelte-external.js +10 -8
  19. package/dist/cdn/validate-_CDJzgIo.js +75 -0
  20. package/dist/cell-formatting.d.ts +2 -0
  21. package/dist/cell-formatting.js +2 -0
  22. package/dist/chart-export.d.ts +1 -0
  23. package/dist/chart.d.ts +31 -5
  24. package/dist/chart.js +9 -3
  25. package/dist/column-groups.js +1 -1
  26. package/dist/column-resize.d.ts +46 -0
  27. package/dist/column-resize.js +205 -0
  28. package/dist/columns.d.ts +0 -3
  29. package/dist/columns.js +0 -57
  30. package/dist/core.d.ts +216 -4
  31. package/dist/core.js +532 -119
  32. package/dist/createTree.svelte.d.ts +3 -0
  33. package/dist/createTree.svelte.js +1 -0
  34. package/dist/datetime/date-core.d.ts +2 -0
  35. package/dist/datetime/date-restrict.d.ts +1 -0
  36. package/dist/datetime/timezone.d.ts +1 -0
  37. package/dist/dock-manager-model.d.ts +3 -0
  38. package/dist/dock-manager-model.js +1 -0
  39. package/dist/dock-model.d.ts +6 -0
  40. package/dist/dock-model.js +3 -0
  41. package/dist/editor-contract.d.ts +1 -0
  42. package/dist/filtering/excel-filters.js +28 -0
  43. package/dist/group-display.d.ts +1 -1
  44. package/dist/index.d.ts +2 -1
  45. package/dist/index.js +6 -0
  46. package/dist/list-option.d.ts +1 -0
  47. package/dist/menus.js +1 -1
  48. package/dist/positioning.d.ts +2 -0
  49. package/dist/row-resize.d.ts +11 -0
  50. package/dist/row-resize.js +7 -1
  51. package/dist/scheduler-ical.d.ts +1 -0
  52. package/dist/scheduler-model.d.ts +1 -0
  53. package/dist/selection.js +9 -0
  54. package/dist/spreadsheet.d.ts +1 -1
  55. package/dist/spreadsheet.js +1 -1
  56. package/dist/summaries.js +22 -0
  57. package/dist/svgrid-wrapper.types.d.ts +5 -0
  58. package/dist/toast-store.svelte.d.ts +4 -0
  59. package/dist/validate.d.ts +50 -0
  60. package/dist/validate.js +187 -0
  61. package/package.json +4 -1
  62. package/src/GridMenus.svelte +17 -12
  63. package/src/SvGrid.controller.svelte.ts +223 -76
  64. package/src/SvGrid.css +1 -1
  65. package/src/SvGrid.svelte +112 -57
  66. package/src/SvGrid.types.ts +191 -1
  67. package/src/ai.ts +28 -0
  68. package/src/cell-formatting.ts +2 -0
  69. package/src/chart-export.ts +1 -0
  70. package/src/chart.ts +31 -5
  71. package/src/column-groups.ts +1 -1
  72. package/src/column-resize.test.ts +381 -0
  73. package/src/column-resize.ts +227 -0
  74. package/src/columns.test.ts +0 -103
  75. package/src/columns.ts +0 -58
  76. package/src/core.aggregate.test.ts +134 -0
  77. package/src/core.filter.test.ts +156 -0
  78. package/src/core.grouping.test.ts +146 -0
  79. package/src/core.row-shape.test.ts +119 -0
  80. package/src/core.rowmodel-cache.test.ts +121 -0
  81. package/src/core.sort.test.ts +293 -0
  82. package/src/core.ts +723 -119
  83. package/src/createTree.svelte.ts +3 -0
  84. package/src/datetime/date-core.ts +2 -0
  85. package/src/datetime/date-restrict.ts +1 -0
  86. package/src/datetime/timezone.ts +1 -0
  87. package/src/dock-manager-model.ts +3 -0
  88. package/src/dock-model.ts +6 -0
  89. package/src/editor-contract.ts +1 -0
  90. package/src/filtering/excel-filters.ts +30 -0
  91. package/src/filtering/normalize-fast-path.test.ts +104 -0
  92. package/src/group-display.ts +1 -1
  93. package/src/index.ts +12 -1
  94. package/src/list-option.ts +1 -0
  95. package/src/menus.ts +1 -1
  96. package/src/positioning.ts +2 -0
  97. package/src/resize-props.test.ts +361 -0
  98. package/src/row-resize.test.ts +31 -0
  99. package/src/row-resize.ts +21 -3
  100. package/src/scheduler-ical.ts +1 -0
  101. package/src/scheduler-model.ts +1 -0
  102. package/src/selection.ts +9 -0
  103. package/src/spreadsheet.ts +1 -1
  104. package/src/summaries.ts +21 -0
  105. package/src/svgrid-wrapper.types.ts +5 -0
  106. package/src/svgrid.summaries.test.ts +217 -0
  107. package/src/toast-store.svelte.ts +4 -0
  108. package/src/validate.test.ts +207 -0
  109. package/src/validate.ts +269 -0
  110. package/dist/cdn/GridMenus-IHK_l7m6.js +0 -489
@@ -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.20",
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
  }
@@ -450,18 +450,23 @@
450
450
  </button>
451
451
  {/if}
452
452
  <div class="sv-grid-menu-sep"></div>
453
- <button
454
- type="button"
455
- class="sv-grid-menu-item"
456
- role="menuitem"
457
- onclick={() => {
458
- autosizeColumn(menuColumnId);
459
- closeMenus();
460
- }}
461
- >
462
- <span class="sv-grid-header-icon">{@render icon("autosize")}</span> Autosize
463
- this column
464
- </button>
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"