@svgrid/grid 1.2.22 → 1.2.24

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 (263) hide show
  1. package/dist/GridFooter.svelte +34 -20
  2. package/dist/GridFooter.svelte.d.ts +8 -0
  3. package/dist/SvAutoComplete.svelte +89 -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 +409 -0
  8. package/dist/SvCalendar.svelte.d.ts +52 -0
  9. package/dist/SvCheckBox.svelte +82 -0
  10. package/dist/SvCheckBox.svelte.d.ts +19 -0
  11. package/dist/SvColorInput.svelte +160 -0
  12. package/dist/SvColorInput.svelte.d.ts +15 -0
  13. package/dist/SvComboBox.svelte +108 -0
  14. package/dist/SvComboBox.svelte.d.ts +15 -0
  15. package/dist/SvCountryInput.svelte +125 -0
  16. package/dist/SvCountryInput.svelte.d.ts +14 -0
  17. package/dist/SvDateTimePicker.svelte +265 -0
  18. package/dist/SvDateTimePicker.svelte.d.ts +38 -0
  19. package/dist/SvDropDownList.svelte +120 -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 +97 -0
  24. package/dist/SvGauge.svelte.d.ts +26 -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 +89 -0
  35. package/dist/SvListBox.svelte.d.ts +22 -0
  36. package/dist/SvMaskedInput.svelte +66 -0
  37. package/dist/SvMaskedInput.svelte.d.ts +15 -0
  38. package/dist/SvNumberInput.svelte +116 -0
  39. package/dist/SvNumberInput.svelte.d.ts +18 -0
  40. package/dist/SvPasswordInput.svelte +100 -0
  41. package/dist/SvPasswordInput.svelte.d.ts +18 -0
  42. package/dist/SvPhoneInput.svelte +87 -0
  43. package/dist/SvPhoneInput.svelte.d.ts +19 -0
  44. package/dist/SvRadioGroup.svelte +85 -0
  45. package/dist/SvRadioGroup.svelte.d.ts +20 -0
  46. package/dist/SvRating.svelte +91 -0
  47. package/dist/SvRating.svelte.d.ts +18 -0
  48. package/dist/SvRepeatButton.svelte +89 -0
  49. package/dist/SvRepeatButton.svelte.d.ts +21 -0
  50. package/dist/SvSlider.svelte +155 -0
  51. package/dist/SvSlider.svelte.d.ts +20 -0
  52. package/dist/SvSwitchButton.svelte +87 -0
  53. package/dist/SvSwitchButton.svelte.d.ts +14 -0
  54. package/dist/SvTabs.svelte +109 -0
  55. package/dist/SvTabs.svelte.d.ts +26 -0
  56. package/dist/SvTagsInput.svelte +70 -0
  57. package/dist/SvTagsInput.svelte.d.ts +14 -0
  58. package/dist/SvTimePicker.svelte +201 -0
  59. package/dist/SvTimePicker.svelte.d.ts +30 -0
  60. package/dist/SvToggleButton.svelte +57 -0
  61. package/dist/SvToggleButton.svelte.d.ts +16 -0
  62. package/dist/SvTree.svelte +131 -0
  63. package/dist/SvTree.svelte.d.ts +27 -0
  64. package/dist/build-api.js +78 -1
  65. package/dist/cdn/svgrid.js +11674 -5907
  66. package/dist/cdn/svgrid.svelte-external.js +10436 -4671
  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/createAutocomplete.svelte.d.ts +77 -0
  76. package/dist/createAutocomplete.svelte.js +100 -0
  77. package/dist/createCalendar.svelte.d.ts +192 -0
  78. package/dist/createCalendar.svelte.js +402 -0
  79. package/dist/createCheckbox.svelte.d.ts +45 -0
  80. package/dist/createCheckbox.svelte.js +26 -0
  81. package/dist/createColorInput.svelte.d.ts +67 -0
  82. package/dist/createColorInput.svelte.js +100 -0
  83. package/dist/createCombobox.svelte.d.ts +102 -0
  84. package/dist/createCombobox.svelte.js +171 -0
  85. package/dist/createCountryInput.svelte.d.ts +86 -0
  86. package/dist/createCountryInput.svelte.js +126 -0
  87. package/dist/createDateTimePicker.svelte.d.ts +83 -0
  88. package/dist/createDateTimePicker.svelte.js +202 -0
  89. package/dist/createDropdownList.svelte.d.ts +78 -0
  90. package/dist/createDropdownList.svelte.js +117 -0
  91. package/dist/createGauge.svelte.d.ts +56 -0
  92. package/dist/createGauge.svelte.js +49 -0
  93. package/dist/createListbox.svelte.d.ts +79 -0
  94. package/dist/createListbox.svelte.js +117 -0
  95. package/dist/createMaskedInput.svelte.d.ts +30 -0
  96. package/dist/createMaskedInput.svelte.js +62 -0
  97. package/dist/createNumberInput.svelte.d.ts +72 -0
  98. package/dist/createNumberInput.svelte.js +167 -0
  99. package/dist/createPasswordInput.svelte.d.ts +60 -0
  100. package/dist/createPasswordInput.svelte.js +72 -0
  101. package/dist/createPhoneInput.svelte.d.ts +46 -0
  102. package/dist/createPhoneInput.svelte.js +105 -0
  103. package/dist/createRadioGroup.svelte.d.ts +64 -0
  104. package/dist/createRadioGroup.svelte.js +73 -0
  105. package/dist/createRating.svelte.d.ts +70 -0
  106. package/dist/createRating.svelte.js +85 -0
  107. package/dist/createSlider.svelte.d.ts +71 -0
  108. package/dist/createSlider.svelte.js +123 -0
  109. package/dist/createSwitch.svelte.d.ts +43 -0
  110. package/dist/createSwitch.svelte.js +41 -0
  111. package/dist/createTabs.svelte.d.ts +78 -0
  112. package/dist/createTabs.svelte.js +92 -0
  113. package/dist/createTagsInput.svelte.d.ts +65 -0
  114. package/dist/createTagsInput.svelte.js +93 -0
  115. package/dist/createTimePicker.svelte.d.ts +106 -0
  116. package/dist/createTimePicker.svelte.js +248 -0
  117. package/dist/createToggle.svelte.d.ts +43 -0
  118. package/dist/createToggle.svelte.js +39 -0
  119. package/dist/createTree.svelte.d.ts +96 -0
  120. package/dist/createTree.svelte.js +194 -0
  121. package/dist/datetime/date-core.d.ts +88 -0
  122. package/dist/datetime/date-core.js +165 -0
  123. package/dist/datetime/date-format.d.ts +39 -0
  124. package/dist/datetime/date-format.js +371 -0
  125. package/dist/datetime/date-restrict.d.ts +23 -0
  126. package/dist/datetime/date-restrict.js +50 -0
  127. package/dist/datetime/date-selection.d.ts +25 -0
  128. package/dist/datetime/date-selection.js +94 -0
  129. package/dist/datetime/mask.d.ts +21 -0
  130. package/dist/datetime/mask.js +90 -0
  131. package/dist/editing.d.ts +1 -1
  132. package/dist/editing.js +7 -0
  133. package/dist/editor-contract.d.ts +48 -0
  134. package/dist/editor-contract.js +26 -0
  135. package/dist/editors/cell-editors.d.ts +2 -2
  136. package/dist/editors/cell-editors.js +3 -1
  137. package/dist/export-format.d.ts +178 -0
  138. package/dist/export-format.js +419 -0
  139. package/dist/hyperformula-adapter.d.ts +4 -0
  140. package/dist/hyperformula-adapter.js +15 -6
  141. package/dist/index.d.ts +60 -0
  142. package/dist/index.js +72 -0
  143. package/dist/list-option.d.ts +10 -0
  144. package/dist/list-option.js +7 -0
  145. package/dist/menus.js +18 -2
  146. package/dist/popover.d.ts +48 -0
  147. package/dist/popover.js +65 -0
  148. package/dist/row-resize.d.ts +4 -4
  149. package/dist/row-resize.js +8 -5
  150. package/dist/selection.d.ts +6 -0
  151. package/dist/selection.js +31 -5
  152. package/dist/server-data-source.d.ts +43 -0
  153. package/dist/server-data-source.js +100 -0
  154. package/dist/sparkline.js +8 -4
  155. package/dist/svgrid-wrapper.types.d.ts +26 -1
  156. package/package.json +6 -1
  157. package/src/GridFooter.svelte +34 -20
  158. package/src/SvAutoComplete.svelte +89 -0
  159. package/src/SvButton.svelte +97 -0
  160. package/src/SvCalendar.svelte +409 -0
  161. package/src/SvCalendar.test.ts +211 -0
  162. package/src/SvCheckBox.svelte +82 -0
  163. package/src/SvColorInput.svelte +160 -0
  164. package/src/SvComboBox.svelte +108 -0
  165. package/src/SvCountryInput.svelte +125 -0
  166. package/src/SvDateTimePicker.svelte +265 -0
  167. package/src/SvDateTimePicker.test.ts +136 -0
  168. package/src/SvDropDownList.svelte +120 -0
  169. package/src/SvForm.svelte +147 -0
  170. package/src/SvGauge.svelte +97 -0
  171. package/src/SvGrid.controller.svelte.ts +74 -8
  172. package/src/SvGrid.css +84 -31
  173. package/src/SvGrid.helpers.ts +41 -12
  174. package/src/SvGrid.svelte +84 -5
  175. package/src/SvGrid.types.ts +33 -0
  176. package/src/SvGridDropdown.svelte +16 -30
  177. package/src/SvListBox.svelte +89 -0
  178. package/src/SvMaskedInput.svelte +66 -0
  179. package/src/SvNumberInput.svelte +116 -0
  180. package/src/SvPasswordInput.svelte +100 -0
  181. package/src/SvPhoneInput.svelte +87 -0
  182. package/src/SvRadioGroup.svelte +85 -0
  183. package/src/SvRating.svelte +91 -0
  184. package/src/SvRepeatButton.svelte +89 -0
  185. package/src/SvSlider.svelte +155 -0
  186. package/src/SvSwitchButton.svelte +87 -0
  187. package/src/SvTabs.svelte +109 -0
  188. package/src/SvTagsInput.svelte +70 -0
  189. package/src/SvTimePicker.svelte +201 -0
  190. package/src/SvTimePicker.test.ts +98 -0
  191. package/src/SvToggleButton.svelte +57 -0
  192. package/src/SvTree.svelte +131 -0
  193. package/src/build-api.ts +115 -0
  194. package/src/cell-render.test.ts +38 -0
  195. package/src/cell-render.ts +43 -0
  196. package/src/clipboard.test.ts +18 -0
  197. package/src/clipboard.ts +24 -1
  198. package/src/collaboration.test.ts +30 -0
  199. package/src/collaboration.ts +6 -0
  200. package/src/core.ts +27 -3
  201. package/src/countries.ts +71 -0
  202. package/src/createAutocomplete.svelte.ts +104 -0
  203. package/src/createCalendar.svelte.ts +488 -0
  204. package/src/createCheckbox.svelte.ts +74 -0
  205. package/src/createColorInput.svelte.ts +115 -0
  206. package/src/createCombobox.svelte.ts +158 -0
  207. package/src/createCountryInput.svelte.ts +126 -0
  208. package/src/createDateTimePicker.svelte.ts +216 -0
  209. package/src/createDropdownList.svelte.ts +125 -0
  210. package/src/createGauge.svelte.ts +86 -0
  211. package/src/createListbox.svelte.ts +186 -0
  212. package/src/createMaskedInput.svelte.ts +81 -0
  213. package/src/createNumberInput.svelte.ts +193 -0
  214. package/src/createPasswordInput.svelte.ts +87 -0
  215. package/src/createPhoneInput.svelte.ts +119 -0
  216. package/src/createRadioGroup.svelte.ts +132 -0
  217. package/src/createRating.svelte.ts +145 -0
  218. package/src/createSlider.svelte.ts +160 -0
  219. package/src/createSwitch.svelte.ts +78 -0
  220. package/src/createTabs.svelte.ts +129 -0
  221. package/src/createTagsInput.svelte.ts +98 -0
  222. package/src/createTimePicker.svelte.ts +289 -0
  223. package/src/createToggle.svelte.ts +69 -0
  224. package/src/createTree.svelte.ts +216 -0
  225. package/src/datetime/date-core.test.ts +217 -0
  226. package/src/datetime/date-core.ts +204 -0
  227. package/src/datetime/date-format.test.ts +121 -0
  228. package/src/datetime/date-format.ts +317 -0
  229. package/src/datetime/date-restrict.ts +60 -0
  230. package/src/datetime/date-selection.test.ts +129 -0
  231. package/src/datetime/date-selection.ts +137 -0
  232. package/src/datetime/mask.test.ts +36 -0
  233. package/src/datetime/mask.ts +84 -0
  234. package/src/editing.test.ts +22 -0
  235. package/src/editing.ts +7 -1
  236. package/src/editor-contract.test.ts +32 -0
  237. package/src/editor-contract.ts +60 -0
  238. package/src/editors/cell-editors.ts +7 -1
  239. package/src/export-data-api.test.ts +126 -0
  240. package/src/export-format.test.ts +107 -0
  241. package/src/export-format.ts +598 -0
  242. package/src/hyperformula-adapter.test.ts +35 -1
  243. package/src/hyperformula-adapter.ts +18 -6
  244. package/src/index.ts +110 -0
  245. package/src/list-option.ts +15 -0
  246. package/src/menus.ts +21 -2
  247. package/src/popover.ts +79 -0
  248. package/src/row-resize.test.ts +25 -0
  249. package/src/row-resize.ts +8 -5
  250. package/src/selection.test.ts +36 -4
  251. package/src/selection.ts +30 -9
  252. package/src/server-data-source.test.ts +201 -0
  253. package/src/server-data-source.ts +148 -0
  254. package/src/sparkline.test.ts +9 -0
  255. package/src/sparkline.ts +9 -4
  256. package/src/svgrid-wrapper.types.ts +28 -1
  257. package/src/svgrid.behavior.test.ts +27 -0
  258. package/src/svgrid.new-features.wrapper.test.ts +27 -1
  259. package/src/ui-buttons.test.ts +105 -0
  260. package/src/ui-composite.test.ts +89 -0
  261. package/src/ui-inputs.test.ts +118 -0
  262. package/src/ui-range.test.ts +61 -0
  263. package/src/ui-selection.test.ts +119 -0
