@svgrid/grid 1.2.21 → 1.2.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 (190) hide show
  1. package/dist/GridFooter.svelte +34 -20
  2. package/dist/GridFooter.svelte.d.ts +8 -0
  3. package/dist/SvAutoComplete.svelte +100 -0
  4. package/dist/SvAutoComplete.svelte.d.ts +16 -0
  5. package/dist/SvButton.svelte +97 -0
  6. package/dist/SvButton.svelte.d.ts +23 -0
  7. package/dist/SvCalendar.svelte +669 -0
  8. package/dist/SvCalendar.svelte.d.ts +70 -0
  9. package/dist/SvCheckBox.svelte +81 -0
  10. package/dist/SvCheckBox.svelte.d.ts +19 -0
  11. package/dist/SvColorInput.svelte +187 -0
  12. package/dist/SvColorInput.svelte.d.ts +15 -0
  13. package/dist/SvComboBox.svelte +130 -0
  14. package/dist/SvComboBox.svelte.d.ts +15 -0
  15. package/dist/SvCountryInput.svelte +123 -0
  16. package/dist/SvCountryInput.svelte.d.ts +14 -0
  17. package/dist/SvDateTimePicker.svelte +322 -0
  18. package/dist/SvDateTimePicker.svelte.d.ts +37 -0
  19. package/dist/SvDropDownList.svelte +143 -0
  20. package/dist/SvDropDownList.svelte.d.ts +15 -0
  21. package/dist/SvForm.svelte +147 -0
  22. package/dist/SvForm.svelte.d.ts +29 -0
  23. package/dist/SvGauge.svelte +99 -0
  24. package/dist/SvGauge.svelte.d.ts +31 -0
  25. package/dist/SvGrid.controller.svelte.d.ts +16 -2
  26. package/dist/SvGrid.controller.svelte.js +63 -8
  27. package/dist/SvGrid.css +84 -31
  28. package/dist/SvGrid.helpers.d.ts +4 -0
  29. package/dist/SvGrid.helpers.js +40 -12
  30. package/dist/SvGrid.svelte +84 -5
  31. package/dist/SvGrid.types.d.ts +36 -0
  32. package/dist/SvGridDropdown.svelte +16 -30
  33. package/dist/SvGridDropdown.svelte.d.ts +4 -0
  34. package/dist/SvListBox.svelte +122 -0
  35. package/dist/SvListBox.svelte.d.ts +19 -0
  36. package/dist/SvMaskedInput.svelte +0 -0
  37. package/dist/SvMaskedInput.svelte.d.ts +15 -0
  38. package/dist/SvNumberInput.svelte +0 -0
  39. package/dist/SvNumberInput.svelte.d.ts +22 -0
  40. package/dist/SvPasswordInput.svelte +110 -0
  41. package/dist/SvPasswordInput.svelte.d.ts +22 -0
  42. package/dist/SvPhoneInput.svelte +0 -0
  43. package/dist/SvPhoneInput.svelte.d.ts +19 -0
  44. package/dist/SvRadioGroup.svelte +108 -0
  45. package/dist/SvRadioGroup.svelte.d.ts +23 -0
  46. package/dist/SvRating.svelte +111 -0
  47. package/dist/SvRating.svelte.d.ts +20 -0
  48. package/dist/SvRepeatButton.svelte +89 -0
  49. package/dist/SvRepeatButton.svelte.d.ts +21 -0
  50. package/dist/SvSlider.svelte +182 -0
  51. package/dist/SvSlider.svelte.d.ts +25 -0
  52. package/dist/SvSwitchButton.svelte +94 -0
  53. package/dist/SvSwitchButton.svelte.d.ts +19 -0
  54. package/dist/SvTabs.svelte +122 -0
  55. package/dist/SvTabs.svelte.d.ts +26 -0
  56. package/dist/SvTagsInput.svelte +81 -0
  57. package/dist/SvTagsInput.svelte.d.ts +19 -0
  58. package/dist/SvTimePicker.svelte +359 -0
  59. package/dist/SvTimePicker.svelte.d.ts +27 -0
  60. package/dist/SvToggleButton.svelte +57 -0
  61. package/dist/SvToggleButton.svelte.d.ts +16 -0
  62. package/dist/SvTree.svelte +210 -0
  63. package/dist/SvTree.svelte.d.ts +29 -0
  64. package/dist/build-api.js +78 -1
  65. package/dist/cdn/svgrid.js +10022 -5904
  66. package/dist/cdn/svgrid.svelte-external.js +8531 -4429
  67. package/dist/cell-render.d.ts +4 -0
  68. package/dist/cell-render.js +35 -1
  69. package/dist/clipboard.js +19 -1
  70. package/dist/collaboration.d.ts +2 -0
  71. package/dist/collaboration.js +4 -0
  72. package/dist/core.d.ts +22 -1
  73. package/dist/countries.d.ts +15 -0
  74. package/dist/countries.js +61 -0
  75. package/dist/datetime/date-core.d.ts +88 -0
  76. package/dist/datetime/date-core.js +165 -0
  77. package/dist/datetime/date-format.d.ts +39 -0
  78. package/dist/datetime/date-format.js +371 -0
  79. package/dist/datetime/date-restrict.d.ts +23 -0
  80. package/dist/datetime/date-restrict.js +50 -0
  81. package/dist/datetime/date-selection.d.ts +25 -0
  82. package/dist/datetime/date-selection.js +94 -0
  83. package/dist/datetime/mask.d.ts +21 -0
  84. package/dist/datetime/mask.js +90 -0
  85. package/dist/editing.d.ts +1 -1
  86. package/dist/editing.js +7 -0
  87. package/dist/editors/cell-editors.d.ts +2 -2
  88. package/dist/editors/cell-editors.js +3 -1
  89. package/dist/export-format.d.ts +178 -0
  90. package/dist/export-format.js +419 -0
  91. package/dist/hyperformula-adapter.d.ts +4 -0
  92. package/dist/hyperformula-adapter.js +15 -6
  93. package/dist/index.d.ts +36 -0
  94. package/dist/index.js +41 -0
  95. package/dist/list-option.d.ts +10 -0
  96. package/dist/list-option.js +7 -0
  97. package/dist/menus.js +18 -2
  98. package/dist/popover.d.ts +48 -0
  99. package/dist/popover.js +65 -0
  100. package/dist/row-resize.d.ts +4 -4
  101. package/dist/row-resize.js +8 -5
  102. package/dist/selection.d.ts +6 -0
  103. package/dist/selection.js +31 -5
  104. package/dist/server-data-source.d.ts +43 -0
  105. package/dist/server-data-source.js +100 -0
  106. package/dist/sparkline.js +8 -4
  107. package/dist/svgrid-wrapper.types.d.ts +26 -1
  108. package/package.json +6 -1
  109. package/src/GridFooter.svelte +34 -20
  110. package/src/SvAutoComplete.svelte +100 -0
  111. package/src/SvButton.svelte +97 -0
  112. package/src/SvCalendar.svelte +669 -0
  113. package/src/SvCalendar.test.ts +211 -0
  114. package/src/SvCheckBox.svelte +81 -0
  115. package/src/SvColorInput.svelte +187 -0
  116. package/src/SvComboBox.svelte +130 -0
  117. package/src/SvCountryInput.svelte +123 -0
  118. package/src/SvDateTimePicker.svelte +322 -0
  119. package/src/SvDateTimePicker.test.ts +136 -0
  120. package/src/SvDropDownList.svelte +143 -0
  121. package/src/SvForm.svelte +147 -0
  122. package/src/SvGauge.svelte +99 -0
  123. package/src/SvGrid.controller.svelte.ts +74 -8
  124. package/src/SvGrid.css +84 -31
  125. package/src/SvGrid.helpers.ts +41 -12
  126. package/src/SvGrid.svelte +84 -5
  127. package/src/SvGrid.types.ts +33 -0
  128. package/src/SvGridDropdown.svelte +16 -30
  129. package/src/SvListBox.svelte +122 -0
  130. package/src/SvMaskedInput.svelte +0 -0
  131. package/src/SvNumberInput.svelte +0 -0
  132. package/src/SvPasswordInput.svelte +110 -0
  133. package/src/SvPhoneInput.svelte +0 -0
  134. package/src/SvRadioGroup.svelte +108 -0
  135. package/src/SvRating.svelte +111 -0
  136. package/src/SvRepeatButton.svelte +89 -0
  137. package/src/SvSlider.svelte +182 -0
  138. package/src/SvSwitchButton.svelte +94 -0
  139. package/src/SvTabs.svelte +122 -0
  140. package/src/SvTagsInput.svelte +81 -0
  141. package/src/SvTimePicker.svelte +359 -0
  142. package/src/SvTimePicker.test.ts +98 -0
  143. package/src/SvToggleButton.svelte +57 -0
  144. package/src/SvTree.svelte +210 -0
  145. package/src/build-api.ts +115 -0
  146. package/src/cell-render.test.ts +38 -0
  147. package/src/cell-render.ts +43 -0
  148. package/src/clipboard.test.ts +18 -0
  149. package/src/clipboard.ts +24 -1
  150. package/src/collaboration.test.ts +30 -0
  151. package/src/collaboration.ts +6 -0
  152. package/src/core.ts +27 -3
  153. package/src/countries.ts +71 -0
  154. package/src/datetime/date-core.test.ts +217 -0
  155. package/src/datetime/date-core.ts +204 -0
  156. package/src/datetime/date-format.test.ts +121 -0
  157. package/src/datetime/date-format.ts +317 -0
  158. package/src/datetime/date-restrict.ts +60 -0
  159. package/src/datetime/date-selection.test.ts +129 -0
  160. package/src/datetime/date-selection.ts +137 -0
  161. package/src/datetime/mask.test.ts +36 -0
  162. package/src/datetime/mask.ts +84 -0
  163. package/src/editing.test.ts +22 -0
  164. package/src/editing.ts +7 -1
  165. package/src/editors/cell-editors.ts +7 -1
  166. package/src/export-data-api.test.ts +126 -0
  167. package/src/export-format.test.ts +107 -0
  168. package/src/export-format.ts +598 -0
  169. package/src/hyperformula-adapter.test.ts +35 -1
  170. package/src/hyperformula-adapter.ts +18 -6
  171. package/src/index.ts +71 -0
  172. package/src/list-option.ts +15 -0
  173. package/src/menus.ts +21 -2
  174. package/src/popover.ts +79 -0
  175. package/src/row-resize.test.ts +25 -0
  176. package/src/row-resize.ts +8 -5
  177. package/src/selection.test.ts +36 -4
  178. package/src/selection.ts +30 -9
  179. package/src/server-data-source.test.ts +201 -0
  180. package/src/server-data-source.ts +148 -0
  181. package/src/sparkline.test.ts +9 -0
  182. package/src/sparkline.ts +9 -4
  183. package/src/svgrid-wrapper.types.ts +28 -1
  184. package/src/svgrid.behavior.test.ts +27 -0
  185. package/src/svgrid.new-features.wrapper.test.ts +27 -1
  186. package/src/ui-buttons.test.ts +105 -0
  187. package/src/ui-composite.test.ts +89 -0
  188. package/src/ui-inputs.test.ts +92 -0
  189. package/src/ui-range.test.ts +61 -0
  190. package/src/ui-selection.test.ts +106 -0
