@svgrid/grid 1.1.0 → 1.1.2

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 (148) hide show
  1. package/dist/FlexRender.svelte +96 -96
  2. package/dist/GridMenus.svelte +205 -127
  3. package/dist/SvGrid.controller.svelte.d.ts +44 -1
  4. package/dist/SvGrid.controller.svelte.js +240 -44
  5. package/dist/SvGrid.css +325 -22
  6. package/dist/SvGrid.helpers.test.d.ts +1 -0
  7. package/dist/SvGrid.helpers.test.js +298 -0
  8. package/dist/SvGrid.svelte +729 -203
  9. package/dist/SvGrid.types.d.ts +91 -4
  10. package/dist/aligned-grids.d.ts +6 -0
  11. package/dist/aligned-grids.js +84 -0
  12. package/dist/aligned-grids.test.d.ts +1 -0
  13. package/dist/aligned-grids.test.js +75 -0
  14. package/dist/build-api.coverage.test.d.ts +20 -0
  15. package/dist/build-api.coverage.test.js +505 -0
  16. package/dist/build-api.js +61 -31
  17. package/dist/cell-render.test.d.ts +1 -0
  18. package/dist/cell-render.test.js +338 -0
  19. package/dist/cell-values.d.ts +1 -1
  20. package/dist/cell-values.js +7 -7
  21. package/dist/chart-export.test.d.ts +1 -0
  22. package/dist/chart-export.test.js +302 -0
  23. package/dist/chart.coverage.test.d.ts +1 -0
  24. package/dist/chart.coverage.test.js +748 -0
  25. package/dist/clipboard.js +88 -24
  26. package/dist/clipboard.test.d.ts +1 -0
  27. package/dist/clipboard.test.js +700 -0
  28. package/dist/collaboration.coverage.test.d.ts +1 -0
  29. package/dist/collaboration.coverage.test.js +200 -0
  30. package/dist/column-groups.d.ts +19 -0
  31. package/dist/column-groups.js +62 -0
  32. package/dist/column-groups.test.d.ts +1 -0
  33. package/dist/column-groups.test.js +56 -0
  34. package/dist/column-types.d.ts +10 -0
  35. package/dist/column-types.js +63 -0
  36. package/dist/column-types.test.d.ts +1 -0
  37. package/dist/column-types.test.js +62 -0
  38. package/dist/columns.test.d.ts +1 -0
  39. package/dist/columns.test.js +625 -0
  40. package/dist/core.d.ts +86 -1
  41. package/dist/core.js +2 -2
  42. package/dist/editing.d.ts +7 -0
  43. package/dist/editing.js +191 -5
  44. package/dist/editing.test.d.ts +1 -0
  45. package/dist/editing.test.js +732 -0
  46. package/dist/editors/cell-editors.coverage.test.d.ts +1 -0
  47. package/dist/editors/cell-editors.coverage.test.js +139 -0
  48. package/dist/facet-buckets.test.d.ts +1 -0
  49. package/dist/facet-buckets.test.js +296 -0
  50. package/dist/filter-operators.test.d.ts +1 -0
  51. package/dist/filter-operators.test.js +135 -0
  52. package/dist/hyperformula-adapter.test.d.ts +1 -0
  53. package/dist/hyperformula-adapter.test.js +205 -0
  54. package/dist/index.d.ts +5 -2
  55. package/dist/index.js +4 -1
  56. package/dist/keyboard-handlers.coverage.test.d.ts +1 -0
  57. package/dist/keyboard-handlers.coverage.test.js +495 -0
  58. package/dist/keyboard-handlers.js +7 -2
  59. package/dist/menus.js +1 -0
  60. package/dist/menus.test.d.ts +1 -0
  61. package/dist/menus.test.js +560 -0
  62. package/dist/named-views.coverage.test.d.ts +1 -0
  63. package/dist/named-views.coverage.test.js +180 -0
  64. package/dist/row-drag.d.ts +49 -0
  65. package/dist/row-drag.js +221 -0
  66. package/dist/row-drag.test.d.ts +1 -0
  67. package/dist/row-drag.test.js +142 -0
  68. package/dist/row-resize.test.d.ts +1 -0
  69. package/dist/row-resize.test.js +329 -0
  70. package/dist/scroll-sync.js +3 -0
  71. package/dist/scroll-sync.test.d.ts +1 -0
  72. package/dist/scroll-sync.test.js +290 -0
  73. package/dist/selection.d.ts +7 -1
  74. package/dist/selection.js +76 -36
  75. package/dist/selection.multi-range.test.d.ts +1 -0
  76. package/dist/selection.multi-range.test.js +55 -0
  77. package/dist/selection.test.d.ts +1 -0
  78. package/dist/selection.test.js +647 -0
  79. package/dist/server-data-source.coverage.test.d.ts +1 -0
  80. package/dist/server-data-source.coverage.test.js +154 -0
  81. package/dist/spreadsheet.d.ts +30 -0
  82. package/dist/spreadsheet.js +48 -0
  83. package/dist/spreadsheet.test.d.ts +1 -0
  84. package/dist/spreadsheet.test.js +446 -0
  85. package/dist/summaries.js +4 -4
  86. package/dist/sv-grid-scrollbar.js +13 -1
  87. package/dist/svgrid-wrapper.types.d.ts +19 -0
  88. package/dist/svgrid.behavior.test.js +20 -0
  89. package/dist/svgrid.interaction.test.js +31 -0
  90. package/dist/svgrid.new-features.wrapper.test.js +34 -2
  91. package/dist/test-setup.js +9 -3
  92. package/dist/virtualization/scroll-scaling.d.ts +17 -0
  93. package/dist/virtualization/scroll-scaling.js +35 -0
  94. package/dist/virtualization/scroll-scaling.test.js +42 -1
  95. package/package.json +2 -1
  96. package/src/FlexRender.svelte +96 -96
  97. package/src/GridMenus.svelte +205 -127
  98. package/src/SvGrid.controller.svelte.ts +204 -36
  99. package/src/SvGrid.css +277 -12
  100. package/src/SvGrid.svelte +727 -205
  101. package/src/SvGrid.types.ts +85 -4
  102. package/src/a11y.contract.test.ts +49 -49
  103. package/src/a11y.test.ts +59 -59
  104. package/src/a11y.ts +59 -59
  105. package/src/aligned-grids.test.ts +80 -0
  106. package/src/aligned-grids.ts +87 -0
  107. package/src/build-api.ts +43 -23
  108. package/src/cell-formatting.ts +169 -169
  109. package/src/cell-values.ts +4 -4
  110. package/src/clipboard.test.ts +49 -0
  111. package/src/clipboard.ts +51 -23
  112. package/src/column-groups.test.ts +59 -0
  113. package/src/column-groups.ts +80 -0
  114. package/src/column-types.test.ts +68 -0
  115. package/src/column-types.ts +82 -0
  116. package/src/core.performance.test.ts +30 -30
  117. package/src/core.ts +1077 -999
  118. package/src/createGrid.svelte.ts +42 -42
  119. package/src/createGrid.test.ts +10 -10
  120. package/src/createGridState.svelte.ts +17 -17
  121. package/src/editing.ts +161 -5
  122. package/src/flex-render.ts +3 -3
  123. package/src/index.ts +208 -196
  124. package/src/keyboard.test.ts +59 -59
  125. package/src/keyboard.ts +97 -97
  126. package/src/menus.ts +1 -0
  127. package/src/merge-objects.ts +48 -48
  128. package/src/render-component.ts +28 -28
  129. package/src/row-drag.test.ts +168 -0
  130. package/src/row-drag.ts +255 -0
  131. package/src/scroll-sync.ts +2 -0
  132. package/src/selection.multi-range.test.ts +61 -0
  133. package/src/selection.ts +71 -37
  134. package/src/spreadsheet.test.ts +46 -2
  135. package/src/spreadsheet.ts +58 -0
  136. package/src/static-functions.ts +11 -11
  137. package/src/subscribe.ts +38 -38
  138. package/src/summaries.ts +4 -4
  139. package/src/svgrid-wrapper.types.ts +412 -393
  140. package/src/svgrid.features.test.ts +157 -157
  141. package/src/svgrid.new-features.wrapper.test.ts +2 -2
  142. package/src/svgrid.wrapper.test.ts +40 -40
  143. package/src/virtualization/column-virtualizer.test.ts +27 -27
  144. package/src/virtualization/column-virtualizer.ts +30 -30
  145. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  146. package/src/virtualization/types.ts +30 -30
  147. package/src/virtualization/virtualizer.test.ts +47 -47
  148. package/src/virtualization/virtualizer.ts +296 -296
