@svgrid/grid 2.6.22 → 2.6.23

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 (115) hide show
  1. package/dist/GridMenus.svelte +184 -6
  2. package/dist/SvAutoComplete.svelte +4 -0
  3. package/dist/SvComboBox.svelte +5 -1
  4. package/dist/SvDateRangeInput.svelte +25 -3
  5. package/dist/SvDropDownList.svelte +4 -0
  6. package/dist/SvForm.svelte +8 -8
  7. package/dist/SvGrid.controller.svelte.d.ts +48 -1
  8. package/dist/SvGrid.controller.svelte.js +214 -52
  9. package/dist/SvGrid.css +221 -0
  10. package/dist/SvGrid.helpers.d.ts +55 -0
  11. package/dist/SvGrid.helpers.js +83 -0
  12. package/dist/SvGrid.svelte +145 -0
  13. package/dist/SvGrid.types.d.ts +105 -0
  14. package/dist/SvGridCellEditor.svelte +99 -17
  15. package/dist/SvGridSelect.svelte +6 -2
  16. package/dist/SvGridSelect.svelte.d.ts +1 -1
  17. package/dist/SvListBox.svelte +8 -0
  18. package/dist/SvMultiSelect.svelte +6 -2
  19. package/dist/SvMultiSelect.svelte.d.ts +1 -1
  20. package/dist/SvNumberInput.svelte +4 -0
  21. package/dist/SvRichCell.svelte +125 -0
  22. package/dist/SvRichCell.svelte.d.ts +15 -0
  23. package/dist/SvTextArea.svelte +5 -1
  24. package/dist/SvTreeSelect.svelte +6 -2
  25. package/dist/SvTreeSelect.svelte.d.ts +1 -1
  26. package/dist/builtin-editors.d.ts +2 -2
  27. package/dist/builtin-editors.js +19 -3
  28. package/dist/cdn/GridMenus-BzWXQjv3.js +600 -0
  29. package/dist/cdn/GridMenus-xr_rHx8F.js +601 -0
  30. package/dist/cdn/SvDateRangeInput-CaOuMs8O.js +198 -0
  31. package/dist/cdn/SvDateRangeInput-DMLKmGEc.js +199 -0
  32. package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +816 -0
  33. package/dist/cdn/SvDateTimePicker-sonaH0oh.js +812 -0
  34. package/dist/cdn/SvGridCellEditor-BAPSC7EL.js +550 -0
  35. package/dist/cdn/SvGridCellEditor-BwzmUWtL.js +549 -0
  36. package/dist/cdn/date-format-BNii4zeD.js +1393 -0
  37. package/dist/cdn/date-format-BnnHlqGw.js +1395 -0
  38. package/dist/cdn/{editor-registry-CiI0xlKg.js → editor-registry-BhgE3S84.js} +51 -25
  39. package/dist/cdn/{src-D1lXwq1l.js → src-DGo7IHug.js} +5730 -5198
  40. package/dist/cdn/{src-C9Hihx1W.js → src-skQN5eqh.js} +7216 -6684
  41. package/dist/cdn/svgrid.js +14 -12
  42. package/dist/cdn/svgrid.svelte-external.js +14 -12
  43. package/dist/cell-values.d.ts +16 -0
  44. package/dist/cell-values.js +28 -0
  45. package/dist/clipboard.d.ts +22 -0
  46. package/dist/clipboard.js +405 -27
  47. package/dist/editing.js +12 -7
  48. package/dist/editors/cell-editors.d.ts +11 -0
  49. package/dist/editors/cell-editors.js +18 -1
  50. package/dist/grid-messages.d.ts +15 -0
  51. package/dist/grid-messages.js +15 -0
  52. package/dist/index.d.ts +4 -0
  53. package/dist/index.js +4 -0
  54. package/dist/list-option.d.ts +6 -0
  55. package/dist/menus.js +40 -7
  56. package/dist/sanitize-html.d.ts +37 -0
  57. package/dist/sanitize-html.js +234 -0
  58. package/dist/selection-bar-view.svelte.d.ts +25 -0
  59. package/dist/selection-bar-view.svelte.js +13 -0
  60. package/dist/selection.d.ts +2 -1
  61. package/dist/selection.js +129 -13
  62. package/package.json +1 -1
  63. package/src/GridMenus.svelte +184 -6
  64. package/src/SvAutoComplete.svelte +4 -0
  65. package/src/SvComboBox.svelte +5 -1
  66. package/src/SvDateRangeInput.svelte +25 -3
  67. package/src/SvDropDownList.svelte +4 -0
  68. package/src/SvForm.svelte +8 -8
  69. package/src/SvGrid.controller.svelte.ts +251 -61
  70. package/src/SvGrid.css +221 -0
  71. package/src/SvGrid.helpers.ts +87 -0
  72. package/src/SvGrid.svelte +145 -0
  73. package/src/SvGrid.types.ts +112 -0
  74. package/src/SvGridCellEditor.svelte +99 -17
  75. package/src/SvGridSelect.svelte +6 -2
  76. package/src/SvListBox.svelte +8 -0
  77. package/src/SvMultiSelect.svelte +6 -2
  78. package/src/SvNumberInput.svelte +4 -0
  79. package/src/SvRichCell.svelte +125 -0
  80. package/src/SvTextArea.svelte +5 -1
  81. package/src/SvTreeSelect.svelte +6 -2
  82. package/src/builtin-editors.test.ts +18 -1
  83. package/src/builtin-editors.ts +19 -3
  84. package/src/cell-values.ts +30 -0
  85. package/src/clipboard.test.ts +93 -20
  86. package/src/clipboard.ts +433 -33
  87. package/src/date-string-column.test.ts +87 -0
  88. package/src/editing.test.ts +25 -0
  89. package/src/editing.ts +12 -9
  90. package/src/editor-block-prop.test.ts +132 -0
  91. package/src/editors/cell-editors.ts +27 -1
  92. package/src/grid-messages.ts +34 -0
  93. package/src/index.ts +13 -0
  94. package/src/list-option.ts +6 -0
  95. package/src/menus.test.ts +43 -0
  96. package/src/menus.ts +37 -7
  97. package/src/move-cells.test.ts +850 -0
  98. package/src/number-editor-literal.test.ts +84 -0
  99. package/src/number-editor.grid.test.ts +160 -0
  100. package/src/sanitize-html.test.ts +182 -0
  101. package/src/sanitize-html.ts +235 -0
  102. package/src/selection-bar-view.svelte.ts +36 -0
  103. package/src/selection.test.ts +157 -0
  104. package/src/selection.ts +131 -13
  105. package/src/svgrid.filter-menu-listbox.svelte.test.ts +56 -10
  106. package/src/svgrid.in-suggest-snapshot.svelte.test.ts +91 -0
  107. package/src/svgrid.menu-scroll-close.test.ts +204 -0
  108. package/src/svgrid.selection-bar-seam.test.ts +185 -0
  109. package/src/svgrid.upsell-license.test.ts +117 -0
  110. package/dist/cdn/GridMenus-BuoBPqxx.js +0 -493
  111. package/dist/cdn/GridMenus-n4llxoOI.js +0 -494
  112. package/dist/cdn/SvDateTimePicker-CHnUkWcH.js +0 -2206
  113. package/dist/cdn/SvDateTimePicker-CRQH_U7E.js +0 -2201
  114. package/dist/cdn/SvGridCellEditor-BGUCzuPB.js +0 -523
  115. package/dist/cdn/SvGridCellEditor-G8IMWIws.js +0 -522