package/dist/index.d.ts CHANGED
@@ -1,9 +1,45 @@
1
1
  export { formatNumericWithConfig, resolveDatePattern } from './cell-formatting';
2
+ export { coerceExportDate, formatValueForExport, projectGridRows, toExcelNumFmt, valueForExcel, serializeDelimited, serializeJson, serializeMarkdown, serializeHtml, serializeXml, downloadTextFile, downloadBlobFile, copyTextToClipboard, type GridExportOptions, type GridExportScope, type GridExportColumn, type GridClipboardOptions, type GridClipboardFormat, type SerializeProgress, type SerializeOptions, type CsvOptions, } from './export-format';
2
3
  export { columnFilteringFeature, columnGroupingFeature, createCoreRowModel, createExpandedRowModel, createFilteredRowModel, createGroupedRowModel, createPaginatedRowModel, createSortedRowModel, applyGroupAggregate, filterFns, rowExpandingFeature, rowPaginationFeature, rowSelectionFeature, rowSortingFeature, sortFns, tableFeatures, type Cell, type CellContext, type CellSpanParams, type ValueParserParams, type EditorContext, type CellData, type ActiveCellState, type CellFormatConfig, type CellFormatter, type Column, type ColumnDef, type ColumnDefTemplate, type GroupAggregator, type Header, type HeaderContext, type HeaderGroup, type Row, type RowData, type SortingState, type SvGrid as SvGridInstance, type SvGridOptions, type TableFeatures, type Updater, } from './core';
