@svgrid/grid 2.6.21 → 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 (151) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +22 -0
  3. package/dist/GridMenus.svelte +201 -18
  4. package/dist/SvAutoComplete.svelte +4 -0
  5. package/dist/SvComboBox.svelte +5 -1
  6. package/dist/SvDateRangeInput.svelte +25 -3
  7. package/dist/SvDropDownList.svelte +4 -0
  8. package/dist/SvForm.svelte +8 -8
  9. package/dist/SvGrid.controller.svelte.d.ts +61 -9
  10. package/dist/SvGrid.controller.svelte.js +363 -123
  11. package/dist/SvGrid.css +222 -1
  12. package/dist/SvGrid.helpers.d.ts +55 -0
  13. package/dist/SvGrid.helpers.js +83 -0
  14. package/dist/SvGrid.svelte +255 -56
  15. package/dist/SvGrid.types.d.ts +146 -1
  16. package/dist/SvGridCellEditor.svelte +99 -17
  17. package/dist/SvGridSelect.svelte +6 -2
  18. package/dist/SvGridSelect.svelte.d.ts +1 -1
  19. package/dist/SvListBox.svelte +8 -0
  20. package/dist/SvMultiSelect.svelte +6 -2
  21. package/dist/SvMultiSelect.svelte.d.ts +1 -1
  22. package/dist/SvNumberInput.svelte +4 -0
  23. package/dist/SvRichCell.svelte +125 -0
  24. package/dist/SvRichCell.svelte.d.ts +15 -0
  25. package/dist/SvTextArea.svelte +5 -1
  26. package/dist/SvTreeSelect.svelte +6 -2
  27. package/dist/SvTreeSelect.svelte.d.ts +1 -1
  28. package/dist/builtin-editors.d.ts +2 -2
  29. package/dist/builtin-editors.js +19 -3
  30. package/dist/cdn/GridMenus-BzWXQjv3.js +600 -0
  31. package/dist/cdn/GridMenus-xr_rHx8F.js +601 -0
  32. package/dist/cdn/SvDateRangeInput-CaOuMs8O.js +198 -0
  33. package/dist/cdn/SvDateRangeInput-DMLKmGEc.js +199 -0
  34. package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +816 -0
  35. package/dist/cdn/SvDateTimePicker-sonaH0oh.js +812 -0
  36. package/dist/cdn/SvGridCellEditor-BAPSC7EL.js +550 -0
  37. package/dist/cdn/SvGridCellEditor-BwzmUWtL.js +549 -0
  38. package/dist/cdn/column-resize-DsfNXMom.js +102 -0
  39. package/dist/cdn/date-format-BNii4zeD.js +1393 -0
  40. package/dist/cdn/date-format-BnnHlqGw.js +1395 -0
  41. package/dist/cdn/{editor-registry-CiI0xlKg.js → editor-registry-BhgE3S84.js} +51 -25
  42. package/dist/cdn/row-resize-BRcimkUT.js +95 -0
  43. package/dist/cdn/{src-DBel9wRZ.js → src-DGo7IHug.js} +10017 -9488
  44. package/dist/cdn/{src-BYq-qyrp.js → src-skQN5eqh.js} +7829 -7300
  45. package/dist/cdn/svgrid.js +14 -10
  46. package/dist/cdn/svgrid.svelte-external.js +14 -10
  47. package/dist/cell-values.d.ts +16 -0
  48. package/dist/cell-values.js +28 -0
  49. package/dist/clipboard.d.ts +22 -0
  50. package/dist/clipboard.js +405 -27
  51. package/dist/column-groups.js +1 -1
  52. package/dist/column-resize.d.ts +46 -0
  53. package/dist/column-resize.js +205 -0
  54. package/dist/columns.d.ts +0 -3
  55. package/dist/columns.js +0 -57
  56. package/dist/core.d.ts +19 -4
  57. package/dist/core.js +460 -119
  58. package/dist/editing.js +12 -7
  59. package/dist/editors/cell-editors.d.ts +11 -0
  60. package/dist/editors/cell-editors.js +18 -1
  61. package/dist/filtering/excel-filters.js +28 -0
  62. package/dist/grid-messages.d.ts +15 -0
  63. package/dist/grid-messages.js +15 -0
  64. package/dist/group-display.d.ts +1 -1
  65. package/dist/index.d.ts +6 -1
  66. package/dist/index.js +10 -0
  67. package/dist/list-option.d.ts +6 -0
  68. package/dist/menus.js +41 -8
  69. package/dist/row-resize.d.ts +11 -0
  70. package/dist/row-resize.js +7 -1
  71. package/dist/sanitize-html.d.ts +37 -0
  72. package/dist/sanitize-html.js +234 -0
  73. package/dist/selection-bar-view.svelte.d.ts +25 -0
  74. package/dist/selection-bar-view.svelte.js +13 -0
  75. package/dist/selection.d.ts +2 -1
  76. package/dist/selection.js +133 -8
  77. package/dist/spreadsheet.d.ts +1 -1
  78. package/dist/spreadsheet.js +1 -1
  79. package/package.json +1 -1
  80. package/src/GridMenus.svelte +201 -18
  81. package/src/SvAutoComplete.svelte +4 -0
  82. package/src/SvComboBox.svelte +5 -1
  83. package/src/SvDateRangeInput.svelte +25 -3
  84. package/src/SvDropDownList.svelte +4 -0
  85. package/src/SvForm.svelte +8 -8
  86. package/src/SvGrid.controller.svelte.ts +404 -133
  87. package/src/SvGrid.css +222 -1
  88. package/src/SvGrid.helpers.ts +87 -0
  89. package/src/SvGrid.svelte +255 -56
  90. package/src/SvGrid.types.ts +153 -1
  91. package/src/SvGridCellEditor.svelte +99 -17
  92. package/src/SvGridSelect.svelte +6 -2
  93. package/src/SvListBox.svelte +8 -0
  94. package/src/SvMultiSelect.svelte +6 -2
  95. package/src/SvNumberInput.svelte +4 -0
  96. package/src/SvRichCell.svelte +125 -0
  97. package/src/SvTextArea.svelte +5 -1
  98. package/src/SvTreeSelect.svelte +6 -2
  99. package/src/builtin-editors.test.ts +18 -1
  100. package/src/builtin-editors.ts +19 -3
  101. package/src/cell-values.ts +30 -0
  102. package/src/clipboard.test.ts +93 -20
  103. package/src/clipboard.ts +433 -33
  104. package/src/column-groups.ts +1 -1
  105. package/src/column-resize.test.ts +381 -0
  106. package/src/column-resize.ts +227 -0
  107. package/src/columns.test.ts +0 -103
  108. package/src/columns.ts +0 -58
  109. package/src/core.aggregate.test.ts +134 -0
  110. package/src/core.filter.test.ts +156 -0
  111. package/src/core.grouping.test.ts +146 -0
  112. package/src/core.row-shape.test.ts +119 -0
  113. package/src/core.rowmodel-cache.test.ts +121 -0
  114. package/src/core.sort.test.ts +293 -0
  115. package/src/core.ts +516 -119
  116. package/src/date-string-column.test.ts +87 -0
  117. package/src/editing.test.ts +25 -0
  118. package/src/editing.ts +12 -9
  119. package/src/editor-block-prop.test.ts +132 -0
  120. package/src/editors/cell-editors.ts +27 -1
  121. package/src/filtering/excel-filters.ts +30 -0
  122. package/src/filtering/normalize-fast-path.test.ts +104 -0
  123. package/src/grid-messages.ts +34 -0
  124. package/src/group-display.ts +1 -1
  125. package/src/index.ts +25 -1
  126. package/src/list-option.ts +6 -0
  127. package/src/menus.test.ts +43 -0
  128. package/src/menus.ts +38 -8
  129. package/src/move-cells.test.ts +850 -0
  130. package/src/number-editor-literal.test.ts +84 -0
  131. package/src/number-editor.grid.test.ts +160 -0
  132. package/src/resize-props.test.ts +361 -0
  133. package/src/row-resize.test.ts +31 -0
  134. package/src/row-resize.ts +21 -3
  135. package/src/sanitize-html.test.ts +182 -0
  136. package/src/sanitize-html.ts +235 -0
  137. package/src/selection-bar-view.svelte.ts +36 -0
  138. package/src/selection.test.ts +157 -0
  139. package/src/selection.ts +135 -8
  140. package/src/spreadsheet.ts +1 -1
  141. package/src/svgrid.filter-menu-listbox.svelte.test.ts +56 -10
  142. package/src/svgrid.in-suggest-snapshot.svelte.test.ts +91 -0
  143. package/src/svgrid.menu-scroll-close.test.ts +204 -0
  144. package/src/svgrid.selection-bar-seam.test.ts +185 -0
  145. package/src/svgrid.upsell-license.test.ts +117 -0
  146. package/dist/cdn/GridMenus-BfTAKn84.js +0 -488
  147. package/dist/cdn/GridMenus-C3bJd7w8.js +0 -489
  148. package/dist/cdn/SvDateTimePicker-CHnUkWcH.js +0 -2206
  149. package/dist/cdn/SvDateTimePicker-CRQH_U7E.js +0 -2201
  150. package/dist/cdn/SvGridCellEditor-BGUCzuPB.js +0 -523
  151. package/dist/cdn/SvGridCellEditor-G8IMWIws.js +0 -522