@@ -18,6 +18,36 @@ export function getColumnBaseValue<TData extends RowData>(row: Row<TData>, colum
18
18
  return row.getCellValueByColumnId(column.id);
19
19
  }
20
20
 
21
+ /**
22
+ * Map a visible row back to its slot in the data array.
23
+ *
24
+ * The bulk writers (paste, clear) rebuild `internalData` immutably, so unlike
25
+ * the inline editor - which mutates `row.original` in place and never needs an
26
+ * index - they have to locate the slot first. They used to read it as
27
+ * `Number(row.id)`, which is only the array index under the DEFAULT `getRowId`.
28
+ * A grid with `getRowId={(t) => String(t.id)}` over 1-based ids wrote one row
29
+ * BELOW the selection on every paste, and one with non-numeric ids ('PLAT-412')
30
+ * parsed to NaN and dropped the write silently.
31
+ *
32
+ * Identity is the reliable key: the row model's `original` IS the object in
33
+ * `data`. Returns a lookup rather than a per-row scan so a multi-row paste
34
+ * stays O(n) instead of O(n * rows).
35
+ */
36
+ export function createDataIndexLookup<TData extends RowData>(
37
+ data: ReadonlyArray<TData>,
38
+ ): (row: Row<TData>) => number {
39
+ const byRef = new Map<unknown, number>();
40
+ for (let i = 0; i < data.length; i += 1) {
41
+ // First slot wins: with a duplicated object reference in `data`, writing
42
+ // the earlier one matches what the row model itself resolves to.
43
+ if (!byRef.has(data[i])) byRef.set(data[i], i);
44
+ }
45
+ return (row) => {
46
+ const hit = byRef.get(row?.original);
47
+ return hit === undefined ? -1 : hit;
48
+ };
49
+ }
50
+
21
51
  export function isGroupRow<TData extends RowData>(row: Row<TData>) {
22
52
  // Tree rows are expandable too, but they are real data rows - rendering one as
23
53
  // a full-width banner would swallow its cells. Only grouping's synthetic rows
@@ -20,6 +20,12 @@ interface FakeOptions {
20
20
  groupRows?: Set<number>
21
21
  /** Predicate for editability; default true. */
22
22
  editableAt?: (r: number, c: number) => boolean
23
+ /**
24
+ * Data indices in DISPLAY order, i.e. what a sort/filter produces. Default is
25
+ * `[0, 1, 2, …]`, where display and data order coincide - which is exactly
26
+ * the case that hides row-index bugs, so the ordering tests pass a permutation.
27
+ */
28
+ displayOrder?: number[]
23
29
  }
24
30
 
25
31
  function makeCtx(opts: FakeOptions = {}) {
@@ -41,15 +47,22 @@ function makeCtx(opts: FakeOptions = {}) {
41
47
  },
42
48
  }))