3
4
  export { createGrid, createSvGrid, type SvelteGrid } from './createGrid.svelte';
4
5
  export { createGridState, createSvGridState } from './createGridState.svelte';
5
6
  export { subscribeGrid, subscribeSvGrid } from './subscribe';
6
7
  export { default as SvGrid } from './SvGrid.svelte';
8
+ export { default as SvGridDropdown } from './SvGridDropdown.svelte';
9
+ export { default as SvCalendar, type CalendarValue, type CalendarPreset, type CalendarAnimation } from './SvCalendar.svelte';
10
+ export { default as SvTimePicker, type TimeValue } from './SvTimePicker.svelte';
11
+ export { default as SvDateTimePicker, type DateTimeValue } from './SvDateTimePicker.svelte';
12
+ export { default as SvButton } from './SvButton.svelte';
13
+ export { default as SvRepeatButton } from './SvRepeatButton.svelte';
14
+ export { default as SvToggleButton } from './SvToggleButton.svelte';
15
+ export { default as SvSwitchButton } from './SvSwitchButton.svelte';
16
+ export { default as SvCheckBox } from './SvCheckBox.svelte';
17
+ export { default as SvRadioGroup, type RadioOption } from './SvRadioGroup.svelte';
18
+ export { default as SvRating } from './SvRating.svelte';
19
+ export { default as SvNumberInput } from './SvNumberInput.svelte';
20
+ export { default as SvPasswordInput } from './SvPasswordInput.svelte';
21
+ export { default as SvMaskedInput } from './SvMaskedInput.svelte';
22
+ export { default as SvPhoneInput } from './SvPhoneInput.svelte';
23
+ export { default as SvColorInput } from './SvColorInput.svelte';
24
+ export { applyMask, unmask, isMaskComplete } from './datetime/mask';
25
+ export { COUNTRIES, COUNTRY_BY_CODE, flagEmoji, type Country } from './countries';
26
+ export { default as SvListBox } from './SvListBox.svelte';
27
+ export { default as SvDropDownList } from './SvDropDownList.svelte';
28
+ export { default as SvComboBox } from './SvComboBox.svelte';
29
+ export { default as SvAutoComplete } from './SvAutoComplete.svelte';
30
+ export { default as SvTagsInput } from './SvTagsInput.svelte';
31
+ export { default as SvCountryInput } from './SvCountryInput.svelte';
32
+ export { filterOptions, type ListOption } from './list-option';
33
+ export { default as SvSlider } from './SvSlider.svelte';
34
+ export { default as SvGauge } from './SvGauge.svelte';
35
+ export { default as SvTabs, type TabItem } from './SvTabs.svelte';
36
+ export { default as SvTree, type TreeNode as SvTreeNode } from './SvTree.svelte';
37
+ export { default as SvForm, type FormField, type FormFieldType } from './SvForm.svelte';
38
+ export { anchoredRect, portalToBody, PANEL_THEME_VARS, type AnchoredRect } from './popover';
39
+ export { toDate, startOfDay, addDays, addMonths, addYears, clampDate, isSameDay, monthMatrix, isoWeek, decadeRange, withTime, snapMinute, type DateLike, } from './datetime/date-core';
40
+ export { formatDate, parseDate, tokenizeMask } from './datetime/date-format';
41
+ export { selectDate, rangeDays, isSelected, isMultiSelectMode, emptySelection, type SelectionMode, type SelectionState, } from './datetime/date-selection';
42
+ export { isDisabledDay, isOutOfRange, isRestricted, isImportant, type RestrictOptions, } from './datetime/date-restrict';
7
43
  export { default as FlexRender } from './FlexRender.svelte';
8
44
  export { renderComponent, renderSnippet } from './render-component';
9
45
  export { default as SvGridChart } from './SvGridChart.svelte';
package/dist/index.js CHANGED
@@ -1,9 +1,50 @@
1
1
  export { formatNumericWithConfig, resolveDatePattern } from './cell-formatting';
2
+ export { coerceExportDate, formatValueForExport, projectGridRows, toExcelNumFmt, valueForExcel, serializeDelimited, serializeJson, serializeMarkdown, serializeHtml, serializeXml, downloadTextFile, downloadBlobFile, copyTextToClipboard, } from './export-format';
2
3
  export { columnFilteringFeature, columnGroupingFeature, createCoreRowModel, createExpandedRowModel, createFilteredRowModel, createGroupedRowModel, createPaginatedRowModel, createSortedRowModel, applyGroupAggregate, filterFns, rowExpandingFeature, rowPaginationFeature, rowSelectionFeature, rowSortingFeature, sortFns, tableFeatures, } from './core';
3
4
  export { createGrid, createSvGrid } from './createGrid.svelte';
4
5
  export { createGridState, createSvGridState } from './createGridState.svelte';
5
6
  export { subscribeGrid, subscribeSvGrid } from './subscribe';
6
7
  export { default as SvGrid } from './SvGrid.svelte';