@@ -6,6 +6,10 @@ export declare function createCellRender<TFeatures extends TableFeatures = Table
6
6
  computeRowClass: (row: Row<TData>, rowIndex: number) => string;
7
7
  computeCellClass: (row: Row<TData>, column: Column<TData>) => string;
8
8
  computeCellTooltip: (row: Row<TData>, column: Column<TData>) => string | null;
9
+ computeCellValidity: (row: Row<TData>, column: Column<TData>) => {
10
+ invalid: boolean;
11
+ message: string | null;
12
+ };
9
13
  computeCellNote: (row: Row<TData>, column: Column<TData>) => string | null;
10
14
  getColumnEditorOptions: (column: Column<TData>, row?: Row<TData> | null) => CellEditorOption[];
11
15
  formatListCellValue: (column: Column<TData>, value: unknown, row?: Row<TData> | null) => string;
@@ -5,7 +5,7 @@ import { normalizeEditorOptions, } from "./index";
5
5
  import "./sv-grid-scrollbar";
6
6
  import { formatNumericWithConfig, getDateFormatter, resolveDatePattern, } from "./cell-formatting";
7
7
  import { resolveCellFormat, } from "./conditional-formatting";
8
- import { resolveClassList, getOptionLabel, asDate, } from "./SvGrid.helpers";
8
+ import { getCellKey, resolveClassList, getOptionLabel, asDate, } from "./SvGrid.helpers";
9
9
  import { getPinnedCellValue, } from "./cell-values";