package/dist/editing.js CHANGED
@@ -5,7 +5,7 @@ import { parseEditorValue, } from "./index.js";
5
5
  import "./sv-grid-scrollbar.js";
6
6
  import { getNextActiveCell } from "./keyboard.js";
7
7
  import { getCellKey, toValueArray, } from "./SvGrid.helpers.js";
8
- import { getColumnBaseValue, isGroupRow, } from "./cell-values.js";
8
+ import { createDataIndexLookup, getColumnBaseValue, isGroupRow, } from "./cell-values.js";
9
9
  export function createEditing(ctx) {
10
10
  /**
11
11
  * Resolve `columnDef.editable` for one row × column pair.
@@ -117,6 +117,8 @@ export function createEditing(ctx) {
117
117
  const column = ctx.allColumns.find((entry) => entry.id === ctx.editingCell?.columnId);
118
118
  const parsedValue = parseEditorValue(ctx.editingCell.editorType, ctx.editingCell.value, {
119
119
  multiple: column?.columnDef.editorMultiple === true,
120
+ // A dateString column holds 'YYYY-MM-DD', not a timestamp.
121
+ dateOnly: column?.columnDef.cellDataType === "dateString",
120
122
  });
121
123
  let oldValue = undefined;
122
124
  let finalValue = parsedValue;
@@ -378,16 +380,16 @@ export function createEditing(ctx) {
378
380
  ? endCol - startCol + 1
379
381
  : Math.max(...lines.map((l) => l.split("\t").length));
380
382
  const next = ctx.internalData.slice();
383
+ const dataIndexOf = createDataIndexLookup(next);
381
384
  for (let i = 0; i < rowSpan; i += 1) {
382
385
  const targetRowIndex = startRow + i;
383
386
  const row = ctx.allRows[targetRowIndex];
384
387
  if (!row || isGroupRow(row))
385
388
  continue;
386
- // Map the visible row back to its index in the data array via row.id (default = String(index)).
387
- const dataIndex = Number(row.id);
388
- if (!Number.isInteger(dataIndex) ||
389
- dataIndex < 0 ||
390
- dataIndex >= next.length)
389
+ // Map the visible row back to its slot in the data array by identity -
390
+ // `row.id` is only the index under the default getRowId.
391
+ const dataIndex = dataIndexOf(row);
392
+ if (dataIndex < 0)
391
393
  continue;
392
394
  const originalRow = next[dataIndex];
393
395
  if (!originalRow)
@@ -407,7 +409,9 @@ export function createEditing(ctx) {
407
409
  const editorType = (column.columnDef.editorType ??
408
410
  "text");
409
411
  const raw = fillRange ? lines[0] : sourceCells?.[j] ?? "";
410
- updated[column.columnDef.field] = parseEditorValue(editorType, raw);
412
+ updated[column.columnDef.field] = parseEditorValue(editorType, raw, {
413
+ dateOnly: column.columnDef.cellDataType === "dateString",
414
+ });
411
415
  }
412
416
  next[dataIndex] = updated;
413
417
  }
@@ -496,6 +500,7 @@ export function createEditing(ctx) {
496
500
  const editorType = (column.columnDef.editorType ?? "text");
497
501
  const parsed = parseEditorValue(editorType, fr.draft[column.id], {
498
502
  multiple: column.columnDef.editorMultiple === true,
503
+ dateOnly: column.columnDef.cellDataType === "dateString",
499
504
  });
500
505
  const oldValue = row.original[field];
501
506
  const parser = column.columnDef.valueParser;
@@ -12,6 +12,17 @@ export type CellEditorOption = {
12
12
  export type ParseEditorValueOptions = {
13
13
  /** When true, list/chips return an array; otherwise a scalar. */
14
14
  multiple?: boolean;
15
+ /**
16
+ * Keep a date as the plain `YYYY-MM-DD` string the user picked, instead of a
17
+ * full ISO timestamp. Set for `cellDataType: 'dateString'` columns, which
18
+ * exist precisely to hold ISO date strings.
19
+ *
20
+ * It is not only about shape. `new Date('2026-12-25').toISOString()` sends
21
+ * the value through UTC, so a local midnight becomes the previous or next
22
+ * day either side of the meridian - the stored DATE changes depending on
23
+ * where the user is sitting. Slicing the picked date avoids that entirely.
24
+ */
25
+ dateOnly?: boolean;
15
26
  };