8
+ export { default as SvGridDropdown } from './SvGridDropdown.svelte';
9
+ export { default as SvCalendar } from './SvCalendar.svelte';
10
+ export { default as SvTimePicker } from './SvTimePicker.svelte';
11
+ export { default as SvDateTimePicker } from './SvDateTimePicker.svelte';
12
+ // UI kit - Group B (buttons & toggles)
13
+ export { default as SvButton } from './SvButton.svelte';
14
+ export { default as SvRepeatButton } from './SvRepeatButton.svelte';
15
+ export { default as SvToggleButton } from './SvToggleButton.svelte';
16
+ export { default as SvSwitchButton } from './SvSwitchButton.svelte';
17
+ export { default as SvCheckBox } from './SvCheckBox.svelte';
18
+ export { default as SvRadioGroup } from './SvRadioGroup.svelte';
19
+ export { default as SvRating } from './SvRating.svelte';
20
+ // UI kit - Group C (text inputs)
21
+ export { default as SvNumberInput } from './SvNumberInput.svelte';
22
+ export { default as SvPasswordInput } from './SvPasswordInput.svelte';
23
+ export { default as SvMaskedInput } from './SvMaskedInput.svelte';
24
+ export { default as SvPhoneInput } from './SvPhoneInput.svelte';
25
+ export { default as SvColorInput } from './SvColorInput.svelte';
26
+ export { applyMask, unmask, isMaskComplete } from './datetime/mask';
27
+ export { COUNTRIES, COUNTRY_BY_CODE, flagEmoji } from './countries';
28
+ // UI kit - Group D (selection overlays)
29
+ export { default as SvListBox } from './SvListBox.svelte';
30
+ export { default as SvDropDownList } from './SvDropDownList.svelte';
31
+ export { default as SvComboBox } from './SvComboBox.svelte';
32
+ export { default as SvAutoComplete } from './SvAutoComplete.svelte';
33
+ export { default as SvTagsInput } from './SvTagsInput.svelte';
34
+ export { default as SvCountryInput } from './SvCountryInput.svelte';
35
+ export { filterOptions } from './list-option';
36
+ // UI kit - Group E (range & feedback)
37
+ export { default as SvSlider } from './SvSlider.svelte';
38
+ export { default as SvGauge } from './SvGauge.svelte';
39
+ // UI kit - Group F (composite / layout)
40
+ export { default as SvTabs } from './SvTabs.svelte';
41
+ export { default as SvTree } from './SvTree.svelte';
42
+ export { default as SvForm } from './SvForm.svelte';
43
+ export { anchoredRect, portalToBody, PANEL_THEME_VARS } from './popover';
44
+ export { toDate, startOfDay, addDays, addMonths, addYears, clampDate, isSameDay, monthMatrix, isoWeek, decadeRange, withTime, snapMinute, } from './datetime/date-core';
45
+ export { formatDate, parseDate, tokenizeMask } from './datetime/date-format';
46
+ export { selectDate, rangeDays, isSelected, isMultiSelectMode, emptySelection, } from './datetime/date-selection';
47
+ export { isDisabledDay, isOutOfRange, isRestricted, isImportant, } from './datetime/date-restrict';
7
48
  export { default as FlexRender } from './FlexRender.svelte';
8
49
  export { renderComponent, renderSnippet } from './render-component';
9
50
  export { default as SvGridChart } from './SvGridChart.svelte';
@@ -0,0 +1,10 @@
1
+ /** Shared option shape for the SvGrid UI-kit selection controls. */
2
+ export type ListOption = {
3
+ value: string | number;
4
+ label: string;
5
+ disabled?: boolean;
6
+ /** Optional group heading this option belongs under. */
7
+ group?: string;
8
+ };
9
+ /** Case-insensitive substring filter over option labels. */
10
+ export declare function filterOptions(options: ReadonlyArray<ListOption>, query: string): ListOption[];
@@ -0,0 +1,7 @@
1
+ /** Case-insensitive substring filter over option labels. */
2
+ export function filterOptions(options, query) {
3
+ const q = query.trim().toLowerCase();
4
+ if (!q)
5
+ return [...options];
6
+ return options.filter((o) => o.label.toLowerCase().includes(q));
7
+ }
package/dist/menus.js CHANGED
@@ -216,21 +216,37 @@ export function createMenus(ctx) {
216
216
  ctx.filterRowValues = next;
217
217
  }
218
218
  }
219
+ // External pagination is controlled: emit the requested page/size and let
220
+ // the consumer fetch + update props. Never mutate local pagination state.
221
+ function emitExternalPagination(pageIndex, pageSize) {
222
+ if (ctx.props.externalPagination !== true)
223
+ return false;
224
+ ctx.props.onPaginationChange?.({ pageIndex: Math.max(0, pageIndex), pageSize });
225
+ ctx.scrollContainer?.scrollTo?.({ top: 0 });
226
+ return true;
227
+ }
219
228
  function changePage(delta) {
229
+ if (emitExternalPagination((ctx.props.pageIndex ?? 0) + delta, ctx.props.pageSize ?? 10))
230
+ return;
220
231
  ctx.grid.setPagination((prev) => ({
221
232
  ...prev,
222
233
  pageIndex: Math.max((prev?.pageIndex ?? 0) + delta, 0),
223
234
  }));
224
- ctx.scrollContainer?.scrollTo({ top: 0 });
235
+ ctx.scrollContainer?.scrollTo?.({ top: 0 });
225
236
  }
226
237
  function goToPage(pageIndex) {
238
+ if (emitExternalPagination(pageIndex, ctx.props.pageSize ?? 10))
239
+ return;
227
240
  ctx.grid.setPagination((prev) => ({
228
241
  ...prev,
229
242
  pageIndex: Math.max(0, pageIndex),
230
243
  }));
231
- ctx.scrollContainer?.scrollTo({ top: 0 });
244
+ ctx.scrollContainer?.scrollTo?.({ top: 0 });
232
245
  }