@@ -1,42 +1,42 @@
1
- import { createSvGridCore } from './core'
2
- import type {
3
- RowData,
4
- SvGrid,
5
- SvGridOptions,
6
- TableFeatures,
7
- } from './core'
8
-
9
- export type SvelteGrid<
10
- TFeatures extends TableFeatures,
11
- TData extends RowData,
12
- TSelected = {},
13
- > = SvGrid<TData> & {
14
- readonly state: Readonly<TSelected>
15
- }
16
-
17
- export function createSvGrid<
18
- TFeatures extends TableFeatures,
19
- TData extends RowData,
20
- TSelected = {},
21
- >(
22
- gridOptions: SvGridOptions<TFeatures, TData>,
23
- selector: (state: Record<string, any>) => TSelected = () => ({}) as TSelected,
24
- ): SvelteGrid<TFeatures, TData, TSelected> {
25
- const grid = createSvGridCore(gridOptions) as SvelteGrid<TFeatures, TData, TSelected>
26
- let selected = $state(selector(grid.store.state))
27
- grid.store.subscribe(() => {
28
- selected = selector(grid.store.state)
29
- })
30
-
31
- Object.defineProperty(grid, 'state', {
32
- get() {
33
- return selected
34
- },
35
- configurable: true,
36
- enumerable: true,
37
- })
38
-
39
- return grid
40
- }
41
-
42
- export const createGrid = createSvGrid
1
+ import { createSvGridCore } from './core'
2
+ import type {
3
+ RowData,
4
+ SvGrid,
5
+ SvGridOptions,
6
+ TableFeatures,
7
+ } from './core'
8
+
9
+ export type SvelteGrid<
10
+ TFeatures extends TableFeatures,
11
+ TData extends RowData,
12
+ TSelected = {},
13
+ > = SvGrid<TData> & {
14
+ readonly state: Readonly<TSelected>
15
+ }
16
+
17
+ export function createSvGrid<
18
+ TFeatures extends TableFeatures,
19
+ TData extends RowData,
20
+ TSelected = {},
21
+ >(
22
+ gridOptions: SvGridOptions<TFeatures, TData>,
23
+ selector: (state: Record<string, any>) => TSelected = () => ({}) as TSelected,
24
+ ): SvelteGrid<TFeatures, TData, TSelected> {
25
+ const grid = createSvGridCore(gridOptions) as SvelteGrid<TFeatures, TData, TSelected>
26
+ let selected = $state(selector(grid.store.state))
27
+ grid.store.subscribe(() => {
28
+ selected = selector(grid.store.state)
29
+ })
30
+
31
+ Object.defineProperty(grid, 'state', {
32
+ get() {
33
+ return selected
34
+ },
35
+ configurable: true,
36
+ enumerable: true,
37
+ })
38
+
39
+ return grid
40
+ }
41
+
42
+ export const createGrid = createSvGrid
@@ -1,10 +1,10 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { createGridState } from './index'
3
-
4
- describe('createGrid', () => {
5
- it('createGridState updates controlled values', () => {
6
- const [sorting, setSorting] = createGridState([{ id: 'age', desc: false }])
7
- setSorting((prev) => prev.map((entry) => ({ ...entry, desc: true })))
8
- expect(sorting()[0]?.desc).toBe(true)
9
- })
10
- })
1
+ import { describe, expect, it } from 'vitest'
2
+ import { createGridState } from './index'
3
+
4
+ describe('createGrid', () => {
5
+ it('createGridState updates controlled values', () => {
6
+ const [sorting, setSorting] = createGridState([{ id: 'age', desc: false }])
7
+ setSorting((prev) => prev.map((entry) => ({ ...entry, desc: true })))
8
+ expect(sorting()[0]?.desc).toBe(true)
9
+ })
10
+ })
@@ -1,17 +1,17 @@
1
- import type { Updater } from './core'
2
-
3
- export function createGridState<TState>(
4
- initialValue: TState,
5
- ): [() => TState, (updater: Updater<TState>) => void] {
6
- let value = $state(initialValue)
7
-
8
- return [
9
- () => value,
10
- (updater: Updater<TState>) => {
11
- if (updater instanceof Function) value = updater(value)
12
- else value = updater
13
- },
14
- ]
15
- }
16
-
17
- export const createSvGridState = createGridState
1
+ import type { Updater } from './core'
2
+
3
+ export function createGridState<TState>(
4
+ initialValue: TState,
5
+ ): [() => TState, (updater: Updater<TState>) => void] {
6
+ let value = $state(initialValue)
7
+
8
+ return [
9
+ () => value,
10
+ (updater: Updater<TState>) => {
11
+ if (updater instanceof Function) value = updater(value)
12
+ else value = updater
13
+ },
14
+ ]
15
+ }
16
+
17
+ export const createSvGridState = createGridState
package/src/editing.ts CHANGED
@@ -251,29 +251,43 @@ export function createEditing<
251
251
  },
