@svgrid/grid 1.2.22 → 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
@@ -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,88 @@
1
+ /**
2
+ * date-core - framework-free date math shared by SvCalendar, SvTimePicker and
3
+ * SvDateTimePicker. No Svelte, no DOM: pure functions over the native `Date`
4
+ * so it is trivially unit- and mutation-testable and reusable standalone.
5
+ *
6
+ * Conventions:
7
+ * - A "day" is compared by local calendar date (year/month/date), ignoring the
8
+ * time-of-day, unless a helper says otherwise.
9
+ * - `firstDayOfWeek` is 0-6 (0 = Sunday), matching `Date.getDay()` and Smart's
10
+ * `firstDayOfWeek`.
11
+ */
12
+ export type DateLike = Date | number | string;
13
+ /** Coerce a Date | epoch-ms | parseable string to a Date, or null if invalid. */
14
+ export declare function toDate(value: DateLike | null | undefined): Date | null;
15
+ /** A new Date at local midnight of the same calendar day (time stripped). */
16
+ export declare function startOfDay(d: Date): Date;
17
+ /** First day (midnight) of the month `d` falls in. */
18
+ export declare function startOfMonth(d: Date): Date;
19
+ /** Last day (midnight) of the month `d` falls in. */
20
+ export declare function endOfMonth(d: Date): Date;
21
+ /** Number of days in the month `d` falls in. */
22
+ export declare function daysInMonth(d: Date): number;
23
+ /**
24
+ * Start of the week containing `d`, honoring `firstDayOfWeek` (0-6). Returns a
25
+ * local-midnight Date on or before `d`.
26
+ */
27
+ export declare function startOfWeek(d: Date, firstDayOfWeek?: number): Date;
28
+ /** True when both dates are the same local calendar day. */
29
+ export declare function isSameDay(a: Date | null, b: Date | null): boolean;
30
+ /** True when both dates are in the same year+month. */
31
+ export declare function isSameMonth(a: Date | null, b: Date | null): boolean;
32
+ /** -1 / 0 / 1 comparison by calendar day (time-of-day ignored). */
33
+ export declare function compareDay(a: Date, b: Date): number;
34
+ /** Add `n` days (can be negative), returning a new Date. */
35
+ export declare function addDays(d: Date, n: number): Date;
36
+ /**
37
+ * Add `n` calendar months, clamping the day-of-month so 31 Jan + 1 month = 28/29
38
+ * Feb rather than spilling into March (matches how calendars paginate).
39
+ */
40
+ export declare function addMonths(d: Date, n: number): Date;
41
+ /** Add `n` years, clamping Feb-29 to Feb-28 on non-leap targets. */
42
+ export declare function addYears(d: Date, n: number): Date;
43
+ /** Clamp `d` to the inclusive [min, max] range (either bound may be null). */
44
+ export declare function clampDate(d: Date, min: Date | null, max: Date | null): Date;
45
+ /**
46
+ * ISO 8601 week number (1-53) for `d`. Week 1 is the week containing the first
47
+ * Thursday of the year; weeks start Monday. Independent of `firstDayOfWeek`,
48
+ * matching Smart's `weekNumbers` column which is always ISO.
49
+ */
50
+ export declare function isoWeek(d: Date): number;
51
+ /**
52
+ * The [start, end] years of the decade block that `year` belongs to, as Smart
53
+ * renders it: a 12-cell page running from the decade start minus 1 to plus 10
54
+ * (one leading + one trailing year for context). We expose the canonical
55
+ * decade [Y0, Y0+9] and let the view pad.
56
+ */
57
+ export declare function decadeRange(year: number): {
58
+ start: number;
59
+ end: number;
60
+ };
61
+ /** The [start, end] years of the century block (100-year) `year` belongs to. */
62
+ export declare function centuryRange(year: number): {
63
+ start: number;
64
+ end: number;
65
+ };
66
+ export type MonthMatrixCell = {
67
+ date: Date;
68
+ /** In the displayed month (vs. leading/trailing days of adjacent months). */
69
+ inMonth: boolean;
70
+ /** ISO week number of the row this cell sits in. */
71
+ week: number;
72
+ };
73
+ /**
74
+ * Build a month grid of `weeks` rows x 7 columns starting at `firstDayOfWeek`,
75
+ * padded with the trailing days of the previous month and leading days of the
76
+ * next so every row is full. `weeks` defaults to 6 (the max any month needs),
77
+ * matching Smart's fixed-height calendar.
78
+ */
79
+ export declare function monthMatrix(monthDate: Date, firstDayOfWeek?: number, weeks?: number): MonthMatrixCell[][];
80
+ /**
81
+ * Ordered weekday indexes (0-6) for a header row starting at `firstDayOfWeek`.
82
+ * e.g. firstDayOfWeek=1 -> [1,2,3,4,5,6,0].
83
+ */
84
+ export declare function weekdayOrder(firstDayOfWeek?: number): number[];
85
+ /** Merge a calendar day with a time-of-day taken from `time`, returning a new Date. */
86
+ export declare function withTime(day: Date, time: Date): Date;
87
+ /** Snap a minute value to the nearest lower multiple of `interval` (>=1). */
88
+ export declare function snapMinute(minute: number, interval: number): number;
@@ -0,0 +1,165 @@
1
+ /**
2
+ * date-core - framework-free date math shared by SvCalendar, SvTimePicker and
3
+ * SvDateTimePicker. No Svelte, no DOM: pure functions over the native `Date`
4
+ * so it is trivially unit- and mutation-testable and reusable standalone.
5
+ *
6
+ * Conventions:
7
+ * - A "day" is compared by local calendar date (year/month/date), ignoring the
8
+ * time-of-day, unless a helper says otherwise.
9
+ * - `firstDayOfWeek` is 0-6 (0 = Sunday), matching `Date.getDay()` and Smart's
10
+ * `firstDayOfWeek`.
11
+ */
12
+ /** Coerce a Date | epoch-ms | parseable string to a Date, or null if invalid. */
13
+ export function toDate(value) {
14
+ if (value == null)
15
+ return null;
16
+ const d = value instanceof Date ? new Date(value.getTime()) : new Date(value);
17
+ return isNaN(d.getTime()) ? null : d;
18
+ }
19
+ /** A new Date at local midnight of the same calendar day (time stripped). */
20
+ export function startOfDay(d) {
21
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
22
+ }
23
+ /** First day (midnight) of the month `d` falls in. */
24
+ export function startOfMonth(d) {
25
+ return new Date(d.getFullYear(), d.getMonth(), 1);
26
+ }
27
+ /** Last day (midnight) of the month `d` falls in. */
28
+ export function endOfMonth(d) {
29
+ return new Date(d.getFullYear(), d.getMonth() + 1, 0);
30
+ }
31
+ /** Number of days in the month `d` falls in. */
32
+ export function daysInMonth(d) {
33
+ return endOfMonth(d).getDate();
34
+ }
35
+ /**
36
+ * Start of the week containing `d`, honoring `firstDayOfWeek` (0-6). Returns a
37
+ * local-midnight Date on or before `d`.
38
+ */
39
+ export function startOfWeek(d, firstDayOfWeek = 0) {
40
+ const start = startOfDay(d);
41
+ const diff = (start.getDay() - firstDayOfWeek + 7) % 7;
42
+ start.setDate(start.getDate() - diff);
43
+ return start;
44
+ }
45
+ /** True when both dates are the same local calendar day. */
46
+ export function isSameDay(a, b) {
47
+ if (!a || !b)
48
+ return false;
49
+ return (a.getFullYear() === b.getFullYear() &&
50
+ a.getMonth() === b.getMonth() &&
51
+ a.getDate() === b.getDate());
52
+ }
53
+ /** True when both dates are in the same year+month. */
54
+ export function isSameMonth(a, b) {
55
+ if (!a || !b)
56
+ return false;
57
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
58
+ }
59
+ /** -1 / 0 / 1 comparison by calendar day (time-of-day ignored). */
60
+ export function compareDay(a, b) {
61
+ const av = startOfDay(a).getTime();
62
+ const bv = startOfDay(b).getTime();
63
+ return av < bv ? -1 : av > bv ? 1 : 0;
64
+ }
65
+ /** Add `n` days (can be negative), returning a new Date. */
66
+ export function addDays(d, n) {
67
+ const r = new Date(d.getTime());
68
+ r.setDate(r.getDate() + n);
69
+ return r;
70
+ }
71
+ /**
72
+ * Add `n` calendar months, clamping the day-of-month so 31 Jan + 1 month = 28/29
73
+ * Feb rather than spilling into March (matches how calendars paginate).
74
+ */
75
+ export function addMonths(d, n) {
76
+ const day = d.getDate();
77
+ const r = new Date(d.getFullYear(), d.getMonth() + n, 1, d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
78
+ r.setDate(Math.min(day, daysInMonth(r)));
79
+ return r;
80
+ }
81
+ /** Add `n` years, clamping Feb-29 to Feb-28 on non-leap targets. */
82
+ export function addYears(d, n) {
83
+ return addMonths(d, n * 12);
84
+ }
85
+ /** Clamp `d` to the inclusive [min, max] range (either bound may be null). */
86
+ export function clampDate(d, min, max) {
87
+ if (min && d.getTime() < min.getTime())
88
+ return new Date(min.getTime());
89
+ if (max && d.getTime() > max.getTime())
90
+ return new Date(max.getTime());
91
+ return d;
92
+ }
93
+ /**
94
+ * ISO 8601 week number (1-53) for `d`. Week 1 is the week containing the first
95
+ * Thursday of the year; weeks start Monday. Independent of `firstDayOfWeek`,
96
+ * matching Smart's `weekNumbers` column which is always ISO.
97
+ */
98
+ export function isoWeek(d) {
99
+ // Shift to the Thursday of this week, then count weeks from Jan 1.
100
+ const date = startOfDay(d);
101
+ const day = (date.getDay() + 6) % 7; // Mon=0..Sun=6
102
+ date.setDate(date.getDate() - day + 3); // Thursday of this week
103
+ const firstThursday = new Date(date.getFullYear(), 0, 4);
104
+ const firstDay = (firstThursday.getDay() + 6) % 7;
105
+ firstThursday.setDate(firstThursday.getDate() - firstDay + 3);
106
+ const diffMs = date.getTime() - firstThursday.getTime();
107
+ return 1 + Math.round(diffMs / (7 * 24 * 3600 * 1000));
108
+ }
109
+ /**
110
+ * The [start, end] years of the decade block that `year` belongs to, as Smart
111
+ * renders it: a 12-cell page running from the decade start minus 1 to plus 10
112
+ * (one leading + one trailing year for context). We expose the canonical
113
+ * decade [Y0, Y0+9] and let the view pad.
114
+ */
115
+ export function decadeRange(year) {
116
+ const start = Math.floor(year / 10) * 10;
117
+ return { start, end: start + 9 };
118
+ }
119
+ /** The [start, end] years of the century block (100-year) `year` belongs to. */
120
+ export function centuryRange(year) {
121
+ const start = Math.floor(year / 100) * 100;
122
+ return { start, end: start + 99 };
123
+ }
124
+ /**
125
+ * Build a month grid of `weeks` rows x 7 columns starting at `firstDayOfWeek`,
126
+ * padded with the trailing days of the previous month and leading days of the
127
+ * next so every row is full. `weeks` defaults to 6 (the max any month needs),
128
+ * matching Smart's fixed-height calendar.
129
+ */
130
+ export function monthMatrix(monthDate, firstDayOfWeek = 0, weeks = 6) {
131
+ const first = startOfMonth(monthDate);
132
+ const gridStart = startOfWeek(first, firstDayOfWeek);
133
+ const rows = [];
134
+ let cursor = gridStart;
135
+ for (let r = 0; r < weeks; r++) {
136
+ const row = [];
137
+ const weekNo = isoWeek(cursor);
138
+ for (let c = 0; c < 7; c++) {
139
+ row.push({
140
+ date: cursor,
141
+ inMonth: cursor.getMonth() === first.getMonth() && cursor.getFullYear() === first.getFullYear(),
142
+ week: weekNo,
143
+ });
144
+ cursor = addDays(cursor, 1);
145
+ }
146
+ rows.push(row);
147
+ }
148
+ return rows;
149
+ }
150
+ /**
151
+ * Ordered weekday indexes (0-6) for a header row starting at `firstDayOfWeek`.
152
+ * e.g. firstDayOfWeek=1 -> [1,2,3,4,5,6,0].
153
+ */
154
+ export function weekdayOrder(firstDayOfWeek = 0) {
155
+ return Array.from({ length: 7 }, (_, i) => (firstDayOfWeek + i) % 7);
156
+ }
157
+ /** Merge a calendar day with a time-of-day taken from `time`, returning a new Date. */
158
+ export function withTime(day, time) {
159
+ return new Date(day.getFullYear(), day.getMonth(), day.getDate(), time.getHours(), time.getMinutes(), time.getSeconds(), time.getMilliseconds());
160
+ }
161
+ /** Snap a minute value to the nearest lower multiple of `interval` (>=1). */
162
+ export function snapMinute(minute, interval) {
163
+ const step = Math.max(1, Math.floor(interval));
164
+ return Math.floor(minute / step) * step;
165
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * date-format - a small .NET/Smart-style date format-token engine used for
3
+ * `formatString` display and for parsing the masked DateTimePicker input. It is
4
+ * locale-aware for names (month/weekday, AM/PM) via `Intl`, and framework-free.
5
+ *
6
+ * Supported tokens (case-sensitive, matching Smart's `formatString`):
7
+ * d dd day of month (5, 05)
8
+ * ddd dddd weekday name (Mon, Monday) [locale]
9
+ * M MM month number (6, 06)
10
+ * MMM MMMM month name (Jun, June) [locale]
11
+ * yy yyyy year (26, 2026)
12
+ * H HH hour 24 (7, 07)
13
+ * h hh hour 12 (7, 07)
14
+ * m mm minute
15
+ * s ss second
16
+ * f ff fff fractional seconds (tenths..millis)
17
+ * t tt AM/PM designator (A, AM) [locale]
18
+ *
19
+ * Anything else is a literal. Wrap literal letters in single quotes ('T') or
20
+ * escape with a backslash (\T) to keep them out of token matching.
21
+ */
22
+ type Part = {
23
+ token: string;
24
+ } | {
25
+ literal: string;
26
+ };
27
+ /** Split a mask into an ordered list of token / literal parts. */
28
+ export declare function tokenizeMask(mask: string): Part[];
29
+ /** Render `date` per `mask` in `locale`. */
30
+ export declare function formatDate(date: Date, mask: string, locale?: string): string;
31
+ /**
32
+ * Parse `str` against `mask` in `locale`. Returns a Date or null if the string
33
+ * does not satisfy the mask. `yearCutoff` disambiguates 2-digit years: values
34
+ * <= (yearCutoff % 100) map to 2000s, above to 1900s (Smart's default 1926 ->
35
+ * a 26 pivot). `base` supplies fields the mask omits (defaults to epoch-zero
36
+ * local midnight so a time-only mask yields a today-agnostic time).
37
+ */
38
+ export declare function parseDate(str: string, mask: string, locale?: string, yearCutoff?: number, base?: Date): Date | null;
39
+ export {};