43
49
 
44
- // allRows mirror internalData by index; row.id === index as a string.
45
- const buildRows = () =>
46
- data.map((row, index) => ({
47
- id: String(index),
48
- original: row,
49
- getCanExpand: () => opts.groupRows?.has(index) ?? false,
50
- getCellValueByColumnId: (colId: string) =>
51
- (row as Record<string, unknown>)[colId],
52
- }))
50
+ // allRows are the rows AS DISPLAYED. By default that mirrors internalData by
51
+ // index (row.id === index as a string); `displayOrder` reorders them the way
52
+ // a sort or filter would, leaving `original` pointing at the same objects.
53
+ const rowsFrom = (source: Array<Record<string, unknown>>) => {
54
+ const order = opts.displayOrder ?? source.map((_, i) => i)
55
+ return order
56
+ .filter((dataIndex) => source[dataIndex] !== undefined)
57
+ .map((dataIndex) => ({
58
+ id: String(dataIndex),
59
+ original: source[dataIndex],
60
+ getCanExpand: () => opts.groupRows?.has(dataIndex) ?? false,
61
+ getCellValueByColumnId: (colId: string) =>
62
+ (source[dataIndex] as Record<string, unknown>)[colId],
63
+ }))
64
+ }
65
+ const buildRows = () => rowsFrom(data)
53
66
 
54
67
  let activeCell: { rowIndex: number; colIndex: number } | null = null
55
68
 
@@ -107,13 +120,7 @@ function makeCtx(opts: FakeOptions = {}) {
107
120
  get: () => backing,
108
121
  set: (next: Array<Record<string, unknown>>) => {
109
122
  backing = next
110
- ctx._rows = next.map((row, index) => ({
111
- id: String(index),
112
- original: row,
113
- getCanExpand: () => opts.groupRows?.has(index) ?? false,
114
- getCellValueByColumnId: (colId: string) =>
115
- (row as Record<string, unknown>)[colId],
116
- }))
123
+ ctx._rows = rowsFrom(next)
117
124
  },
118
125
  })
119
126
 
@@ -195,6 +202,53 @@ describe('writeCellRaw', () => {
195
202
  cb.writeCellRaw(0, 'noField', 5)
196
203
  expect(ctx.internalData[0]).toEqual({ a: 1 })
197
204
  })