10
10
  export function createCellRender(ctx) {
11
11
  function cellConditionalFormat(row, column, value) {
@@ -69,6 +69,39 @@ export function createCellRender(ctx) {
69
69
  }
70
70
  return null;
71
71
  }
72
+ /**
73
+ * Resolve per-cell validity via the column's declarative `validate` hook.
74
+ * Runs for every rendered cell so pre-existing bad data is flagged on load.
75
+ * Returns `{ invalid, message }` - `invalid` drives the red highlight class,
76
+ * `message` (when present) becomes the cell's tooltip.
77
+ */
78
+ function computeCellValidity(row, column) {
79
+ const rule = column.columnDef.validate;
80
+ if (typeof rule !== "function")
81
+ return { invalid: false, message: null };
82
+ // Read the edit-aware value: inline edits land in `editedCellValues`
83
+ // (keyed rowId:colId) and are NOT written into the row's memoized value
84
+ // cache, so `getCellValueByColumnId` would return the stale pre-edit value
85
+ // and validation would never re-run after an edit. Consult the overlay
86
+ // first - same rule the display path (getCellDisplayValue) and summaries
87
+ // use - so a cell re-validates live as the user types.
88
+ const edited = ctx.editedCellValues ?? {};
89
+ const key = getCellKey(row.id, column.id);
90
+ const value = key in edited ? edited[key] : row.getCellValueByColumnId(column.id);
91
+ const out = rule({
92
+ value,
93
+ row: row.original,
94
+ rowIndex: row.index,
95
+ column,
96
+ });
97
+ // Valid: null / undefined / true. Invalid: false or a message string.
98
+ if (out == null || out === true)
99
+ return { invalid: false, message: null };
100
+ if (out === false)
101
+ return { invalid: true, message: null };
102
+ const msg = String(out);
103
+ return { invalid: true, message: msg.trim() ? msg : null };
104
+ }
72
105
  /**
73
106
  * Resolve a per-cell note (a longer comment / annotation). Notes
74
107
  * come from the grid's `notes` prop - a `{ [rowId]: { [columnId]: string } }`
@@ -236,6 +269,7 @@ export function createCellRender(ctx) {
236
269
  computeRowClass,
237
270
  computeCellClass,
238
271
  computeCellTooltip,
272
+ computeCellValidity,
239
273
  computeCellNote,
240
274
  getColumnEditorOptions,
241
275
  formatListCellValue,
package/dist/clipboard.js CHANGED
@@ -355,6 +355,7 @@ export function createClipboard(ctx) {
355
355
  const endCol = Math.max(anchor.colIndex, focus.colIndex);
356
356
  const next = ctx.internalData.slice();
357
357
  let mutated = false;
358
+ const changes = [];
358
359
  for (let r = startRow; r <= endRow; r += 1) {
359
360
  const row = ctx.allRows[r];
360
361
  if (!row || isGroupRow(row))
@@ -381,8 +382,18 @@ export function createClipboard(ctx) {
381
382
  // else. parseEditorValue handles the per-type coercion.
382
383
  const editorType = (column.columnDef.editorType ??
383
384
  "text");
384
- updated[column.columnDef.field] = parseEditorValue(editorType, "");
385
+ const field = column.columnDef.field;
386
+ const oldValue = updated[field];
387
+ const cleared = parseEditorValue(editorType, "");
388
+ updated[field] = cleared;
385
389
  rowChanged = true;
390
+ changes.push({
391
+ rowIndex: dataIndex,
392
+ columnId: column.id,
393
+ oldValue,
394
+ newValue: cleared,
395
+ row: updated,
396
+ });
386
397
  }
387
398
  if (rowChanged) {
388
399
  next[dataIndex] = updated;
@@ -392,6 +403,13 @@ export function createClipboard(ctx) {
392
403
  if (mutated) {
393
404
  ctx.internalData = next;
394
405
  ctx.grid.store.setState((prev) => ({ ...prev }));
406
+ // Fire onCellValueChange per cleared cell - same as paste's writeCellRaw -
407
+ // so consumers (formula engines, autosave) recompute. Clear IS a value
408
+ // change; without this, a HyperFormula-backed grid wouldn't re-evaluate.
409
+ if (ctx.props.onCellValueChange) {
410
+ for (const ch of changes)
411
+ ctx.props.onCellValueChange(ch);
412
+ }
395
413
  }
396
414
  return mutated;
397
415
  }
@@ -43,6 +43,8 @@ export type CollabMessage = {
43
43
  export type CollabTransport = {
44
44
  post(msg: CollabMessage): void;
45
45
  subscribe(handler: (msg: CollabMessage) => void): () => void;
46
+ /** Release any underlying resource (e.g. a BroadcastChannel). Optional. */
47
+ dispose?(): void;
46
48
  };