233
246
  function setPageSize(pageSize) {
247
+ // External mode: changing page size resets to the first page.
248
+ if (emitExternalPagination(0, pageSize))
249
+ return;
234
250
  ctx.grid.setPagination((prev) => {
235
251
  const oldSize = prev?.pageSize ?? 10;
236
252
  const oldIndex = prev?.pageIndex ?? 0;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * popover - shared helpers for panels that must escape the grid's
3
+ * `overflow:hidden` scroll container by portalling to <body>: the theme-var
4
+ * snapshot + portal action, and the fixed-position anchor/flip math. Rune-free
5
+ * and DOM-only so it is reused verbatim by SvGridDropdown, SvDateTimePicker and
6
+ * any future popover. The per-component `$effect` wiring (scroll/resize
7
+ * reposition, outside-click close) stays in the component since it needs the
8
+ * component's own open state.
9
+ */
10
+ /**
11
+ * Theme tokens a portalled panel must carry with it. `--sg-*` custom properties
12
+ * are scoped to whatever wrapper the grid sits inside (e.g. a per-preset theme
13
+ * class); moving the panel to <body> leaves that scope, so we snapshot the
14
+ * resolved values and pin them inline. Keep this list in sync with the tokens
15
+ * any popover panel actually consumes.
16
+ */
17
+ export declare const PANEL_THEME_VARS: readonly ["--sg-accent", "--sg-bg", "--sg-fg", "--sg-border", "--sg-muted", "--sg-header-bg", "--sg-row-hover-bg", "--sg-selection-bg", "--sg-input-bg", "--sg-input-border", "--sg-selection-fg"];
18
+ /**
19
+ * Svelte action: snapshot the resolved theme tokens from the node's current
20
+ * (in-scope) position, then detach it and append to <body> so it can never be
21
+ * clipped by an ancestor's overflow. Removes itself on destroy.
22
+ */
23
+ export declare function portalToBody(node: HTMLElement, vars?: ReadonlyArray<string>): {
24
+ destroy(): void;
25
+ };
26
+ export type AnchoredRect = {
27
+ top: number;
28
+ left: number;
29
+ width: number;
30
+ openUpward: boolean;
31
+ };
32
+ export type AnchorOptions = {
33
+ /** Estimated panel height, used to decide whether to flip upward. */
34
+ estimatedHeight: number;
35
+ /** Gap between trigger and panel. Default 2px. */
36
+ gap?: number;
37
+ /** Force the panel at least this wide (else it matches the trigger). */
38
+ minWidth?: number;
39
+ /** Keep the panel within the viewport horizontally. Default true. */
40
+ clampHorizontal?: boolean;
41
+ };
42
+ /**
43
+ * Compute a `position: fixed` rect anchored to `triggerRect`, flipping above the
44
+ * trigger when there isn't room below and there's more room above. Mirrors the
45
+ * behavior SvGridDropdown shipped with, generalized with min-width + horizontal
46
+ * clamping for wider panels (date/time popovers).
47
+ */
48
+ export declare function anchoredRect(triggerRect: DOMRect, opts: AnchorOptions): AnchoredRect;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * popover - shared helpers for panels that must escape the grid's
3
+ * `overflow:hidden` scroll container by portalling to <body>: the theme-var
4
+ * snapshot + portal action, and the fixed-position anchor/flip math. Rune-free
5
+ * and DOM-only so it is reused verbatim by SvGridDropdown, SvDateTimePicker and
6
+ * any future popover. The per-component `$effect` wiring (scroll/resize
7
+ * reposition, outside-click close) stays in the component since it needs the
8
+ * component's own open state.
9
+ */
10
+ /**
11
+ * Theme tokens a portalled panel must carry with it. `--sg-*` custom properties
12
+ * are scoped to whatever wrapper the grid sits inside (e.g. a per-preset theme
13
+ * class); moving the panel to <body> leaves that scope, so we snapshot the
14
+ * resolved values and pin them inline. Keep this list in sync with the tokens
15
+ * any popover panel actually consumes.
16
+ */
17
+ export const PANEL_THEME_VARS = [
18
+ '--sg-accent', '--sg-bg', '--sg-fg', '--sg-border', '--sg-muted',
19
+ '--sg-header-bg', '--sg-row-hover-bg', '--sg-selection-bg',
20
+ '--sg-input-bg', '--sg-input-border', '--sg-selection-fg',
21
+ ];
22
+ /**
23
+ * Svelte action: snapshot the resolved theme tokens from the node's current
24
+ * (in-scope) position, then detach it and append to <body> so it can never be
25
+ * clipped by an ancestor's overflow. Removes itself on destroy.
26
+ */
27
+ export function portalToBody(node, vars = PANEL_THEME_VARS) {
28
+ const cs = getComputedStyle(node);
29
+ for (const v of vars) {
30
+ const val = cs.getPropertyValue(v).trim();
31
+ if (val)
32
+ node.style.setProperty(v, val);
33
+ }
34
+ document.body.appendChild(node);
35
+ return {
36
+ destroy() {
37
+ if (node.parentNode === document.body)
38
+ document.body.removeChild(node);
39
+ },
40
+ };
41
+ }
42
+ /**
43
+ * Compute a `position: fixed` rect anchored to `triggerRect`, flipping above the
44
+ * trigger when there isn't room below and there's more room above. Mirrors the
45
+ * behavior SvGridDropdown shipped with, generalized with min-width + horizontal
46
+ * clamping for wider panels (date/time popovers).
47
+ */
48
+ export function anchoredRect(triggerRect, opts) {
49
+ const gap = opts.gap ?? 2;
50
+ const spaceBelow = window.innerHeight - triggerRect.bottom;
51
+ const spaceAbove = triggerRect.top;
52
+ const openUpward = spaceBelow < opts.estimatedHeight && spaceAbove > spaceBelow;
53
+ const width = Math.max(triggerRect.width, opts.minWidth ?? 0);
54
+ let left = triggerRect.left;
55
+ if (opts.clampHorizontal !== false) {
56
+ const maxLeft = window.innerWidth - width - 4;
57
+ left = Math.max(4, Math.min(left, maxLeft));
58
+ }
59
+ return {
60
+ top: openUpward ? triggerRect.top - opts.estimatedHeight - gap : triggerRect.bottom + gap,
61
+ left,
62
+ width,
63
+ openUpward,
64
+ };
65
+ }
@@ -4,10 +4,10 @@
4
4
  * reads per-row heights from a reactive store.
5
5
  *
6
6
  * The action is OFF until a consumer attaches it AND a row-header
7
- * gutter cell exists in each row. A row-header cell is any TD with
8
- * the `sv-row-gutter` class (typically the one rendering the row
9
- * number 1, 2, 3, ...). Demos opt in by setting
10
- * `cellClass: 'sv-row-gutter'` on the gutter column.
7
+ * gutter cell exists in each row. A row-header cell is either the
8
+ * grid's built-in row-number gutter (enable `showRowNumbers`) or any
9
+ * TD tagged with the `sv-row-gutter` class (set
10
+ * `cellClass: 'sv-row-gutter'` on a custom gutter column).
11
11
  *
12
12
  * <script>
13
13
  * let heights = $state<Record<number, number>>({})
@@ -4,10 +4,10 @@
4
4
  * reads per-row heights from a reactive store.
5
5
  *
6
6
  * The action is OFF until a consumer attaches it AND a row-header
7
- * gutter cell exists in each row. A row-header cell is any TD with
8
- * the `sv-row-gutter` class (typically the one rendering the row
9
- * number 1, 2, 3, ...). Demos opt in by setting
10
- * `cellClass: 'sv-row-gutter'` on the gutter column.
7
+ * gutter cell exists in each row. A row-header cell is either the
8
+ * grid's built-in row-number gutter (enable `showRowNumbers`) or any
9
+ * TD tagged with the `sv-row-gutter` class (set
10
+ * `cellClass: 'sv-row-gutter'` on a custom gutter column).
11
11
  *
12
12
  * <script>
13
13
  * let heights = $state<Record<number, number>>({})
@@ -103,7 +103,10 @@ export function rowResize(node, opts) {
103
103
  continue;
104
104
  if (tr.classList.contains('sv-grid-row-spacer'))
105
105
  continue;
106
- const gutter = tr.querySelector('.sv-grid-cell.sv-row-gutter');
106
+ // A row-header gutter cell: either a user column tagged
107
+ // `sv-row-gutter`, or the grid's built-in row-number gutter
108
+ // (`showRowNumbers`), whose cell is `.sv-grid-row-number-cell`.
109
+ const gutter = tr.querySelector('.sv-grid-cell.sv-row-gutter, .sv-grid-cell.sv-grid-row-number-cell');
107
110
  if (!gutter)
108
111
  continue;
109
112
  if (gutter.querySelector(`:scope > .${STRIP_CLASS}`))
@@ -15,6 +15,12 @@ export declare function createSelection<TFeatures extends TableFeatures = TableF
15
15
  left: boolean;
16
16
  right: boolean;
17
17
  } | null;
18
+ fillMarqueeEdges: (rowIndex: number, colIndex: number) => {
19
+ top: boolean;
20
+ bottom: boolean;
21
+ left: boolean;
22
+ right: boolean;
23
+ } | null;
18
24
  getSelectionRects: () => {
19
25
  minRow: number;
20
26
  maxRow: number;
package/dist/selection.js CHANGED
@@ -264,6 +264,31 @@ export function createSelection(ctx) {
264
264
  colIndex <= d.sourceMaxCol;
265
265
  return !inSource;
266
266
  }
267
+ /**
268
+ * Which edges of the fill-drag rectangle a cell sits on, so the render can
269
+ * paint a single dashed marquee around the WHOLE target range (source +
270
+ * extension) - the Excel fill-handle look - instead of a thick per-cell
271
+ * border. Returns null when the cell isn't on the rectangle's boundary.
272
+ */
273
+ function fillMarqueeEdges(rowIndex, colIndex) {
274
+ const d = ctx.fillDrag;
275
+ if (!d)
276
+ return null;
277
+ const minR = Math.min(d.sourceMinRow, d.targetRow);
278
+ const maxR = Math.max(d.sourceMaxRow, d.targetRow);
279
+ const minC = Math.min(d.sourceMinCol, d.targetCol);
280
+ const maxC = Math.max(d.sourceMaxCol, d.targetCol);
281
+ if (rowIndex < minR || rowIndex > maxR || colIndex < minC || colIndex > maxC) {
282
+ return null;
283
+ }
284
+ const top = rowIndex === minR;
285
+ const bottom = rowIndex === maxR;
286
+ const left = colIndex === minC;
287
+ const right = colIndex === maxC;
288
+ if (!top && !bottom && !left && !right)
289
+ return null;
290
+ return { top, bottom, left, right };
291
+ }
267
292
  /** Look up a column by id without depending on `buildApi`'s private
268
293
  * closure (those helpers don't exist at this scope). */
269
294
  function findColumnById(columnId) {
@@ -276,11 +301,11 @@ export function createSelection(ctx) {
276
301
  const column = ctx.allColumns[colIndex];
277
302
  if (!row || !column || isGroupRow(row))
278
303
  return;
279
- const cellValue = ctx.getCellDisplayValue(row.id, column.id, getColumnBaseValue(row, column));
280
- const isCheckboxColumn = column.columnDef.editorType === "checkbox" ||
281
- typeof cellValue === "boolean";
282
- if (isCheckboxColumn)
283
- return; // let onCellClick toggle the checkbox
304
+ // NOTE: we no longer bail for checkbox columns. Bailing here meant a
305
+ // range drag could never START on a checkbox cell. A plain click still
306
+ // toggles the checkbox via onCellClick (a same-cell click), while a
307
+ // cross-cell drag fires its `click` on a common ancestor - not the cell -
308
+ // so it selects a range without toggling.
284
309
  const active = ctx.grid.getState().activeCell;
285
310
  ctx.activeAtPointerDown = active
286
311
  ? { rowIndex: active.rowIndex, colIndex: active.colIndex }
@@ -414,6 +439,7 @@ export function createSelection(ctx) {
414
439
  extendSelection,
415
440
  isCellInSelectedRange,
416
441
  getCellRangeEdges,
442
+ fillMarqueeEdges,
417
443
  getSelectionRects,
418
444
  isInFillPreview,
419
445
  findColumnById,
@@ -8,6 +8,12 @@
8
8
  * It's headless and framework-agnostic on purpose: wire `setSort` /
9
9
  * `setFilter` / `setPage` to the grid's controlled callbacks, and render the
10
10
  * grid from the `{ rows, total, loading }` it hands you. See the demo.
11
+ *
12
+ * The write side (`createRow` / `updateRow` / `deleteRow`) is optional: a
13
+ * source that only implements `getRows` stays a pure read model, and the
14
+ * matching controller methods throw a clear error if called. Mutations are
15
+ * non-optimistic for now - the grid reflects a change only after the
16
+ * follow-up re-fetch of the current page lands.
11
17
  */
12
18
  export type ServerSortModel = Array<{
13
19
  id: string;
@@ -45,11 +51,22 @@ export type ServerResult<TData> = {
45
51
  };
46
52
  export type ServerDataSource<TData> = {
47
53
  getRows(request: ServerRequest): Promise<ServerResult<TData>>;
54
+ /**
55
+ * Optional write side. Implement whichever your backend supports; the
56
+ * controller exposes matching `createRow` / `updateRow` / `deleteRow`
57
+ * methods that call through and then `refresh()` the current page.
58
+ * Calling a controller method whose source counterpart is missing throws.
59
+ */
60
+ createRow?(input: Partial<TData>): Promise<TData>;
61
+ updateRow?(id: string, patch: Partial<TData>): Promise<TData>;
62
+ deleteRow?(id: string): Promise<void>;
48
63
  };
49
64
  export type ServerState<TData> = {
50
65
  rows: ReadonlyArray<TData>;
51
66
  total: number;
52
67
  loading: boolean;
68
+ /** True while a create / update / delete mutation is in flight. */
69
+ saving: boolean;
53
70
  error: unknown;
54
71
  pageIndex: number;
55
72
  pageSize: number;
@@ -64,6 +81,23 @@ export type ServerController<TData> = {
64
81
  setFilter(filterModel: ServerFilterModel): void;
65
82
  setPage(pageIndex: number): void;
66
83
  setPageSize(pageSize: number): void;
84
+ /**
85
+ * Create a row through the source, then refresh the current page. Resolves
86
+ * with the created row. Rejects if the source has no `createRow` (or if the
87
+ * create itself fails - the read state is left untouched on failure).
88
+ */
89
+ createRow(input: Partial<TData>): Promise<TData>;
90
+ /**
91
+ * Update a row by id through the source. Non-optimistic: refreshes the page.
92
+ * Optimistic (see `optimistic` + `getRowId` options): patches the local row
93
+ * immediately, then reconciles with the server result, rolling back on error.
94
+ */
95
+ updateRow(id: string, patch: Partial<TData>): Promise<TData>;
96
+ /**
97
+ * Delete a row by id through the source. Non-optimistic: refreshes the page.
98
+ * Optimistic: removes the local row immediately, restoring it on error.
99
+ */
100
+ deleteRow(id: string): Promise<void>;
67
101
  getState(): ServerState<TData>;
68
102
  /** Stop accepting in-flight responses (call on unmount). */
69
103
  dispose(): void;
@@ -72,5 +106,14 @@ export type ServerControllerOptions<TData> = {
72
106
  pageSize?: number;
73
107
  /** Called whenever any of `rows` / `total` / `loading` / page changes. */
74
108
  onChange: (state: ServerState<TData>) => void;
109
+ /**
110
+ * Apply `updateRow` / `deleteRow` to the local rows immediately (before the
111
+ * server confirms) and roll back on error - so edits feel instant and no
112
+ * refetch is needed. Requires `getRowId` to locate rows; ignored without it.
113
+ * A subsequent `refresh()` reconciles ordering/filtering. Default false.
114
+ */
115
+ optimistic?: boolean;
116
+ /** Resolve a row's stable id, so optimistic update/delete can find it in `rows`. */
117
+ getRowId?: (row: TData) => string;
75
118
  };
76
119
  export declare function createServerDataSource<TData>(source: ServerDataSource<TData>, options: ServerControllerOptions<TData>): ServerController<TData>;
@@ -3,6 +3,7 @@ export function createServerDataSource(source, options) {
3
3
  rows: [],
4
4
  total: 0,
5
5
  loading: false,
6
+ saving: false,
6
7
  error: null,
7
8
  pageIndex: 0,
8
9
  pageSize: options.pageSize ?? 50,
@@ -51,8 +52,100 @@ export function createServerDataSource(source, options) {
51
52
  emit();
52
53
  }
53
54
  }
55
+ // Shared mutation lifecycle: flip `saving`, run the write, refresh the
56
+ // current page on success, and always clear `saving`. A missing source
57
+ // method (or a disposed controller) rejects before anything is emitted.
58
+ function mutate(name, thunk) {
59
+ if (disposed) {
60
+ return Promise.reject(new Error('createServerDataSource: controller is disposed'));
61
+ }
62
+ if (!thunk) {
63
+ return Promise.reject(new Error(`createServerDataSource: the datasource does not implement ${name}()`));
64
+ }
65
+ state.saving = true;
66
+ emit();
67
+ return (async () => {
68
+ try {
69
+ const result = await thunk();
70
+ await fetchPage();
71
+ return result;
72
+ }
73
+ finally {
74
+ state.saving = false;
75
+ emit();
76
+ }
77
+ })();
78
+ }
79
+ const optimistic = !!options.optimistic && !!options.getRowId;
80
+ const getRowId = options.getRowId;
81
+ // Optimistic update: patch the local row, reconcile with the server result,
82
+ // roll back on error. Falls back to the plain refresh path when the row
83
+ // isn't on the current page (nothing local to update).
84
+ async function optimisticUpdate(id, patch, fn) {
85
+ if (disposed)
86
+ throw new Error('createServerDataSource: controller is disposed');
87
+ const prevRows = state.rows;
88
+ const idx = prevRows.findIndex((r) => getRowId(r) === id);
89
+ if (idx < 0)
90
+ return mutate('updateRow', () => fn(id, patch));
91
+ state.rows = [...prevRows.slice(0, idx), { ...prevRows[idx], ...patch }, ...prevRows.slice(idx + 1)];
92
+ state.saving = true;
93
+ emit();
94
+ try {
95
+ const result = await fn(id, patch);
96
+ state.rows = state.rows.map((r) => (getRowId(r) === id ? result : r));
97
+ return result;
98
+ }
99
+ catch (err) {
100
+ state.rows = prevRows;
101
+ throw err;
102
+ }
103
+ finally {
104
+ state.saving = false;
105
+ emit();
106
+ }
107
+ }
108
+ // Optimistic delete: drop the local row + decrement total, restore on error.
109
+ async function optimisticDelete(id, fn) {
110
+ if (disposed)
111
+ throw new Error('createServerDataSource: controller is disposed');
112
+ const prevRows = state.rows;
113
+ const prevTotal = state.total;
114
+ const next = prevRows.filter((r) => getRowId(r) !== id);
115
+ if (next.length === prevRows.length)
116
+ return mutate('deleteRow', () => fn(id));
117
+ state.rows = next;
118
+ state.total = Math.max(0, prevTotal - (prevRows.length - next.length));
119
+ state.saving = true;
120
+ emit();
121
+ try {
122
+ await fn(id);
123
+ }
124
+ catch (err) {
125
+ state.rows = prevRows;
126
+ state.total = prevTotal;
127
+ throw err;
128
+ }
129
+ finally {
130
+ state.saving = false;
131
+ emit();
132
+ }
133
+ }
54
134
  return {
55
135
  refresh: fetchPage,
136
+ createRow: (input) => mutate('createRow', source.createRow ? () => source.createRow(input) : null),
137
+ updateRow: (id, patch) => {
138
+ if (!source.updateRow)
139
+ return mutate('updateRow', null);
140
+ const fn = source.updateRow;
141
+ return optimistic ? optimisticUpdate(id, patch, fn) : mutate('updateRow', () => fn(id, patch));
142
+ },
143
+ deleteRow: (id) => {
144
+ if (!source.deleteRow)
145
+ return mutate('deleteRow', null);
146
+ const fn = source.deleteRow;
147
+ return optimistic ? optimisticDelete(id, fn) : mutate('deleteRow', () => fn(id));
148
+ },
56
149
  setSort(sortModel) {
57
150
  state.sortModel = sortModel;
58
151
  state.pageIndex = 0;
@@ -78,6 +171,13 @@ export function createServerDataSource(source, options) {
78
171
  getState: () => ({ ...state }),
79
172
  dispose() {
80
173
  disposed = true;
174
+ // An in-flight fetch's resolution short-circuits on `disposed`, so it
175
+ // never clears `loading`. Clear it here (and emit) so a disposed
176
+ // controller doesn't report a permanent loading state.
177
+ if (state.loading) {
178
+ state.loading = false;
179
+ emit();
180
+ }
81
181
  },
82
182
  };
83
183
  }
package/dist/sparkline.js CHANGED
@@ -72,12 +72,16 @@ export function buildSparkline(values, cfg = {}) {
72
72
  const n = values.length;
73
73
  const stepX = n > 1 ? innerW / (n - 1) : 0;
74
74
  const pts = values.map((v, i) => ({
75
- x: pad + i * stepX,
75
+ // A single point has no span to walk, so centre it rather than pinning it
76
+ // to the left edge.
77
+ x: n === 1 ? pad + innerW / 2 : pad + i * stepX,
76
78
  y: pad + innerH - ((v - min) / span) * innerH,
77
79
  }));
78
- const linePath = pts
79
- .map((p, i) => `${i === 0 ? 'M' : 'L'}${round(p.x)},${round(p.y)}`)
80
- .join(' ');
80
+ const linePath = n === 1
81
+ ? // One point produces only a moveto, which draws nothing. Emit a tiny
82
+ // horizontal segment so a single value still renders a visible mark.
83
+ `M${round(pts[0].x - 1)},${round(pts[0].y)} L${round(pts[0].x + 1)},${round(pts[0].y)}`
84
+ : pts.map((p, i) => `${i === 0 ? 'M' : 'L'}${round(p.x)},${round(p.y)}`).join(' ');
81
85
  const first = pts[0];
82
86
  const last = pts[pts.length - 1];
83
87
  const baseY = pad + innerH;
@@ -1,4 +1,5 @@
1
- import type { ColumnDef, RowData, SvGridOptions, TableFeatures } from './core';
1
+ import type { CellFormatConfig, ColumnDef, RowData, SvGridOptions, TableFeatures } from './core';
2
+ import type { GridExportOptions, GridClipboardOptions } from './export-format';
2
3
  export type SvGridFilterOperator = 'contains' | 'equals' | 'startsWith' | 'greaterThan' | 'lessThan' | 'between' | 'isBlank';
3
4
  /**
4
5
  * A serializable snapshot of everything that makes up the current "view":
@@ -168,7 +169,31 @@ export type SvGridApi<TFeatures extends TableFeatures, TData extends RowData> =
168
169
  field?: string;
169
170
  header: string;
170
171
  visible: boolean;
172
+ /** The column's `format` config, when set. Lets an exporter reproduce
173
+ * the on-screen display value (currency, date pattern, etc.). */
174
+ format?: CellFormatConfig;
175
+ /** Effective horizontal alignment ('left' | 'center' | 'right'). */
176
+ align?: 'left' | 'center' | 'right';
171
177
  }>;
178
+ /**
179
+ * Export the grid to a **CSV** file. Free in the community grid; the
180
+ * richer Excel / PDF / styled formats live in @svgrid/enterprise. Values
181
+ * are formatted as shown on screen (pass `rawValues: true` for raw). Rows
182
+ * default to the current view (`rows: 'selected' | 'all'` to change).
183
+ * Resolves with the serialized text; pass `download: false` to skip the
184
+ * browser download and just get the string.
185
+ */
186
+ exportCsv(options?: GridExportOptions): Promise<string>;
187
+ /** Export the grid to a **TSV** file (tab-separated). See `exportCsv`. */
188
+ exportTsv(options?: GridExportOptions): Promise<string>;
189
+ /** Export the grid to a **JSON** file (array of `{ field: value }`). */
190
+ exportJson(options?: GridExportOptions): Promise<string>;
191
+ /**
192
+ * Copy the grid to the system clipboard. `format: 'tsv'` (default) pastes
193
+ * straight into Excel / Sheets; `'csv'` / `'markdown'` also supported.
194
+ * Resolves with the copied text.
195
+ */
196
+ copyToClipboard(options?: GridClipboardOptions): Promise<string>;
172
197
  /** Clear every checked row. Emits `onRowSelectionChange({}, [])`. */
173
198
  clearRowSelection(): void;
174
199
  /**