252
252
  );
253
253
  let oldValue: unknown = undefined;
254
+ let finalValue: unknown = parsedValue;
254
255
  if (row?.original && column?.columnDef.field) {
255
256
  oldValue = (row.original as Record<string, unknown>)[
256
257
  column.columnDef.field
257
258
  ];
259
+ // Per-column valueParser refines the type-coerced value before storing.
260
+ const parser = (
261
+ column.columnDef as { valueParser?: (p: unknown) => unknown }
262
+ ).valueParser;
263
+ if (parser) {
264
+ finalValue = parser({
265
+ newValue: parsedValue,
266
+ oldValue,
267
+ rawInput: ctx.editingCell.value,
268
+ data: row.original,
269
+ columnId: ctx.editingCell.columnId,
270
+ });
271
+ }
258
272
  (row.original as Record<string, unknown>)[column.columnDef.field] =
259
- parsedValue;
273
+ finalValue;
260
274
  }
261
275
  const key = getCellKey(ctx.editingCell.rowId, ctx.editingCell.columnId);
262
276
  ctx.editedCellValues = {
263
277
  ...ctx.editedCellValues,
264
- [key]: parsedValue,
278
+ [key]: finalValue,
265
279
  };
266
280
  ctx.grid.store.setState((prev: any) => ({ ...prev }));
