@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
@@ -30,6 +30,7 @@ export type TreeNode = {
30
30
  * even with no children yet; the renderer loads them on first expand). */
31
31
  lazy?: boolean;
32
32
  };
33
+ /** A node's checkbox state. `'indeterminate'` means some but not all descendants are checked. */
33
34
  export type CheckState = 'checked' | 'indeterminate' | 'unchecked';
34
35
  /** A single visible (flattened) tree row. */
35
36
  export type TreeRow = {
@@ -73,6 +74,7 @@ export type TreeConfig = {
73
74
  /** Filter query: show only matching nodes + their ancestors, auto-expanded. */
74
75
  filter?: () => string | undefined;
75
76
  };
77
+ /** Build the headless tree model: expansion, selection and checkbox cascading, with no markup. */
76
78
  export declare function createTree(config: TreeConfig): {
77
79
  /** Internally-managed set of expanded node ids. */
78
80
  readonly expanded: Set<string>;
@@ -113,4 +115,5 @@ export declare function createTree(config: TreeConfig): {
113
115
  onkeydown: (e: KeyboardEvent) => void;
114
116
  };
115
117
  };
118
+ /** The headless tree instance returned by {@link createTree}. */
116
119
  export type Tree = ReturnType<typeof createTree>;
@@ -87,6 +87,7 @@ export function sortTreeNodes(nodes, compare) {
87
87
  .sort(compare)
88
88
  .map((n) => (n.children ? { ...n, children: sortTreeNodes(n.children, compare) } : n));
89
89
  }
90
+ /** Build the headless tree model: expansion, selection and checkbox cascading, with no markup. */
90
91
  export function createTree(config) {
91
92
  const nodes = () => config.nodes();
92
93
  const checkable = () => config.checkable?.() ?? false;
@@ -9,6 +9,7 @@
9
9
  * - `firstDayOfWeek` is 0-6 (0 = Sunday), matching `Date.getDay()` and Smart's
10
10
  * `firstDayOfWeek`.
11
11
  */
12
+ /** Anything the date helpers accept: a `Date`, epoch milliseconds, or a parseable string. */
12
13
  export type DateLike = Date | number | string;
13
14
  /** Coerce a Date | epoch-ms | parseable string to a Date, or null if invalid. */
14
15
  export declare function toDate(value: DateLike | null | undefined): Date | null;
@@ -63,6 +64,7 @@ export declare function centuryRange(year: number): {
63
64
  start: number;
64
65
  end: number;
65
66
  };
67
+ /** One cell of a month grid, including the leading and trailing days from adjacent months. */
66
68
  export type MonthMatrixCell = {
67
69
  date: Date;
68
70
  /** In the displayed month (vs. leading/trailing days of adjacent months). */
@@ -4,6 +4,7 @@
4
4
  * still selectable). Mirrors Smart's calendar constraint model. Framework-free.
5
5
  */
6
6
  import { type DateLike } from './date-core.js';
7
+ /** Which dates a picker allows: bounds, an explicit disabled set, and disabled weekdays. */
7
8
  export type RestrictOptions = {
8
9
  min?: DateLike | null;
9
10
  max?: DateLike | null;
@@ -11,6 +11,7 @@
11
11
  * These functions are DST-aware and take no ambient "now" (safe for the model /
12
12
  * headless contexts that forbid `Date.now()`).
13
13
  */
14
+ /** A wall-clock time broken into fields, as it reads in a specific time zone. */
14
15
  export type ZoneParts = {
15
16
  year: number;
16
17
  month: number;
@@ -16,6 +16,7 @@
16
16
  * painting; all the surgery lives here and is unit-tested without a browser.
17
17
  */
18
18
  import { type DockNode, type DockTabs, type DockPane, type DockZone, type IdGen } from './dock-model.js';
19
+ /** Which edge a pane docks against when dropped. */
19
20
  export type DockSide = Exclude<DockZone, 'center'>;
20
21
  /** A floating window: one tabs leaf shown in a movable/resizable frame. */
21
22
  export type FloatWindow = {
@@ -40,6 +41,7 @@ export type AutoHideEntry = {
40
41
  /** Fly-out panel size in px along the reveal axis. */
41
42
  size: number;
42
43
  };
44
+ /** The whole dock manager: its layout tree plus floating and pinned panes. */
43
45
  export type DockManagerState = {
44
46
  main: DockNode | null;
45
47
  floating: FloatWindow[];
@@ -57,6 +59,7 @@ export type PaneLocation = {
57
59
  kind: 'autoHide';
58
60
  entryId: string;
59
61
  };
62
+ /** Find a pane in the layout tree by id, returning it with its parent for mutation. */
60
63
  export declare function locatePane(state: DockManagerState, paneId: string): PaneLocation | null;
61
64
  /** Which surface holds a tabs leaf: 'main', a window id, or null. */
62
65
  export declare function surfaceOfTabs(state: DockManagerState, tabsId: string): 'main' | string | null;
@@ -16,6 +16,7 @@
16
16
  * painting; all the surgery lives here and is unit-tested without a browser.
17
17
  */
18
18
  import { tabs, dockInto, removePane as removePaneFromTree, removeLeaf, dockLeafToEdge, reorderPane, findTabsWithPane, allPaneIds, } from './dock-model.js';
19
+ /** Find a pane in the layout tree by id, returning it with its parent for mutation. */
19
20
  export function locatePane(state, paneId) {
20
21
  if (state.main && findTabsWithPane(state.main, paneId))
21
22
  return { kind: 'main' };
@@ -12,6 +12,7 @@
12
12
  * the drag gestures; all the tree surgery lives here so it is unit-testable
13
13
  * without a browser. Modelled on Smart's `smart-layout` group/item structure.
14
14
  */
15
+ /** One dockable pane: its id, title, and the state a tab needs to render. */
15
16
  export type DockPane = {
16
17
  id: string;
17
18
  title: string;
@@ -36,14 +37,19 @@ export type DockGroup = {
36
37
  /** Size weight per child; parallel to `children`, normalized to sum ~1. */
37
38
  sizes: number[];
38
39
  };
40
+ /** A node in the layout tree - either a split group or a tabbed leaf. */
39
41
  export type DockNode = DockGroup | DockTabs;
40
42
  /** Where a dragged pane lands relative to a target leaf. */
41
43
  export type DockZone = 'left' | 'right' | 'top' | 'bottom' | 'center';
44
+ /** Supplies ids for newly created nodes, so layouts stay deterministic in tests. */
42
45
  export type IdGen = () => string;
46
+ /** Build a {@link DockPane}. */
43
47
  export declare function pane(id: string, title: string, closable?: boolean): DockPane;
44
48
  /** Largest per-pane `minSize` in a leaf (0 if none). Used by splitter clamping. */
45
49
  export declare function leafMinSize(node: DockTabs): number;
50
+ /** Build a tabbed leaf holding the given panes. */
46
51
  export declare function tabs(genId: IdGen, panes: DockPane[], active?: number): DockTabs;
52
+ /** Build a split group: panes or nested groups laid out in a row or column. */
47
53
  export declare function group(genId: IdGen, direction: 'row' | 'column', children: DockNode[], sizes?: number[]): DockGroup;
48
54
  /** Return sizes summing to 1, padded/trimmed to `count`. Even split when absent. */
49
55
  export declare function normalizeSizes(sizes: number[] | undefined, count: number): number[];
@@ -13,6 +13,7 @@
13
13
  * without a browser. Modelled on Smart's `smart-layout` group/item structure.
14
14
  */
15
15
  // ---- construction helpers -------------------------------------------------
16
+ /** Build a {@link DockPane}. */
16
17
  export function pane(id, title, closable = true) {
17
18
  return { id, title, closable };
18
19
  }
@@ -20,9 +21,11 @@ export function pane(id, title, closable = true) {
20
21
  export function leafMinSize(node) {
21
22
  return node.panes.reduce((m, p) => Math.max(m, p.minSize ?? 0), 0);
22
23
  }
24
+ /** Build a tabbed leaf holding the given panes. */
23
25
  export function tabs(genId, panes, active = 0) {
24
26
  return { type: 'tabs', id: genId(), panes, active: clampIndex(active, panes.length) };
25
27
  }
28
+ /** Build a split group: panes or nested groups laid out in a row or column. */
26
29
  export function group(genId, direction, children, sizes) {
27
30
  return { type: 'group', id: genId(), direction, children, sizes: normalizeSizes(sizes, children.length) };
28
31
  }
@@ -9,6 +9,7 @@
9
9
  *
10
10
  * Framework-free (no Svelte, no DOM) so it can be unit-tested and reused.
11
11
  */
12
+ /** How much room a cell editor asks for, so the grid can size its overlay. */
12
13
  export type EditorSize = 'sm' | 'md' | 'lg';
13
14
  /**
14
15
  * A small action button rendered inside a framed field (via `SvField`'s
@@ -8,11 +8,39 @@ const DIACRITIC_RE = /[̀-ͯ]/g;
8
8
  export function normalizeForFilter(s, locale) {
9
9
  if (!s)
10
10
  return '';
11
+ // Fast path for pure-ASCII input, which is the overwhelming majority of what
12
+ // a grid filters. Both of the expensive steps are provably no-ops there:
13
+ // ASCII is already NFD-normalised, and the combining-marks block this strips
14
+ // (U+0300..U+036F) contains no ASCII. So lowercasing alone is the same
15
+ // answer, and `normalize-fast-path.test.ts` checks that exhaustively over
16
+ // every ASCII code point rather than taking the argument on trust.
17
+ //
18
+ // Worth the branch: this runs once per cell per filter - 100,000 times for a
19
+ // one-column filter over a 100k grid - and a CPU profile of a browser filter
20
+ // put more time in this function than in any other.
21
+ //
22
+ // Note the locale still routes through toLocaleLowerCase even on the fast
23
+ // path: 'I'.toLocaleLowerCase('tr') is 'ı', so ASCII does NOT imply the
24
+ // locale can be ignored.
25
+ if (isAsciiOnly(s)) {
26
+ return locale ? s.toLocaleLowerCase(locale) : s.toLowerCase();
27
+ }
11
28
  const stripped = s.normalize('NFD').replace(DIACRITIC_RE, '');
12
29
  return locale
13
30
  ? stripped.toLocaleLowerCase(locale)
14
31
  : stripped.toLowerCase();
15
32
  }
33
+ /**
34
+ * True when every code unit is ASCII. A plain char-code scan, which is far
35
+ * cheaper than the `normalize()` + regex pass it lets us skip.
36
+ */
37
+ function isAsciiOnly(s) {
38
+ for (let i = 0; i < s.length; i++) {
39
+ if (s.charCodeAt(i) > 127)
40
+ return false;
41
+ }
42
+ return true;
43
+ }
16
44
  /**
17
45
  * Delimiter used to serialise the `in` / `notIn` value list into the single
18
46
  * `value` string that the filter model carries.
@@ -13,7 +13,7 @@
13
13
  * Both are generic over the row / column shape (via small accessor callbacks) so
14
14
  * they are trivially unit-testable and carry no dependency on the grid runtime.
15
15
  */
16
- /** AG-Grid-style grouping display variants. */
16
+ /** Grouping display variants. */
17
17
  export type GroupDisplayType = 'groupRows' | 'singleColumn' | 'multipleColumns';
18
18
  export type GroupFooterOptions<R> = {
19
19
  /** Depth of a row (group rows and their descendants). */
package/dist/index.d.ts CHANGED
@@ -194,6 +194,7 @@ export { chartToSvgString, downloadChartSvg, chartToPngBlob, downloadChartPng, c
194
194
  export { buildSparkline, toSparklineValues, type SparklineConfig, type SparklineType, type SparklineGeometry, } from './sparkline.js';
195
195
  export { spreadsheetLayout, spansToMerges, type SpanColumn, type SpreadsheetActionOptions, type MergeSpec, type CellBorderSpec, type BorderSpec, } from './spreadsheet.js';
196
196
  export { rowResize, type RowResizeOptions } from './row-resize.js';
197
+ export { columnResize, type ColumnResizeOptions } from './column-resize.js';
197
198
  export { rowDropZone, type RowDragEndEvent, type RowDropZoneOptions } from './row-drag.js';
198
199
  export { resolveColumnTypes, inferCellDataType, type CellDataType } from './column-types.js';
199
200
  export { computeColumnGroupMeta, hiddenLeavesForCollapse, type ColumnGroupMeta, type ColumnGroupShow, } from './column-groups.js';
@@ -212,7 +213,7 @@ export { createVirtualizer } from './virtualization/virtualizer.js';
212
213
  export { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte.js';
213
214
  export { createColumnVirtualizer } from './virtualization/column-virtualizer.js';
214
215
  export type { VirtualItem, VirtualizerOptions, VirtualizerState } from './virtualization/types.js';
215
- export type { SvGridApi, SvGridFilterOperator, SvGridWrapperProps } from './svgrid-wrapper.types.js';
216
+ export type { SvGridApi, SvGridFilterOperator, SvGridViewState, SvGridWrapperProps, } from './svgrid-wrapper.types.js';
216
217
  export { parseEditorValue, normalizeEditorOptions, type CellEditorType, type CellEditorOption, } from './editors/cell-editors.js';
217
218
  export { applyExcelFilter, compileExcelFilter, normalizeForFilter, splitInTokens, joinInTokens, trailingInToken, type CompiledExcelFilter, type ExcelFilter, type ExcelFilterOperator, type ExcelFilterOptions, } from './filtering/excel-filters.js';
218
219
  export { ALL_FILTER_OPERATORS, SET_OPERATOR_IDS, VALUELESS_OPERATOR_IDS, RANGE_OPERATOR_IDS, isSetOperator, isValuelessOperator, isRangeOperator, type FilterValueType, } from './filtering/filter-operator-catalogue.js';
package/dist/index.js CHANGED
@@ -230,7 +230,13 @@ export { buildChart, formatChartValue, rowsToChartSpec, niceScale, niceLogScale,
230
230
  export { chartToSvgString, downloadChartSvg, chartToPngBlob, downloadChartPng, chartSpecToCsv, downloadChartCsv, } from './chart-export.js';
231
231
  export { buildSparkline, toSparklineValues, } from './sparkline.js';
232
232
  export { spreadsheetLayout, spansToMerges, } from './spreadsheet.js';
233
+ // The two resize actions, exported as a matched pair. `<SvGrid>` drives both
234
+ // through the `rowResize` / `columnResize` props - both default OFF, both are
235
+ // turned on by setting them true, and the grid `import()`s the module itself so
236
+ // a grid that leaves them off carries neither. Import them directly only for a
237
+ // headless table, which renders its own rows and headers.
233
238
  export { rowResize } from './row-resize.js';
239
+ export { columnResize } from './column-resize.js';
234
240
  export { rowDropZone } from './row-drag.js';
235
241
  export { resolveColumnTypes, inferCellDataType } from './column-types.js';
236
242
  export { computeColumnGroupMeta, hiddenLeavesForCollapse, } from './column-groups.js';
@@ -48,6 +48,7 @@ export type VirtualListRow = {
48
48
  opt: IndexedOption;
49
49
  size: number;
50
50
  };
51
+ /** A flattened option list plus its measurements, for virtualizing long dropdowns. */
51
52
  export type FlatVirtualModel = {
52
53
  /** Group headings + options in render order, each with its px height. */
53
54
  entries: VirtualListRow[];
package/dist/menus.js CHANGED
@@ -118,7 +118,7 @@ export function createMenus(ctx) {
118
118
  function openChooseColumns(event) {
119
119
  event.stopPropagation();
120
120
  // Submenu behavior: keep the parent operations menu open and float the
121
- // Choose Columns panel out to the right of it (AG-Grid style). Falls
121
+ // Choose Columns panel out to the right of it, submenu-style. Falls
122
122
  // back to below the item if the popover would clip the viewport.
123
123
  const trigger = event.currentTarget;
124
124
  const menuEl = trigger.closest(".sv-grid-menu");
@@ -37,6 +37,7 @@ export type Viewport = {
37
37
  width: number;
38
38
  height: number;
39
39
  };
40
+ /** Where to put a floating element: its preferred side, offsets, and collision behaviour. */
40
41
  export type ComputePositionOptions = {
41
42
  /** Preferred placement. Default `'bottom-start'`. */
42
43
  placement?: Placement;
@@ -61,6 +62,7 @@ export type ComputePositionOptions = {
61
62
  /** Floor for the available main-axis size returned by `size`. Default 0. */
62
63
  minMainAxis?: number;
63
64
  };
65
+ /** The resolved position, including the side actually used after collision handling. */
64
66
  export type ComputePositionResult = {
65
67
  /** Floating left, in viewport (fixed-position) coordinates. */
66
68
  x: number;
@@ -36,6 +36,17 @@ export type RowResizeOptions = {
36
36
  max?: number;
37
37
  /** When true, the action removes its strips and ignores events. */
38
38
  disabled?: boolean;
39
+ /**
40
+ * Where the drag strip is anchored.
41
+ * - `'gutter'` (default) - only rows that have a row-header gutter cell get
42
+ * a strip: the built-in row-number column (`showRowNumbers`) or a column
43
+ * tagged `cellClass: 'sv-row-gutter'`. Rows without one are skipped, so on
44
+ * a plain grid the action does nothing.
45
+ * - `'row'` - use the gutter when there is one, otherwise fall back to the
46
+ * row's first body cell. This is what `<SvGrid rowResize>` passes, so the
47
+ * prop works without also asking for a gutter column.
48
+ */
49
+ anchor?: 'gutter' | 'row';
39
50
  };
40
51
  export declare function rowResize(node: HTMLElement, opts: RowResizeOptions): {
41
52
  update(next: RowResizeOptions): void;
@@ -141,7 +141,13 @@ export function rowResize(node, opts) {
141
141
  // A row-header gutter cell: either a user column tagged
142
142
  // `sv-row-gutter`, or the grid's built-in row-number gutter
143
143
  // (`showRowNumbers`), whose cell is `.sv-grid-row-number-cell`.
144
- const gutter = tr.querySelector('.sv-grid-cell.sv-row-gutter, .sv-grid-cell.sv-grid-row-number-cell');
144
+ const gutter = tr.querySelector('.sv-grid-cell.sv-row-gutter, .sv-grid-cell.sv-grid-row-number-cell') ??
145
+ // `anchor: 'row'` - no gutter on this grid, so hang the strip off the
146
+ // first body cell instead. Without this a grid with no row-number
147
+ // column would silently not resize.
148
+ (current.anchor === 'row'
149
+ ? tr.querySelector('.sv-grid-cell')
150
+ : null);
145
151
  if (!gutter)
146
152
  continue;
147
153
  if (gutter.querySelector(`:scope > .${STRIP_CLASS}`))
@@ -6,6 +6,7 @@
6
6
  * LOCATION. No `Date.now()` in the core - `toICS` accepts a stamp.
7
7
  */
8
8
  import type { RecurrenceRule } from './recurrence.js';
9
+ /** One VEVENT: the fields SvGrid reads from and writes to an iCalendar feed. */
9
10
  export type ICalEvent = {
10
11
  uid?: string;
11
12
  title: string;
@@ -171,6 +171,7 @@ export type DayLayout<TData = unknown> = {
171
171
  events: PositionedEvent<TData>[];
172
172
  overflows: OverflowMarker<TData>[];
173
173
  };
174
+ /** Tuning for event layout: how overlapping events share horizontal space. */
174
175
  export type LayoutOptions = {
175
176
  dayStartHour?: number;
176
177
  dayEndHour?: number;
package/dist/selection.js CHANGED
@@ -225,6 +225,15 @@ export function createSelection(ctx) {
225
225
  * outline wins on overlap). Returns null when the cell is in no range.
226
226
  */
227
227
  function getCellRangeEdges(rowIndex, colIndex) {
228
+ // Bail before `getSelectionRects()`, which allocates an array and resolves
229
+ // every range. This runs once per rendered CELL on every render - about 250
230
+ // times per frame on a default grid - and with nothing selected it did all
231
+ // that work to return null every time. An anchored active range or a
232
+ // committed one is the only way any rect can exist.
233
+ const active = ctx.selectionRange;
234
+ const committed = ctx.selectionRanges;
235
+ if (!committed?.length && !(active?.anchor && active?.focus))
236
+ return null;
228
237
  for (const rect of getSelectionRects()) {
229
238
  if (rowIndex < rect.minRow ||
230
239
  rowIndex > rect.maxRow ||
@@ -63,7 +63,7 @@ export type SpanColumn<TData = Record<string, unknown>> = {
63
63
  /**
64
64
  * Turn declarative per-column `colSpan` / `rowSpan` callbacks into a
65
65
  * `MergeSpec[]` you can hand to `spreadsheetLayout` - so value-driven,
66
- * AG-Grid-style spanning runs on the SAME real colspan/rowspan merge engine
66
+ * Declarative spanning runs on the SAME real colspan/rowspan merge engine
67
67
  * instead of a second code path. Recompute after sort/filter (indexes are
68
68
  * display-row indexes). A common pattern is "merge runs of equal values":
69
69
  *
@@ -22,7 +22,7 @@
22
22
  /**
23
23
  * Turn declarative per-column `colSpan` / `rowSpan` callbacks into a
24
24
  * `MergeSpec[]` you can hand to `spreadsheetLayout` - so value-driven,
25
- * AG-Grid-style spanning runs on the SAME real colspan/rowspan merge engine
25
+ * Declarative spanning runs on the SAME real colspan/rowspan merge engine
26
26
  * instead of a second code path. Recompute after sort/filter (indexes are
27
27
  * display-row indexes). A common pattern is "merge runs of equal values":
28
28
  *
package/dist/summaries.js CHANGED
@@ -1,3 +1,7 @@
1
+ // summaries 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 { applyGroupAggregate, } from "./index.js";
1
5
  import "./sv-grid-scrollbar.js";
2
6
  import { getCellKey, } from "./SvGrid.helpers.js";
3
7
  import { formatSummaryNumeric, } from "./cell-values.js";
@@ -25,6 +29,24 @@ export function createSummaries(ctx) {
25
29
  const fieldFn = def.fieldFn;
26
30
  const field = def.field;
27
31
  const columnId = column.id;
32
+ // A column that declares its own `summary` opts out of the default
33
+ // sum/count below. Only that column pays for the aggregator dispatch, so
34
+ // a grid that declares none keeps the original hot loop exactly as it was.
35
+ const declared = def.summary;
36
+ if (declared !== undefined) {
37
+ if (declared === false) {
38
+ summary[columnId] = "";
39
+ continue;
40
+ }
41
+ const value = applyGroupAggregate(declared, columnId, rows);
42
+ summary[columnId] =
43
+ typeof value === "number" && Number.isFinite(value)
44
+ ? formatSummaryNumeric(column, value)
45
+ : value == null
46
+ ? ""
47
+ : String(value);
48
+ continue;
49
+ }
28
50
  let numericSum = 0;
29
51
  let numericCount = 0;
30
52
  for (let i = 0; i < rowCount; i += 1) {
@@ -3,6 +3,7 @@ import type { FilterOperator, Props } from './SvGrid.types.js';
3
3
  import type { GridExportOptions, GridClipboardOptions } from './export-format.js';
4
4
  import type { ChartSpec, ChartType } from './chart.js';
5
5
  import type { GridPredicateExpr } from './filtering/predicate-expr.js';
6
+ /** The comparisons a column filter can use, as shown in the filter menu. */
6
7
  export type SvGridFilterOperator = FilterOperator;
7
8
  /**
8
9
  * A serializable snapshot of everything that makes up the current "view":
@@ -80,6 +81,7 @@ export type SvGridTransaction<TData> = {
80
81
  update?: ReadonlyArray<TData>;
81
82
  remove?: ReadonlyArray<TData | string>;
82
83
  };
84
+ /** What a batched `applyTransaction` did: how many rows were added, updated and removed. */
83
85
  export type SvGridTransactionResult = {
84
86
  added: number;
85
87
  updated: number;
@@ -458,6 +460,7 @@ export type SvGridApi<TFeatures extends TableFeatures, TData extends RowData> =
458
460
  /** Force a recompute of the row pipeline + a re-render. */
459
461
  refresh(): void;
460
462
  };
463
+ /** The props `<SvGrid>` accepts. See the SvGrid reference for the full list with defaults. */
461
464
  export type SvGridWrapperProps<TFeatures extends TableFeatures, TData extends RowData> = {
462
465
  data: ReadonlyArray<TData>;
463
466
  columns: Array<ColumnDef<TFeatures, TData>>;
@@ -505,6 +508,8 @@ export type SvGridWrapperProps<TFeatures extends TableFeatures, TData extends Ro
505
508
  enableCellSelection?: boolean;
506
509
  enableInlineEditing?: boolean;
507
510
  enableRowSummaries?: boolean;
511
+ /** Shortcut alias for `enableRowSummaries`; wins when both are set. */
512
+ summary?: boolean;
508
513
  /** Receives the imperative grid API when the component is ready. */
509
514
  onApiReady?: (api: SvGridApi<TFeatures, TData>) => void;
510
515
  };
@@ -14,6 +14,7 @@
14
14
  * Mount a single <SvToaster /> once near the app root to render the queue.
15
15
  */
16
16
  import type { Snippet } from 'svelte';
17
+ /** A toast's severity, which selects its colour and icon. */
17
18
  export type ToastVariant = 'info' | 'success' | 'warning' | 'error';
18
19
  /** A button rendered inside a toast (a primary `action` or a secondary `cancel`). */
19
20
  export type ToastAction = {
@@ -23,6 +24,7 @@ export type ToastAction = {
23
24
  /** Keep the toast open after the click (default: dismiss it). */
24
25
  keepOpen?: boolean;
25
26
  };
27
+ /** Options for one toast: its variant, how long it stays, and any action button. */
26
28
  export type ToastOptions = {
27
29
  variant?: ToastVariant;
28
30
  /** Auto-dismiss after N ms. `0` = sticky (dismiss manually). Default 4000. */
@@ -38,6 +40,7 @@ export type ToastOptions = {
38
40
  /** Render a fully custom toast body instead of the icon + title + message. */
39
41
  render?: Snippet<[Toast]>;
40
42
  };
43
+ /** A live toast: its options plus the id needed to dismiss it. */
41
44
  export type Toast = {
42
45
  id: number;
43
46
  message: string;
@@ -58,6 +61,7 @@ export type PromiseMessages<T> = {
58
61
  error: string | ((error: unknown) => string);
59
62
  };
60
63
  type VariantOptions = Omit<ToastOptions, 'variant'>;
64
+ /** The callable toast API - `toast(msg)` plus `.success` / `.error` / friends. */
61
65
  export type ToastFn = {
62
66
  (message: string, options?: ToastOptions): number;
63
67
  info: (message: string, options?: VariantOptions) => number;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Development-time configuration checks.
3
+ *
4
+ * The grid used to fail silently on the most common mistakes. A misspelled
5
+ * `field` rendered a column of empty cells with nothing on the console; a
6
+ * `pageSize` with no pagination was quietly ignored; a column marked
7
+ * `sortable` with no sorting enabled just did not sort. Each of those costs
8
+ * someone an afternoon, and none of them were detectable from the outside.
9
+ *
10
+ * This module is the check. It is pure and returns messages rather than
11
+ * printing them, so it can be unit-tested; the controller runs it in an effect
12
+ * and prints each message once, in dev builds only.
13
+ *
14
+ * Rules for anything added here:
15
+ * - Never fire on valid config. A false positive teaches people to ignore
16
+ * warnings, which is worse than staying silent.
17
+ * - Say what happened, what it means, and how to fix it - the house style set
18
+ * by the one pre-existing warning in server-data-source.ts.
19
+ * - Stay O(columns), or O(columns x a fixed row sample). This runs on data
20
+ * changes, and must not scale with row count.
21
+ */
22
+ import type { ColumnDef, RowData, TableFeatures } from './core.js';
23
+ export type ValidateInput<TFeatures extends TableFeatures, TData extends RowData> = {
24
+ data: ReadonlyArray<TData>;
25
+ columns: ReadonlyArray<ColumnDef<TFeatures, TData>>;
26
+ /** The resolved feature set, after the boolean shortcuts have injected theirs. */
27
+ features?: Record<string, unknown>;
28
+ sortable?: boolean;
29
+ pageable?: boolean;
30
+ showPagination?: boolean;
31
+ pageSize?: number;
32
+ groupBy?: ReadonlyArray<string>;
33
+ treeData?: {
34
+ parentField?: string;
35
+ idField?: string;
36
+ column?: string;
37
+ };
38
+ initialColumnPinning?: {
39
+ left?: ReadonlyArray<string>;
40
+ right?: ReadonlyArray<string>;
41
+ };
42
+ columnVirtualization?: boolean;
43
+ externalPagination?: boolean;
44
+ rowCount?: number;
45
+ externalSort?: boolean;
46
+ onSortingChange?: unknown;
47
+ externalFilter?: boolean;
48
+ onFiltersChange?: unknown;
49
+ };
50
+ export declare function validateGridConfig<TFeatures extends TableFeatures, TData extends RowData>(input: ValidateInput<TFeatures, TData>): string[];