16
27
  /** Normalize the loose `editorOptions` ColumnDef prop into a uniform shape.
17
28
  * Dynamic (function) editorOptions are resolved before reaching here. */
@@ -36,7 +36,24 @@ export function parseEditorValue(rawType, value, opts) {
36
36
  return s || '#000000';
37
37
  }
38
38
  if (type === 'date') {
39
- const date = new Date(String(value));
39
+ const raw = String(value ?? '');
40
+ if (opts?.dateOnly) {
41
+ // Take the calendar date the editor produced as-is. It already arrives
42
+ // as `YYYY-MM-DD` (or that prefix on a datetime), so there is nothing to
43
+ // convert - and converting is exactly what shifts the day across a
44
+ // timezone boundary.
45
+ const match = /^(\d{4}-\d{2}-\d{2})/.exec(raw.trim());
46
+ if (match)
47
+ return match[1];
48
+ const parsed = new Date(raw);
49
+ if (Number.isNaN(parsed.getTime()))
50
+ return null;
51
+ // A non-ISO input (a locale string) still has to become a date string.
52
+ // Use the LOCAL parts, so the day is the one the user saw.
53
+ const pad = (n) => String(n).padStart(2, '0');
54
+ return `${parsed.getFullYear()}-${pad(parsed.getMonth() + 1)}-${pad(parsed.getDate())}`;
55
+ }
56
+ const date = new Date(raw);
40
57
  return Number.isNaN(date.getTime()) ? null : date.toISOString();
41
58
  }