267
281
  // Record into the history at the current pointer. Any forward
268
282
  // history (steps the user could have redone) is truncated - this
269
283
  // is the standard "edit invalidates redo" rule.
270
- if (oldValue !== parsedValue && row?.original && column?.columnDef.field) {
284
+ if (oldValue !== finalValue && row?.original && column?.columnDef.field) {
271
285
  const step: HistoryStep = {
272
286
  rowId: ctx.editingCell.rowId,
273
287
  columnId: ctx.editingCell.columnId,
274
288
  field: column.columnDef.field as string,
275
289
  before: oldValue,
276
- after: parsedValue,
290
+ after: finalValue,
277
291
  }
278
292
  const truncated = ctx.history.slice(0, ctx.historyPtr + 1)
279
293
  truncated.push(step)
@@ -298,7 +312,7 @@ export function createEditing<
298
312
  rowIndex,
299
313
  columnId: column.id,
300
314
  oldValue,
301
- newValue: parsedValue,
315
+ newValue: finalValue,
302
316
  row: row.original as TData,
303
317
  });
304
318
  }
@@ -368,6 +382,12 @@ export function createEditing<
368
382
  const column = ctx.allColumns[colIndex];
369
383
  if (!row || !column) return;
370
384
  if (isGroupRow(row)) return;