47
49
  export type Collaboration = {
48
50
  /** Broadcast where this user's cursor is (or null when it leaves). */
@@ -14,6 +14,9 @@ export function broadcastChannelTransport(name) {
14
14
  channel.addEventListener('message', listener);
15
15
  return () => channel.removeEventListener('message', listener);
16
16
  },
17
+ dispose() {
18
+ channel?.close();
19
+ },
17
20
  };
18
21
  }
19
22
  export function createCollaboration(options) {
@@ -93,6 +96,7 @@ export function createCollaboration(options) {
93
96
  unsubscribe();
94
97
  if (pruneTimer)
95
98
  clearInterval(pruneTimer);
99
+ transport.dispose?.(); // release the channel (BroadcastChannel etc.)
96
100
  },
97
101
  };
98
102
  }
package/dist/core.d.ts CHANGED
@@ -173,7 +173,7 @@ export type ColumnDef<TFeatures extends TableFeatures, TData extends RowData> =
173
173
  * and `columnGroupShow: 'closed'` children show until the user expands it.
174
174
  */
175
175
  openByDefault?: boolean;
176
- editorType?: 'text' | 'number' | 'date' | 'datetime' | 'time' | 'password' | 'checkbox' | 'list' | 'chips' | 'select' | 'rich-select' | 'autocomplete' | 'textarea' | 'color' | 'rating';
176
+ editorType?: 'text' | 'number' | 'date' | 'datetime' | 'time' | 'date-native' | 'datetime-native' | 'time-native' | 'password' | 'checkbox' | 'list' | 'chips' | 'select' | 'rich-select' | 'autocomplete' | 'textarea' | 'color' | 'rating';
177
177
  /**
178
178
  * Custom in-cell editor. Receives the cell context PLUS a `commit(value)`
179
179
  * and `cancel()` helper. Use when none of the built-in `editorType`s fit;
@@ -191,6 +191,27 @@ export type ColumnDef<TFeatures extends TableFeatures, TData extends RowData> =
191
191
  * string skips the tooltip.
192
192
  */
193
193
  tooltip?: string | ((ctx: CellContext<TData>) => string | null | undefined);