42
59
  if (type === 'datetime') {
@@ -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.
@@ -54,9 +54,24 @@ export type GridMessages = {
54
54
  menuRemoveRow: string;
55
55
  menuRemoveColumn: string;
56
56
  menuEditComment: string;
57
+ selectionBarLabel: string;
58
+ selectionBarCount: string;
59
+ selectionBarClear: string;
60
+ selectionBarMore: string;
61
+ selectionBarSelectAll: string;
62
+ selectionBarEditFields: string;
63
+ bulkEditTitle: string;
64
+ bulkEditLead: string;
65
+ bulkEditMixed: string;
66
+ bulkEditApply: string;
67
+ bulkEditCancel: string;
68
+ bulkEditNoFields: string;
57
69
  chartRange: string;
58
70
  pivotUpsellTitle: string;
59
71
  pivotUpsellBody: string;
72
+ /** Shared licensing line under every Enterprise upsell note. */
73
+ upsellLicense: string;
74
+ upsellLicenseLink: string;
60
75
  };
61
76
  /** English defaults - the literal strings the grid shipped before localization. */
62
77
  export declare const defaultGridMessages: GridMessages;
@@ -49,6 +49,19 @@ export const defaultGridMessages = {
49
49
  menuRemoveRow: 'Remove row',
50
50
  menuRemoveColumn: 'Remove column',
51
51
  menuEditComment: 'Edit comment',
52
+ selectionBarLabel: 'Selection actions',
53
+ /** Label beside the count CHIP, which carries the number itself. */
54
+ selectionBarCount: 'selected',
55
+ selectionBarClear: 'Clear selection',
56
+ selectionBarMore: 'More actions',
57
+ selectionBarSelectAll: 'Select all',
58
+ selectionBarEditFields: 'Edit fields',
59
+ bulkEditTitle: 'Edit fields',
60
+ bulkEditLead: 'Changes apply to all {count} selected items. Fields you leave alone keep their existing values.',
61
+ bulkEditMixed: 'Multiple values',
62
+ bulkEditApply: 'Apply to {count} items',
63
+ bulkEditCancel: 'Cancel',
64
+ bulkEditNoFields: 'No editable fields on this grid.',
52
65
  opContains: 'Contains',
53
66
  opNotContains: 'Not contains',
54
67
  opEquals: 'Equals',
@@ -68,6 +81,8 @@ export const defaultGridMessages = {
68
81
  chartRange: 'Chart selected range',
69
82
  pivotUpsellTitle: 'Pivot mode',
70
83
  pivotUpsellBody: 'Pivot mode is an Enterprise feature. Install @svgrid/enterprise and call enablePivot() to use it.',
84
+ upsellLicense: 'A license key is required for production use. Without one the feature still works, but the grid renders an unlicensed watermark.',
85
+ upsellLicenseLink: 'View licensing',
71
86
  };
72
87
  /**
73
88
  * Merge a consumer's `localeText` over the English defaults. Undefined/empty
@@ -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
@@ -7,10 +7,12 @@ export { subscribeGrid, subscribeSvGrid } from './subscribe.js';
7
7
  export { default as SvGrid } from './SvGrid.svelte';
8
8
  export type { BoardConfig, BoardLane, BoardCardMoveEvent, BoardCardCommitEvent, BoardLayout, BoardSubtask, BoardComment, BoardMenuContext, BoardLaneMenuContext, BoardDrawerConfig } from './SvGrid.types.js';
9
9
  export { registerBoardView, getBoardView, hasBoardView } from './board-view.svelte.js';
10
+ export type { SelectionBarAction, SelectionBarTarget, ContextMenuItem, ContextMenuTarget, } from './SvGrid.types.js';
10
11
  export * from './ai.js';
11
12
  export { registerExportProvider, getExportProvider, type GridExportProvider, type GridExportRunOptions, } from './export-provider.js';
12
13
  export type { SchedulerConfig, SchedulerDrawerConfig, SchedulerEventMoveEvent, SchedulerEventResizeEvent, SchedulerEventCommitEvent, SchedulerRangeSelection, SchedulerView, SchedulerResource, SchedulerCollisionMode, SchedulerException, SchedulerEditScope, SchedulerOccurrenceChangeEvent, } from './SvGrid.types.js';
13
14
  export { registerSchedulerView, getSchedulerView, hasSchedulerView } from './scheduler-view.svelte.js';
15
+ export { registerSelectionBarView, getSelectionBarView, hasSelectionBarView } from './selection-bar-view.svelte.js';
14
16
  export type { ChartViewConfig } from './SvGrid.types.js';
15
17
  export { registerChartView, getChartView, hasChartView } from './chart-view.svelte.js';
16
18
  export { registerPivotEngine, getPivotEngine, hasPivotEngine, type PivotEngine, type GridPivotConfig, type GridPivotValue, type GridPivotRow, } from './pivot-view.svelte.js';
@@ -55,6 +57,7 @@ export { default as SvVisuallyHidden } from './SvVisuallyHidden.svelte';
55
57
  export { default as SvAspectRatio } from './SvAspectRatio.svelte';
56
58
  export { registerCellEditor, getCellEditor, hasCellEditor, unregisterCellEditor, registeredCellEditorTypes, defaultEditorProps, resolveEditorProps, type CellEditorContext, type CellEditorRegistration, } from './editor-registry.js';
57
59
  export { registerBuiltinEditors } from './builtin-editors.js';
60
+ export { sanitizeHtml, renderMarkdown, htmlToText } from './sanitize-html.js';
58
61
  export { default as SvField } from './SvField.svelte';
59
62
  export { createListbox, toSelectedArray, toSelectedSet, type Listbox, type ListboxConfig, type ListboxValue, type ListboxRootProps, type OptionProps, } from './createListbox.svelte.js';
60
63
  export { createCombobox, type Combobox, type ComboboxConfig, type ComboboxValue } from './createCombobox.svelte.js';
@@ -152,6 +155,7 @@ export { default as SvAvatarGroup } from './SvAvatarGroup.svelte';
152
155
  export { default as SvCommand, type CommandItem } from './SvCommand.svelte';
153
156
  export { fuzzyScore, fuzzyMatch } from './fuzzy.js';
154
157
  export { default as SvRichText, type RichTextTool } from './SvRichText.svelte';
158
+ export { default as SvRichCell } from './SvRichCell.svelte';
155
159
  export { default as SvCarousel } from './SvCarousel.svelte';
156
160
  export { default as SvTour, type TourStep } from './SvTour.svelte';
157
161
  export { default as SvPagination } from './SvPagination.svelte';
@@ -194,6 +198,7 @@ export { chartToSvgString, downloadChartSvg, chartToPngBlob, downloadChartPng, c
194
198
  export { buildSparkline, toSparklineValues, type SparklineConfig, type SparklineType, type SparklineGeometry, } from './sparkline.js';
195
199
  export { spreadsheetLayout, spansToMerges, type SpanColumn, type SpreadsheetActionOptions, type MergeSpec, type CellBorderSpec, type BorderSpec, } from './spreadsheet.js';
196
200
  export { rowResize, type RowResizeOptions } from './row-resize.js';
201
+ export { columnResize, type ColumnResizeOptions } from './column-resize.js';
197
202
  export { rowDropZone, type RowDragEndEvent, type RowDropZoneOptions } from './row-drag.js';
198
203
  export { resolveColumnTypes, inferCellDataType, type CellDataType } from './column-types.js';
199
204
  export { computeColumnGroupMeta, hiddenLeavesForCollapse, type ColumnGroupMeta, type ColumnGroupShow, } from './column-groups.js';
@@ -212,7 +217,7 @@ export { createVirtualizer } from './virtualization/virtualizer.js';
212
217
  export { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte.js';
213
218
  export { createColumnVirtualizer } from './virtualization/column-virtualizer.js';
214
219
  export type { VirtualItem, VirtualizerOptions, VirtualizerState } from './virtualization/types.js';
215
- export type { SvGridApi, SvGridFilterOperator, SvGridWrapperProps } from './svgrid-wrapper.types.js';
220
+ export type { SvGridApi, SvGridFilterOperator, SvGridViewState, SvGridWrapperProps, } from './svgrid-wrapper.types.js';
216
221
  export { parseEditorValue, normalizeEditorOptions, type CellEditorType, type CellEditorOption, } from './editors/cell-editors.js';
217
222
  export { applyExcelFilter, compileExcelFilter, normalizeForFilter, splitInTokens, joinInTokens, trailingInToken, type CompiledExcelFilter, type ExcelFilter, type ExcelFilterOperator, type ExcelFilterOptions, } from './filtering/excel-filters.js';
218
223
  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
@@ -15,6 +15,8 @@ export { registerBoardView, getBoardView, hasBoardView } from './board-view.svel
15
15
  export * from './ai.js';
16
16
  export { registerExportProvider, getExportProvider, } from './export-provider.js';
17
17
  export { registerSchedulerView, getSchedulerView, hasSchedulerView } from './scheduler-view.svelte.js';
18
+ // Bulk-action bar over the row selection. Prop + types free, renderer Pro.
19
+ export { registerSelectionBarView, getSelectionBarView, hasSelectionBarView } from './selection-bar-view.svelte.js';
18
20
  export { registerChartView, getChartView, hasChartView } from './chart-view.svelte.js';
19
21
  export { registerPivotEngine, getPivotEngine, hasPivotEngine, } from './pivot-view.svelte.js';
20
22
  // Pure scheduler layout/model helpers (framework-free, like recurrence/date-core).
@@ -73,6 +75,7 @@ export { default as SvAspectRatio } from './SvAspectRatio.svelte';
73
75
  // UI kit - custom cell-editor registry (register any component as a grid editor)
74
76
  export { registerCellEditor, getCellEditor, hasCellEditor, unregisterCellEditor, registeredCellEditorTypes, defaultEditorProps, resolveEditorProps, } from './editor-registry.js';
75
77
  export { registerBuiltinEditors } from './builtin-editors.js';
78
+ export { sanitizeHtml, renderMarkdown, htmlToText } from './sanitize-html.js';
76
79
  // UI kit - shared field chrome (label + hint + error + direction wrapper)
77
80
  export { default as SvField } from './SvField.svelte';
78
81
  // UI kit - headless cores (state + prop-getters you render yourself, like createSvGrid)
@@ -187,6 +190,7 @@ export { default as SvAvatarGroup } from './SvAvatarGroup.svelte';
187
190
  export { default as SvCommand } from './SvCommand.svelte';
188
191
  export { fuzzyScore, fuzzyMatch } from './fuzzy.js';
189
192
  export { default as SvRichText } from './SvRichText.svelte';
193
+ export { default as SvRichCell } from './SvRichCell.svelte';
190
194
  export { default as SvCarousel } from './SvCarousel.svelte';
191
195
  export { default as SvTour } from './SvTour.svelte';
192
196
  export { default as SvPagination } from './SvPagination.svelte';
@@ -230,7 +234,13 @@ export { buildChart, formatChartValue, rowsToChartSpec, niceScale, niceLogScale,
230
234
  export { chartToSvgString, downloadChartSvg, chartToPngBlob, downloadChartPng, chartSpecToCsv, downloadChartCsv, } from './chart-export.js';
231
235
  export { buildSparkline, toSparklineValues, } from './sparkline.js';
232
236
  export { spreadsheetLayout, spansToMerges, } from './spreadsheet.js';
237
+ // The two resize actions, exported as a matched pair. `<SvGrid>` drives both
238
+ // through the `rowResize` / `columnResize` props - both default OFF, both are
239
+ // turned on by setting them true, and the grid `import()`s the module itself so
240
+ // a grid that leaves them off carries neither. Import them directly only for a
241
+ // headless table, which renders its own rows and headers.
233
242
  export { rowResize } from './row-resize.js';
243
+ export { columnResize } from './column-resize.js';
234
244
  export { rowDropZone } from './row-drag.js';
235
245
  export { resolveColumnTypes, inferCellDataType } from './column-types.js';
236
246
  export { computeColumnGroupMeta, hiddenLeavesForCollapse, } from './column-groups.js';
@@ -7,6 +7,12 @@ export type ListOption = {
7
7
  group?: string;
8
8
  /** Optional color swatch (any CSS color) shown before the label. */
9
9
  color?: string;
10
+ /**
11
+ * Optional muted text pinned to the trailing edge of the row - a count, a
12
+ * shortcut, a unit. Not part of the label, so it does not affect matching
13
+ * or the accessible name.
14
+ */
15
+ hint?: string;
10
16
  };
11
17
  /**
12
18
  * Coerce a raw options array into well-formed ListOptions. A primitive item
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");
@@ -249,19 +249,52 @@ export function createMenus(ctx) {
249
249
  }
250
250
  ctx.valueFilters = { ...ctx.valueFilters, [columnId]: new Set(next) };
251
251
  }
252
+ /**
253
+ * With a search query typed, "select all" means the RESULTS - the same thing
254
+ * it means in a spreadsheet. It used to mean the whole column regardless, so
255
+ * the standard flow (type a query, tick select-all, get just the matches)
256
+ * cleared the entire selection instead: `isAllFacetsChecked` saw every value
257
+ * ticked, read that as "on", and turned everything off.
258
+ */
252
259
  function isAllFacetsChecked(columnId) {
253
260
  const selected = ctx.valueFilters[columnId];
254
- return !selected || selected.size >= ctx.columnMenuFacetValues.length;
261
+ const visible = ctx.columnMenuVisibleFacets;
262
+ const searching = visible.length !== ctx.columnMenuFacetValues.length;
263
+ if (!selected)
264
+ return true;
265
+ if (searching)
266
+ return visible.every((value) => selected.has(value));
267
+ return selected.size >= ctx.columnMenuFacetValues.length;
255
268
  }
256
269
  function toggleAllFacets(columnId) {
257
- if (isAllFacetsChecked(columnId)) {
258
- ctx.valueFilters = { ...ctx.valueFilters, [columnId]: new Set() };
270
+ const visible = ctx.columnMenuVisibleFacets;
271
+ const all = ctx.columnMenuFacetValues;
272
+ const searching = visible.length !== all.length;
273
+ const on = isAllFacetsChecked(columnId);
274
+ if (!searching) {
275
+ if (on) {
276
+ ctx.valueFilters = { ...ctx.valueFilters, [columnId]: new Set() };
277
+ }
278
+ else {
279
+ const copy = { ...ctx.valueFilters };
280
+ delete copy[columnId];
281
+ ctx.valueFilters = copy;
282
+ }
283
+ return;
259
284
  }
260
- else {
261
- const copy = { ...ctx.valueFilters };
262
- delete copy[columnId];
263
- ctx.valueFilters = copy;
285
+ // Searching: add or remove just the matches, leaving the rest of the
286
+ // selection alone. An absent entry means "everything", so it has to be
287
+ // materialized before a subtraction can mean anything.
288
+ const current = ctx.valueFilters[columnId]
289
+ ? new Set(ctx.valueFilters[columnId])
290
+ : new Set(all);
291
+ for (const value of visible) {
292
+ if (on)
293
+ current.delete(value);
294
+ else
295
+ current.add(value);
264
296
  }
297
+ ctx.valueFilters = { ...ctx.valueFilters, [columnId]: current };
265
298
  }
266
299
  function clearColumnFilter(columnId) {
267
300
  // `untrack`: read-modify-write on the filter maps. Keeps the method safe to
@@ -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}`))
@@ -0,0 +1,37 @@
1
+ /**
2
+ * sanitize-html - a dependency-free HTML sanitizer and a small Markdown
3
+ * renderer, sized for what a grid cell needs to display.
4
+ *
5
+ * A rich-text cell has to paint markup that came from a user, which is the one
6
+ * place a data grid can hand an attacker script execution. Everything rendered
7
+ * through `SvRichCell` goes through `sanitizeHtml` first, and the allowlist is
8
+ * deliberately narrower than a general-purpose sanitizer: it covers the tags
9
+ * `SvRichText` can produce, plus links and images.
10
+ *
11
+ * The parse uses the platform parser where there is one (`DOMParser`, which
12
+ * neither executes scripts nor runs loaders on a detached document) and a
13
+ * conservative regex stripper during SSR, where there is no DOM. The SSR path
14
+ * is stricter by design: when in doubt it drops markup rather than passing it
15
+ * through, and the hydrated client re-sanitizes with the real parser anyway.
16
+ *
17
+ * This is not a general-purpose sanitizer for arbitrary third-party HTML. It is
18
+ * a narrow allowlist for cell content. To render something outside the
19
+ * allowlist, extend it explicitly rather than widening the defaults.
20
+ */
21
+ /**
22
+ * Return `html` with everything outside the allowlist removed. Idempotent, so
23
+ * it is safe to call on already-sanitized markup.
24
+ */
25
+ export declare function sanitizeHtml(html: string | null | undefined): string;
26
+ /**
27
+ * Render a common Markdown subset to HTML: headings, bold, italic,
28
+ * strikethrough, inline code, links, images, and bullet / ordered lists.
29
+ *
30
+ * Deliberately not a full CommonMark implementation - a grid cell shows a line
31
+ * or two, and a complete parser is a dependency this package does not take. The
32
+ * output is sanitized, so a Markdown link cannot smuggle `javascript:`.
33
+ */
34
+ export declare function renderMarkdown(markdown: string | null | undefined): string;
35
+ /** Strip markup down to readable text, for exports, aria labels and tooltips
36
+ * where a cell's HTML has to collapse to one plain string. */
37
+ export declare function htmlToText(html: string | null | undefined): string;