385
+ // Full-row editing: put the whole row into edit instead of one cell.
386
+ if (ctx.props.fullRowEditing) {
387
+ if (ctx.fullRowEdit?.rowId === row.id) return;
388
+ startFullRowEdit(rowIndex);
389
+ return;
390
+ }
371
391
  if (!isCellEditable(column, row)) return;
372
392
  if (ctx.editingCell?.rowId === row.id && ctx.editingCell?.columnId === column.id)
373
393
  return;
@@ -495,12 +515,148 @@ export function createEditing<
495
515
  ctx.grid.store.setState((prev: any) => ({ ...prev }));
496
516
  }
497
517
 
518
+ /** Programmatically begin editing a cell (mirrors a double-click). */
519
+ function startEditing(rowIndex: number, columnId: string): boolean {
520
+ if (!ctx.editingEnabled) return false;
521
+ const row = ctx.allRows[rowIndex];
522
+ if (!row || isGroupRow(row)) return false;
523
+ const colIndex = ctx.allColumns.findIndex((c: any) => c.id === columnId);
524
+ if (colIndex < 0) return false;
525
+ const column = ctx.allColumns[colIndex];
526
+ if (!isCellEditable(column, row)) return false;
527
+ if (
528
+ ctx.editingCell?.rowId === row.id &&
529
+ ctx.editingCell?.columnId === columnId
530
+ )
531
+ return true;
532
+ onCellDoubleClick(rowIndex, colIndex);
533
+ return (
534
+ ctx.editingCell?.rowId === row.id &&
535
+ ctx.editingCell?.columnId === columnId
536
+ );
537
+ }
538
+
539
+ /** Commit (default) or cancel the active edit, if any. */
540
+ function stopEditing(cancel = false): boolean {
541
+ if (!ctx.editingCell) return false;
542
+ if (cancel) {
543
+ ctx.editingCell = null;
544
+ } else {
545
+ saveEditingCell();
546
+ }
547
+ return true;
548
+ }
549
+
550
+ // ---- Full-row editing -------------------------------------------------
551
+ /** Put the whole row into edit mode, seeding a draft per editable column. */
552
+ function startFullRowEdit(rowIndex: number): boolean {
553
+ if (!ctx.editingEnabled || !ctx.props.fullRowEditing) return false;
554
+ const row = ctx.allRows[rowIndex];
555
+ if (!row || isGroupRow(row)) return false;
556
+ if (ctx.fullRowEdit && ctx.fullRowEdit.rowId !== row.id) commitFullRowEdit();
557
+ ctx.editingCell = null; // never both at once
558
+ const draft: Record<string, unknown> = {};
559
+ for (const column of ctx.allColumns) {
560
+ if (!column.columnDef.field) continue;
561
+ if (!isCellEditable(column, row)) continue;
562
+ draft[column.id] = getCellDisplayValue(
563
+ row.id,
564
+ column.id,
565
+ row.getCellValueByColumnId(column.id),
566
+ );
567
+ }
568
+ ctx.fullRowEdit = { rowId: row.id, draft };
569
+ ctx.setActiveCell(rowIndex, Math.max(0, ctx.allColumns.findIndex((c: any) => c.id in draft)));
570
+ return true;
571
+ }
572
+
573
+ /** Stage one column's draft value during full-row editing. */
574
+ function setFullRowDraft(columnId: string, value: unknown): void {
575
+ const fr = ctx.fullRowEdit;
576
+ if (!fr) return;
577
+ ctx.fullRowEdit = { rowId: fr.rowId, draft: { ...fr.draft, [columnId]: value } };
578
+ }
579
+
580
+ /** Commit every drafted column of the full-row edit in one data update. */
581
+ function commitFullRowEdit(): void {
582
+ const fr = ctx.fullRowEdit;
583
+ if (!fr) return;
584
+ const row = ctx.allRows.find((r: any) => r.id === fr.rowId);
585
+ if (!row?.original) {
586
+ ctx.fullRowEdit = null;
587
+ return;
588
+ }
589
+ const changed: Array<{ columnId: string; field: string; before: unknown; after: unknown }> = [];
590
+ for (const column of ctx.allColumns) {
591
+ if (!(column.id in fr.draft)) continue;
592
+ const field = column.columnDef.field as string | undefined;
593
+ if (!field) continue;
594
+ const editorType = (column.columnDef.editorType ?? "text") as CellEditorType;
595
+ const parsed = parseEditorValue(editorType, fr.draft[column.id], {
596
+ multiple: column.columnDef.editorMultiple === true,
597
+ });
598
+ const oldValue = (row.original as Record<string, unknown>)[field];
599
+ const parser = (column.columnDef as { valueParser?: (p: unknown) => unknown }).valueParser;
600
+ const finalValue = parser
601
+ ? parser({
602
+ newValue: parsed,
603
+ oldValue,
604
+ rawInput: String(fr.draft[column.id] ?? ""),
605
+ data: row.original,
606
+ columnId: column.id,
607
+ })
608
+ : parsed;
609
+ if (oldValue !== finalValue) {
610
+ (row.original as Record<string, unknown>)[field] = finalValue;
611
+ const key = getCellKey(fr.rowId, column.id);
612
+ ctx.editedCellValues = { ...ctx.editedCellValues, [key]: finalValue };
613
+ changed.push({ columnId: column.id, field, before: oldValue, after: finalValue });
614
+ }
615
+ }
616
+ // Record the whole-row change as consecutive history steps.
617
+ if (changed.length) {
618
+ let hist = ctx.history.slice(0, ctx.historyPtr + 1);
619
+ for (const c of changed) {
620
+ hist.push({ rowId: fr.rowId, columnId: c.columnId, field: c.field, before: c.before, after: c.after });
621
+ }
622
+ if (hist.length > ctx.UNDO_LIMIT) hist = hist.slice(hist.length - ctx.UNDO_LIMIT);
623
+ ctx.history = hist;
624
+ ctx.historyPtr = ctx.history.length - 1;
625
+ ctx.historyVersion += 1;
626
+ }
627
+ ctx.grid.store.setState((prev: any) => ({ ...prev }));
628
+ if (ctx.props.onCellValueChange && changed.length) {
629
+ const rowIndex = ctx.internalData.indexOf(row.original as TData);
630
+ for (const c of changed) {
631
+ ctx.props.onCellValueChange({
632
+ rowIndex,
633
+ columnId: c.columnId,
634
+ oldValue: c.before,
635
+ newValue: c.after,
636
+ row: row.original as TData,
637
+ });
638
+ }
639
+ }
640
+ ctx.fullRowEdit = null;
641
+ }
642
+
643
+ /** Discard the full-row edit. */
644
+ function cancelFullRowEdit(): void {
645
+ ctx.fullRowEdit = null;
646
+ }
647
+
498
648
  return {
499
649
  isCellEditable,
500
650
  isCellEditableAt,
501
651
  getRowColumnValue,
502
652
  getCellDisplayValue,
503
653
  startEditingWithChar,
654
+ startEditing,
655
+ stopEditing,
656
+ startFullRowEdit,
657
+ setFullRowDraft,
658
+ commitFullRowEdit,
659
+ cancelFullRowEdit,
504
660
  saveEditingCell,
505
661
  applyHistoryStep,
506
662
  updateEditingCellValue,
@@ -1,3 +1,3 @@
1
- /// <reference types="svelte" />
2
-
3
- export { default as FlexRender } from './FlexRender.svelte'
1
+ /// <reference types="svelte" />
2
+
3
+ export { default as FlexRender } from './FlexRender.svelte'