194
+ /**
195
+ * Declarative per-cell validation (Handsontable-style). Runs for EVERY
196
+ * rendered cell - including values already present in `data` on load, not
197
+ * just on edit - so bad data is flagged immediately. Invalid cells get the
198
+ * `sv-grid-cell-invalid` class (red highlight) and the returned message as
199
+ * their tooltip.
200
+ *
201
+ * Return value:
202
+ * - `null` / `undefined` / `true` → valid (no highlight)
203
+ * - `false` → invalid, no message
204
+ * - a non-empty `string` → invalid, string is the tooltip
205
+ *
206
+ * The value keeps rendering as-is (the grid does NOT roll it back); pair
207
+ * with `onCellValueChange` if you also want to reject the commit.
208
+ */
209
+ validate?: (params: {
210
+ value: unknown;
211
+ row: TData;
212
+ rowIndex: number;
213
+ column: Column<TData>;
214
+ }) => string | boolean | null | undefined;
194
215
  /**
195
216
  * Gate editing per column or per cell.
196
217
  *
@@ -0,0 +1,15 @@
1
+ /**
2
+ * countries - a compact country reference for SvPhoneInput / SvCountryInput.
3
+ * ISO 3166-1 alpha-2 code, display name, E.164 dial code, and a flag emoji
4
+ * (derived from the code so no image assets are needed). This is a curated
5
+ * common-set; extend `COUNTRIES` for full coverage.
6
+ */
7
+ export type Country = {
8
+ code: string;
9
+ name: string;
10
+ dial: string;
11
+ };
12
+ /** Regional-indicator flag emoji from an ISO alpha-2 code. */
13
+ export declare function flagEmoji(code: string): string;
14
+ export declare const COUNTRIES: Country[];
15
+ export declare const COUNTRY_BY_CODE: Map<string, Country>;
@@ -0,0 +1,61 @@
1
+ /** Regional-indicator flag emoji from an ISO alpha-2 code. */
2
+ export function flagEmoji(code) {
3
+ return code
4
+ .toUpperCase()
5
+ .replace(/./g, (c) => String.fromCodePoint(127397 + c.charCodeAt(0)));
6
+ }
7
+ export const COUNTRIES = [
8
+ { code: 'US', name: 'United States', dial: '+1' },
9
+ { code: 'CA', name: 'Canada', dial: '+1' },
10
+ { code: 'GB', name: 'United Kingdom', dial: '+44' },
11
+ { code: 'IE', name: 'Ireland', dial: '+353' },
12
+ { code: 'FR', name: 'France', dial: '+33' },
13
+ { code: 'DE', name: 'Germany', dial: '+49' },
14
+ { code: 'ES', name: 'Spain', dial: '+34' },
15
+ { code: 'PT', name: 'Portugal', dial: '+351' },
16
+ { code: 'IT', name: 'Italy', dial: '+39' },
17
+ { code: 'NL', name: 'Netherlands', dial: '+31' },
18
+ { code: 'BE', name: 'Belgium', dial: '+32' },
19
+ { code: 'CH', name: 'Switzerland', dial: '+41' },
20
+ { code: 'AT', name: 'Austria', dial: '+43' },
21
+ { code: 'SE', name: 'Sweden', dial: '+46' },
22
+ { code: 'NO', name: 'Norway', dial: '+47' },
23
+ { code: 'DK', name: 'Denmark', dial: '+45' },
24
+ { code: 'FI', name: 'Finland', dial: '+358' },
25
+ { code: 'PL', name: 'Poland', dial: '+48' },
26
+ { code: 'CZ', name: 'Czechia', dial: '+420' },
27
+ { code: 'GR', name: 'Greece', dial: '+30' },
28
+ { code: 'BG', name: 'Bulgaria', dial: '+359' },
29
+ { code: 'RO', name: 'Romania', dial: '+40' },
30
+ { code: 'UA', name: 'Ukraine', dial: '+380' },
31
+ { code: 'RU', name: 'Russia', dial: '+7' },
32
+ { code: 'TR', name: 'Turkey', dial: '+90' },
33
+ { code: 'IL', name: 'Israel', dial: '+972' },
34
+ { code: 'AE', name: 'United Arab Emirates', dial: '+971' },
35
+ { code: 'SA', name: 'Saudi Arabia', dial: '+966' },
36
+ { code: 'IN', name: 'India', dial: '+91' },
37
+ { code: 'PK', name: 'Pakistan', dial: '+92' },
38
+ { code: 'CN', name: 'China', dial: '+86' },
39
+ { code: 'HK', name: 'Hong Kong', dial: '+852' },
40
+ { code: 'JP', name: 'Japan', dial: '+81' },
41
+ { code: 'KR', name: 'South Korea', dial: '+82' },
42
+ { code: 'SG', name: 'Singapore', dial: '+65' },
43
+ { code: 'MY', name: 'Malaysia', dial: '+60' },
44
+ { code: 'TH', name: 'Thailand', dial: '+66' },
45
+ { code: 'ID', name: 'Indonesia', dial: '+62' },
46
+ { code: 'PH', name: 'Philippines', dial: '+63' },
47
+ { code: 'VN', name: 'Vietnam', dial: '+84' },
48
+ { code: 'AU', name: 'Australia', dial: '+61' },
49
+ { code: 'NZ', name: 'New Zealand', dial: '+64' },
50
+ { code: 'ZA', name: 'South Africa', dial: '+27' },
51
+ { code: 'NG', name: 'Nigeria', dial: '+234' },
52
+ { code: 'EG', name: 'Egypt', dial: '+20' },
53
+ { code: 'KE', name: 'Kenya', dial: '+254' },
54
+ { code: 'MA', name: 'Morocco', dial: '+212' },
55
+ { code: 'BR', name: 'Brazil', dial: '+55' },
56
+ { code: 'AR', name: 'Argentina', dial: '+54' },
57
+ { code: 'CL', name: 'Chile', dial: '+56' },
58
+ { code: 'CO', name: 'Colombia', dial: '+57' },
59
+ { code: 'MX', name: 'Mexico', dial: '+52' },
60
+ ];
61
+ export const COUNTRY_BY_CODE = new Map(COUNTRIES.map((c) => [c.code, c]));
@@ -0,0 +1,77 @@
1
+ /**
2
+ * createAutocomplete - the HEADLESS core behind <SvAutoComplete>: a free-text
3
+ * field with a live-filtered suggestion list (accepts ANY typed value; the
4
+ * suggestions are shortcuts). Roving active index + keyboard, exposed as
5
+ * **prop-getters** you spread onto YOUR OWN markup. No styles/DOM/portal - those
6
+ * stay in the styled component.
7
+ *
8
+ * ```svelte
9
+ * <script lang="ts">
10
+ * import { createAutocomplete } from '@svgrid/grid'
11
+ * let value = $state('')
12
+ * const ac = createAutocomplete({ value: () => value, suggestions: () => list, onChange: (v) => (value = v) })
13
+ * </script>
14
+ * <input {...ac.inputProps()} />
15
+ * {#if ac.open}
16
+ * <ul {...ac.listboxProps()}>
17
+ * {#each ac.filtered as opt, i (opt.value)}<li {...ac.optionProps(i)}>{opt.label}</li>{/each}
18
+ * </ul>
19
+ * {/if}
20
+ * ```
21
+ */
22
+ import { type ListOption } from './list-option';
23
+ /** Reactive inputs are passed as getters so the core tracks live prop changes. */
24
+ export type AutocompleteConfig = {
25
+ value: () => string;
26
+ onChange?: (value: string) => void;
27
+ /** Suggestions - strings or {value,label} (label shown, value inserted). */
28
+ suggestions: () => ReadonlyArray<string | ListOption>;
29
+ minChars?: () => number;
30
+ disabled?: () => boolean;
31
+ ariaLabel?: () => string | undefined;
32
+ /** DOM focus hook provided by the renderer; the core never touches the DOM. */
33
+ focusInput?: () => void;
34
+ };
35
+ export declare function createAutocomplete(config: AutocompleteConfig): {
36
+ /** Panel open state. */
37
+ readonly open: boolean;
38
+ /** Highlighted suggestion index into `filtered`. */
39
+ readonly activeIndex: number;
40
+ /** Suggestions after the live substring filter (capped at 10). */
41
+ readonly filtered: ListOption[];
42
+ /** Controlled text value. */
43
+ readonly value: string;
44
+ isActive: (i: number) => boolean;
45
+ setActive: (i: number) => void;
46
+ maybeOpen: () => void;
47
+ close: () => void;
48
+ pick: (o: ListOption | undefined) => void;
49
+ onInput: (e: Event) => void;
50
+ onKeydown: (e: KeyboardEvent) => void;
51
+ /** Spread onto the text <input>. */
52
+ inputProps: () => {
53
+ role: "combobox";
54
+ 'aria-expanded': boolean;
55
+ 'aria-autocomplete': "list";
56
+ 'aria-label': string | undefined;
57
+ value: string;
58
+ disabled: boolean;
59
+ oninput: (e: Event) => void;
60
+ onfocus: () => void;
61
+ onkeydown: (e: KeyboardEvent) => void;
62
+ };
63
+ /** Spread onto the listbox container. */
64
+ listboxProps: () => {
65
+ role: "listbox";
66
+ };
67
+ /** Spread onto the suggestion at `index` (index into `filtered`). */
68
+ optionProps: (index: number) => {
69
+ role: "option";
70
+ tabindex: number;
71
+ 'aria-selected': boolean;
72
+ 'data-idx': number;
73
+ onclick: () => void;
74
+ onpointermove: () => void;
75
+ };
76
+ };
77
+ export type Autocomplete = ReturnType<typeof createAutocomplete>;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * createAutocomplete - the HEADLESS core behind <SvAutoComplete>: a free-text
3
+ * field with a live-filtered suggestion list (accepts ANY typed value; the
4
+ * suggestions are shortcuts). Roving active index + keyboard, exposed as
5
+ * **prop-getters** you spread onto YOUR OWN markup. No styles/DOM/portal - those
6
+ * stay in the styled component.
7
+ *
8
+ * ```svelte
9
+ * <script lang="ts">
10
+ * import { createAutocomplete } from '@svgrid/grid'
11
+ * let value = $state('')
12
+ * const ac = createAutocomplete({ value: () => value, suggestions: () => list, onChange: (v) => (value = v) })
13
+ * </script>
14
+ * <input {...ac.inputProps()} />
15
+ * {#if ac.open}
16
+ * <ul {...ac.listboxProps()}>
17
+ * {#each ac.filtered as opt, i (opt.value)}<li {...ac.optionProps(i)}>{opt.label}</li>{/each}
18
+ * </ul>
19
+ * {/if}
20
+ * ```
21
+ */
22
+ import { filterOptions } from './list-option';
23
+ export function createAutocomplete(config) {
24
+ const minChars = () => config.minChars?.() ?? 1;
25
+ let open = $state(false);
26
+ let active = $state(0);
27
+ const asOptions = $derived(config.suggestions().map((s) => (typeof s === 'string' ? { value: s, label: s } : s)));
28
+ const filtered = $derived(config.value().length >= minChars() ? filterOptions(asOptions, config.value()).slice(0, 10) : []);
29
+ function maybeOpen() { open = filtered.length > 0; if (open)
30
+ active = 0; }
31
+ function pick(o) { if (!o)
32
+ return; config.onChange?.(String(o.value)); open = false; config.focusInput?.(); }
33
+ function close() { open = false; }
34
+ function onInput(e) { config.onChange?.(e.currentTarget.value); queueMicrotask(maybeOpen); }
35
+ function onKeydown(e) {
36
+ if (!open) {
37
+ if (e.key === 'ArrowDown') {
38
+ queueMicrotask(maybeOpen);
39
+ }
40
+ return;
41
+ }
42
+ if (e.key === 'ArrowDown') {
43
+ e.preventDefault();
44
+ active = Math.min(active + 1, filtered.length - 1);
45
+ }
46
+ else if (e.key === 'ArrowUp') {
47
+ e.preventDefault();
48
+ active = Math.max(active - 1, 0);
49
+ }
50
+ else if (e.key === 'Enter') {
51
+ e.preventDefault();
52
+ pick(filtered[active]);
53
+ }
54
+ else if (e.key === 'Escape') {
55
+ close();
56
+ }
57
+ }
58
+ return {
59
+ /** Panel open state. */
60
+ get open() { return open; },
61
+ /** Highlighted suggestion index into `filtered`. */
62
+ get activeIndex() { return active; },
63
+ /** Suggestions after the live substring filter (capped at 10). */
64
+ get filtered() { return filtered; },
65
+ /** Controlled text value. */
66
+ get value() { return config.value(); },
67
+ isActive: (i) => i === active,
68
+ setActive: (i) => { active = i; },
69
+ maybeOpen,
70
+ close,
71
+ pick,
72
+ onInput,
73
+ onKeydown,
74
+ /** Spread onto the text <input>. */
75
+ inputProps: () => ({
76
+ role: 'combobox',
77
+ 'aria-expanded': open,
78
+ 'aria-autocomplete': 'list',
79
+ 'aria-label': config.ariaLabel?.(),
80
+ value: config.value(),
81
+ disabled: config.disabled?.() ?? false,
82
+ oninput: onInput,
83
+ onfocus: maybeOpen,
84
+ onkeydown: onKeydown,
85
+ }),
86
+ /** Spread onto the listbox container. */
87
+ listboxProps: () => ({
88
+ role: 'listbox',
89
+ }),
90
+ /** Spread onto the suggestion at `index` (index into `filtered`). */
91
+ optionProps: (index) => ({
92
+ role: 'option',
93
+ tabindex: -1,
94
+ 'aria-selected': index === active,
95
+ 'data-idx': index,
96
+ onclick: () => pick(filtered[index]),
97
+ onpointermove: () => { active = index; },
98
+ }),
99
+ };
100
+ }
@@ -0,0 +1,192 @@
1
+ /**
2
+ * createCalendar - the HEADLESS core behind <SvCalendar>, in the same spirit as
3
+ * `createListbox` / `createSvGrid`: a runes-based state machine (view page +
4
+ * drill level, roving focus, every Smart `selectionMode`, range hover preview,
5
+ * full keyboard) with **prop-getters** you spread onto YOUR OWN markup. No
6
+ * styles, no DOM assumptions. It reuses the framework-free date engine in
7
+ * ./datetime for all the actual date math / selection / restriction, so this
8
+ * file only owns the reactive component-level state + interaction wiring.
9
+ *
10
+ * ```svelte
11
+ * <script lang="ts">
12
+ * import { createCalendar } from '@svgrid/grid'
13
+ * let value = $state<Date | null>(new Date())
14
+ * const cal = createCalendar({ value: () => value, onChange: (d) => (value = d[0] ?? null) })
15
+ * </script>
16
+ * <div {...cal.gridProps()}>
17
+ * {#each cal.panels[0].matrix as week}
18
+ * {#each week as cell}
19
+ * <button {...cal.dayProps(cell, cal.panels[0].month)}>{cell.date.getDate()}</button>
20
+ * {/each}
21
+ * {/each}
22
+ * </div>
23
+ * ```
24
+ *
25
+ * The styled <SvCalendar> is one renderer over this core; it keeps the render-only
26
+ * concerns (WAAPI view-change animation, mouse-wheel navigation, DOM refs) itself.
27
+ */
28
+ import { type DateLike, type MonthMatrixCell } from './datetime/date-core';
29
+ import { type SelectionMode } from './datetime/date-selection';
30
+ import { type RestrictOptions } from './datetime/date-restrict';
31
+ export type DisplayMode = 'month' | 'year' | 'decade';
32
+ export type CalendarNameFormat = 'narrow' | 'short' | 'long';
33
+ /** Selected value(s). Single Date for one/zeroOrOne, array for multi modes. */
34
+ export type CalendarValue = Date | number | string | ReadonlyArray<Date | number | string> | null;
35
+ /** A one-click shortcut shown in the presets rail. `value` is a single date
36
+ * (or an [start, end] range for range mode); pass a function for "today"-
37
+ * relative shortcuts that resolve at click time. */
38
+ export type CalendarPreset = {
39
+ label: string;
40
+ value: Date | number | string | readonly [Date | number | string, Date | number | string] | (() => Date | number | string | readonly [Date | number | string, Date | number | string]);
41
+ };
42
+ /** How the calendar animates a navigation / drill (a render concern the styled
43
+ * component acts on; the core only reports the direction). */
44
+ export type CalendarAnimation = 'slide' | 'fade' | 'none';
45
+ /** Why the visible view last changed - drives the styled component's animation. */
46
+ export type CalendarNavDir = 'next' | 'prev' | 'drillDown' | 'drillUp' | 'fade';
47
+ /** Per-day interaction/visual state (booleans the renderer maps to classes). */
48
+ export type CalendarDayState = {
49
+ disabled: boolean;
50
+ selected: boolean;
51
+ important: boolean;
52
+ today: boolean;
53
+ outside: boolean;
54
+ focused: boolean;
55
+ preview: boolean;
56
+ };
57
+ /** Reactive inputs are passed as getters so the core tracks live prop changes;
58
+ * callbacks are plain closures. */
59
+ export type CalendarConfig = {
60
+ value: () => CalendarValue;
61
+ onChange?: (dates: Date[]) => void;
62
+ onNavigate?: (viewDate: Date, displayMode: DisplayMode) => void;
63
+ selectionMode?: () => SelectionMode;
64
+ min?: () => DateLike | null;
65
+ max?: () => DateLike | null;
66
+ restrictedDates?: () => RestrictOptions['restrictedDates'];
67
+ importantDates?: () => ReadonlyArray<DateLike> | ((d: Date) => boolean) | null;
68
+ firstDayOfWeek?: () => number;
69
+ weeks?: () => number;
70
+ months?: () => number;
71
+ displayMode?: () => DisplayMode;
72
+ disabled?: () => boolean;
73
+ readonly?: () => boolean;
74
+ locale?: () => string;
75
+ dayNameFormat?: () => CalendarNameFormat;
76
+ monthNameFormat?: () => CalendarNameFormat;
77
+ dateTooltip?: () => ((date: Date) => string | null | undefined) | undefined;
78
+ };
79
+ /** Normalize a calendar value to an ordered day-midnight list (pure). */
80
+ export declare function normalizeCalendarValue(v: CalendarValue): Date[];
81
+ export declare function createCalendar(config: CalendarConfig): {
82
+ readonly view: Date;
83
+ readonly mode: DisplayMode;
84
+ readonly title: string;
85
+ readonly panels: {
86
+ month: Date;
87
+ title: string;
88
+ matrix: MonthMatrixCell[][];
89
+ }[];
90
+ readonly weekdayHeaders: {
91
+ label: string;
92
+ weekday: number;
93
+ }[];
94
+ readonly yearCells: {
95
+ index: number;
96
+ label: string;
97
+ current: boolean;
98
+ selected: boolean;
99
+ }[];
100
+ readonly decadeCells: {
101
+ year: number;
102
+ outside: boolean;
103
+ current: boolean;
104
+ selected: boolean;
105
+ }[];
106
+ readonly selectedDates: Date[];
107
+ readonly previewRange: Date[] | null;
108
+ readonly focusDate: Date;
109
+ readonly hoverDate: Date | null;
110
+ readonly canClear: boolean;
111
+ readonly isInteractive: boolean;
112
+ /** Increments on every view/drill change; the styled component watches it to
113
+ * replay its WAAPI animation on the updated DOM. */
114
+ readonly navToken: number;
115
+ /** Why the view last changed (read untracked from the animation effect). */
116
+ readonly navDir: CalendarNavDir;
117
+ dayState: (d: Date, panelMonth: Date) => CalendarDayState;
118
+ inPreview: (d: Date) => boolean;
119
+ formatFullDate: (d: Date) => string;
120
+ formatMonthTitle: (d: Date) => string;
121
+ pickDay: (d: Date, e?: {
122
+ ctrlKey?: boolean;
123
+ metaKey?: boolean;
124
+ shiftKey?: boolean;
125
+ }) => void;
126
+ step: (dir: -1 | 1) => void;
127
+ drillUp: () => void;
128
+ pickMonth: (monthIndex: number) => void;
129
+ pickYear: (year: number) => void;
130
+ goToday: () => void;
131
+ applyPreset: (p: CalendarPreset) => void;
132
+ clearSelection: () => void;
133
+ setHover: (d: Date | null) => Date | null;
134
+ onKeydown: (e: KeyboardEvent) => void;
135
+ /** Previous / Next navigation button. */
136
+ navProps(dir: -1 | 1): {
137
+ type: "button";
138
+ disabled: boolean;
139
+ 'aria-label': string;
140
+ onclick: () => void;
141
+ };
142
+ /** The centered title button that drills up (month -> year -> decade). */
143
+ titleProps(): {
144
+ type: "button";
145
+ disabled: boolean;
146
+ 'aria-live': "polite";
147
+ onclick: () => void;
148
+ };
149
+ /** The grid container: keyboard + ARIA. Works for month, year and decade
150
+ * modes (the handler dispatches on the current mode). */
151
+ gridProps(): {
152
+ role: "grid";
153
+ 'aria-label': string;
154
+ tabindex: number;
155
+ onkeydown: (e: KeyboardEvent) => void;
156
+ };
157
+ /** A single day button in the month grid. Combine with `dayState` for the
158
+ * visual class flags. */
159
+ dayProps(cell: MonthMatrixCell, panelMonth: Date): {
160
+ type: "button";
161
+ role: "gridcell";
162
+ 'aria-selected': boolean;
163
+ 'aria-disabled': boolean;
164
+ 'aria-current': "date" | undefined;
165
+ 'aria-label': string;
166
+ title: string | undefined;
167
+ tabindex: number;
168
+ disabled: boolean;
169
+ onclick: (e: MouseEvent) => void;
170
+ onpointerenter: () => Date | null;
171
+ onpointerleave: () => Date | null;
172
+ };
173
+ /** A month cell in year mode. */
174
+ monthCellProps(monthIndex: number): {
175
+ type: "button";
176
+ disabled: boolean;
177
+ onclick: () => void;
178
+ };
179
+ /** A year cell in decade mode. */
180
+ yearCellProps(year: number): {
181
+ type: "button";
182
+ disabled: boolean;
183
+ onclick: () => void;
184
+ };
185
+ /** A preset shortcut button. */
186
+ presetProps(preset: CalendarPreset): {
187
+ type: "button";
188
+ disabled: boolean;
189
+ onclick: () => void;
190
+ };
191
+ };
192
+ export type Calendar = ReturnType<typeof createCalendar>;