205
+
206
+ describe('when the view is sorted or filtered (display order != data order)', () => {
207
+ // `rowIndex` is a DISPLAY index. These functions used to index
208
+ // `internalData` with it, which is the same row only while the two orders
209
+ // coincide - so the fill handle and enterprise bulk edit read from one row
210
+ // and wrote to another as soon as a column was sorted.
211
+ const sorted = () =>
212
+ makeCtx({
213
+ columns: [{ id: 'a', field: 'a', editable: true, editorType: 'text' }],
214
+ data: [{ a: 'data0' }, { a: 'data1' }, { a: 'data2' }],
215
+ displayOrder: [2, 0, 1],
216
+ })
217
+
218
+ it('readCellRaw reads the row shown at that position', () => {
219
+ const cb = createClipboard(sorted())
220
+ expect(cb.readCellRaw(0, 'a')).toBe('data2')
221
+ expect(cb.readCellRaw(1, 'a')).toBe('data0')
222
+ expect(cb.readCellRaw(2, 'a')).toBe('data1')
223
+ })
224
+
225
+ it('writeCellRaw writes the row shown at that position', () => {
226
+ const ctx = sorted()
227
+ const cb = createClipboard(ctx)
228
+ cb.writeCellRaw(0, 'a', 'WRITTEN')
229
+ expect(ctx.internalData.map((d: any) => d.a)).toEqual([
230
+ 'data0',
231
+ 'data1',
232
+ 'WRITTEN',
233
+ ])
234
+ })
235
+
236
+ it('reports the position in props.data to onCellValueChange', () => {
237
+ const ctx = sorted()
238
+ const onCellValueChange = vi.fn()
239
+ ctx.props.onCellValueChange = onCellValueChange
240
+ createClipboard(ctx).writeCellRaw(0, 'a', 'WRITTEN')
241
+ expect(onCellValueChange).toHaveBeenCalledWith(
242
+ expect.objectContaining({ rowIndex: 2, oldValue: 'data2', newValue: 'WRITTEN' }),
243
+ )
244
+ })
245
+
246
+ it('records the edit under the displayed row id', () => {
247
+ const ctx = sorted()
248
+ createClipboard(ctx).writeCellRaw(1, 'a', 'WRITTEN')
249
+ expect(ctx.editedCellValues['0:a']).toBe('WRITTEN')
250
+ })
251
+ })
198
252
  })
199
253
 
200
254
  describe('clearSelectedCellValues', () => {
@@ -753,14 +807,14 @@ describe('clearSelectedCells', () => {
753
807
  expect(ctx.internalData[0].a).toBe('x')
754
808
  })
755
809
 
756
- it('skips rows whose id maps outside the data bounds', () => {
810
+ it('skips a row whose data object is not in the data array', () => {
757
811
  const ctx = makeCtx({
758
812
  columns: [{ id: 'a', field: 'a', editable: true, editorType: 'text' }],
759
813
  data: [{ a: 'x' }],
760
814
  })
761
- // Force the row's id to a non-numeric value so Number(id) is NaN
762
- // and the integer/bounds guard skips it.
763
- ctx._rows[0].id = 'not-a-number'
815
+ // A row model pointing at an object that is no longer in `internalData`
816
+ // (a stale model after data was replaced) has no slot to write to.
817
+ ctx._rows[0].original = { a: 'stale' }
764
818
  ctx.selectionRange = {
765
819
  anchor: { rowIndex: 0, colIndex: 0 },
766
820
  focus: { rowIndex: 0, colIndex: 0 },
@@ -769,6 +823,25 @@ describe('clearSelectedCells', () => {
769
823
  expect(cb.clearSelectedCells()).toBe(false)
770
824
  expect(ctx.internalData[0].a).toBe('x')
771
825
  })
826
+
827
+ it('clears the selected row when getRowId does not return the array index', () => {
828
+ // The bug this guards: the row -> data-slot lookup used to be
829
+ // `Number(row.id)`, which is the array index only under the DEFAULT
830
+ // getRowId. Ids that are 1-based (or not numbers at all) cleared the row
831
+ // below the selection, or silently cleared nothing.
832
+ const ctx = makeCtx({
833
+ columns: [{ id: 'a', field: 'a', editable: true, editorType: 'text' }],
834
+ data: [{ a: 'x0' }, { a: 'x1' }, { a: 'x2' }],
835
+ })
836
+ ctx._rows.forEach((r: any, i: number) => { r.id = `PLAT-${i + 1}` })
837
+ ctx.selectionRange = {
838
+ anchor: { rowIndex: 1, colIndex: 0 },
839
+ focus: { rowIndex: 1, colIndex: 0 },
840
+ }
841
+ const cb = createClipboard(ctx)
842
+ expect(cb.clearSelectedCells()).toBe(true)
843
+ expect(ctx.internalData.map((d: any) => d.a)).toEqual(['x0', '', 'x2'])
844
+ })
772
845
  })
773
846
 
774
847
  describe('cutSelectionToClipboard', () => {