@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
@@ -0,0 +1,43 @@
1
+ /**
2
+ * createToggle - the HEADLESS core behind <SvToggleButton>, in the same spirit
3
+ * as `createSvGrid`: a tiny controlled state machine (a single pressed on/off
4
+ * bit + ARIA) exposed as **prop-getters** you spread onto YOUR OWN markup. No
5
+ * styles, no DOM assumptions.
6
+ *
7
+ * ```svelte
8
+ * <script lang="ts">
9
+ * import { createToggle } from '@svgrid/grid'
10
+ * let pressed = $state(false)
11
+ * const t = createToggle({ pressed: () => pressed, onChange: (v) => (pressed = v) })
12
+ * </script>
13
+ * <button {...t.buttonProps()}>Bold</button>
14
+ * ```
15
+ *
16
+ * The styled <SvToggleButton> is just one renderer over this core.
17
+ */
18
+ /** Reactive inputs are passed as getters so the core tracks live prop changes
19
+ * (the same controlled pattern used across the SvGrid UI kit). */
20
+ export type ToggleConfig = {
21
+ pressed: () => boolean;
22
+ onChange?: (pressed: boolean) => void;
23
+ disabled?: () => boolean;
24
+ ariaLabel?: () => string | undefined;
25
+ };
26
+ export type ToggleButtonProps = {
27
+ type: 'button';
28
+ 'aria-pressed': boolean;
29
+ 'aria-label': string | undefined;
30
+ disabled: boolean;
31
+ 'data-pressed': '' | undefined;
32
+ onclick: () => void;
33
+ };
34
+ export type Toggle = {
35
+ /** Current pressed state. */
36
+ readonly pressed: boolean;
37
+ readonly disabled: boolean;
38
+ /** Flip pressed (no-op when disabled). */
39
+ toggle: () => void;
40
+ /** Spread onto the button element. */
41
+ buttonProps: () => ToggleButtonProps;
42
+ };
43
+ export declare function createToggle(config: ToggleConfig): Toggle;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * createToggle - the HEADLESS core behind <SvToggleButton>, in the same spirit
3
+ * as `createSvGrid`: a tiny controlled state machine (a single pressed on/off
4
+ * bit + ARIA) exposed as **prop-getters** you spread onto YOUR OWN markup. No
5
+ * styles, no DOM assumptions.
6
+ *
7
+ * ```svelte
8
+ * <script lang="ts">
9
+ * import { createToggle } from '@svgrid/grid'
10
+ * let pressed = $state(false)
11
+ * const t = createToggle({ pressed: () => pressed, onChange: (v) => (pressed = v) })
12
+ * </script>
13
+ * <button {...t.buttonProps()}>Bold</button>
14
+ * ```
15
+ *
16
+ * The styled <SvToggleButton> is just one renderer over this core.
17
+ */
18
+ export function createToggle(config) {
19
+ const pressed = () => config.pressed();
20
+ const disabled = () => config.disabled?.() ?? false;
21
+ function toggle() {
22
+ if (disabled())
23
+ return;
24
+ config.onChange?.(!pressed());
25
+ }
26
+ return {
27
+ get pressed() { return pressed(); },
28
+ get disabled() { return disabled(); },
29
+ toggle,
30
+ buttonProps: () => ({
31
+ type: 'button',
32
+ 'aria-pressed': pressed(),
33
+ 'aria-label': config.ariaLabel?.(),
34
+ disabled: disabled(),
35
+ 'data-pressed': pressed() ? '' : undefined,
36
+ onclick: toggle,
37
+ }),
38
+ };
39
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * createTree - the HEADLESS core behind <SvTree>, in the same spirit as
3
+ * `createSvGrid` for the grid: a runes-based state machine (expand/collapse,
4
+ * single-select highlight, cascading tri-state checkboxes, full keyboard) with
5
+ * **prop-getters** you spread onto YOUR OWN markup. The visible tree is
6
+ * flattened into `rows` so roving-focus keyboard nav stays a flat index walk.
7
+ *
8
+ * ```svelte
9
+ * <script lang="ts">
10
+ * import { createTree } from '@svgrid/grid'
11
+ * let selected = $state<string | null>(null)
12
+ * const tree = createTree({ nodes: () => nodes, selected: () => selected, onSelect: (id) => (selected = id) })
13
+ * </script>
14
+ * <div {...tree.treeProps()}>
15
+ * {#each tree.rows as row (row.node.id)}
16
+ * <div {...tree.itemProps(row)}>{row.node.label}</div>
17
+ * {/each}
18
+ * </div>
19
+ * ```
20
+ *
21
+ * The styled <SvTree> is just one renderer over this core. The cascade math
22
+ * (`treeDescendantIds`, `treeCheckState`) is exported as pure functions.
23
+ */
24
+ export type TreeNode = {
25
+ id: string;
26
+ label: string;
27
+ children?: TreeNode[];
28
+ disabled?: boolean;
29
+ };
30
+ export type CheckState = 'checked' | 'indeterminate' | 'unchecked';
31
+ /** A single visible (flattened) tree row. */
32
+ export type TreeRow = {
33
+ node: TreeNode;
34
+ depth: number;
35
+ hasChildren: boolean;
36
+ open: boolean;
37
+ parentId: string | null;
38
+ index: number;
39
+ };
40
+ /** All descendant ids of `node` (pure, depth-first). */
41
+ export declare function treeDescendantIds(node: TreeNode): string[];
42
+ /** Tri-state check status of `node` given the set of checked ids (pure). */
43
+ export declare function treeCheckState(node: TreeNode, checked: ReadonlySet<string>): CheckState;
44
+ /** Reactive inputs are passed as getters so the core tracks live prop changes. */
45
+ export type TreeConfig = {
46
+ nodes: () => ReadonlyArray<TreeNode>;
47
+ selected?: () => string | null;
48
+ onSelect?: (id: string) => void;
49
+ /** Seed the internally-managed expanded set (read once). */
50
+ expandedIds?: () => string[] | undefined;
51
+ onToggle?: (id: string, expanded: boolean) => void;
52
+ checkable?: () => boolean;
53
+ checked?: () => string[];
54
+ onCheck?: (ids: string[]) => void;
55
+ ariaLabel?: () => string | undefined;
56
+ };
57
+ export declare function createTree(config: TreeConfig): {
58
+ /** Internally-managed set of expanded node ids. */
59
+ readonly expanded: Set<string>;
60
+ /** Selected node id (single-select highlight). */
61
+ readonly selected: string | null;
62
+ /** Flattened list of visible rows. */
63
+ readonly rows: TreeRow[];
64
+ /** Roving-focus row index. */
65
+ readonly activeIndex: number;
66
+ /** Monotonic counter; changes only on keyboard navigation. */
67
+ readonly focusTick: number;
68
+ isExpanded: (id: string) => boolean;
69
+ isSelected: (id: string) => boolean;
70
+ checkStateOf: (node: TreeNode) => CheckState;
71
+ toggle: (id: string) => void;
72
+ toggleExpand: (node: TreeNode) => void;
73
+ select: (id: string) => void;
74
+ toggleCheck: (node: TreeNode) => void;
75
+ setActive: (i: number, focus?: boolean) => void;
76
+ onKeydown: (e: KeyboardEvent) => void;
77
+ /** Spread onto the tree container element. */
78
+ treeProps: () => {
79
+ role: "tree";
80
+ 'aria-label': string | undefined;
81
+ 'aria-multiselectable': boolean;
82
+ };
83
+ /** Spread onto the treeitem element for a flattened `row`. */
84
+ itemProps: (row: TreeRow) => {
85
+ role: "treeitem";
86
+ 'aria-level': number;
87
+ 'aria-selected': boolean;
88
+ 'aria-expanded': boolean | undefined;
89
+ 'aria-checked': "true" | "mixed" | "false" | undefined;
90
+ 'data-row': number;
91
+ tabindex: number;
92
+ onclick: () => void;
93
+ onkeydown: (e: KeyboardEvent) => void;
94
+ };
95
+ };
96
+ export type Tree = ReturnType<typeof createTree>;
@@ -0,0 +1,194 @@
1
+ /** All descendant ids of `node` (pure, depth-first). */
2
+ export function treeDescendantIds(node) {
3
+ const ids = [];
4
+ const walk = (n) => { for (const c of n.children ?? []) {
5
+ ids.push(c.id);
6
+ walk(c);
7
+ } };
8
+ walk(node);
9
+ return ids;
10
+ }
11
+ /** Tri-state check status of `node` given the set of checked ids (pure). */
12
+ export function treeCheckState(node, checked) {
13
+ if (!node.children?.length)
14
+ return checked.has(node.id) ? 'checked' : 'unchecked';
15
+ const desc = treeDescendantIds(node);
16
+ const on = desc.filter((id) => checked.has(id)).length;
17
+ if (on === 0 && !checked.has(node.id))
18
+ return 'unchecked';
19
+ if (on === desc.length)
20
+ return 'checked';
21
+ return 'indeterminate';
22
+ }
23
+ function findNode(list, id) {
24
+ for (const n of list) {
25
+ if (n.id === id)
26
+ return n;
27
+ if (n.children) {
28
+ const f = findNode(n.children, id);
29
+ if (f)
30
+ return f;
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+ export function createTree(config) {
36
+ const nodes = () => config.nodes();
37
+ const checkable = () => config.checkable?.() ?? false;
38
+ const selected = () => config.selected?.() ?? null;
39
+ // Internal expanded set, seeded from the prop once (then self-managed).
40
+ let expanded = $state(new Set());
41
+ let seeded = false;
42
+ $effect(() => {
43
+ if (!seeded) {
44
+ seeded = true;
45
+ const ids = config.expandedIds?.();
46
+ if (ids)
47
+ expanded = new Set(ids);
48
+ }
49
+ });
50
+ const rows = $derived.by(() => {
51
+ const out = [];
52
+ const walk = (list, depth, parentId) => {
53
+ for (const node of list) {
54
+ const hasChildren = !!node.children?.length;
55
+ const open = expanded.has(node.id);
56
+ out.push({ node, depth, hasChildren, open, parentId, index: out.length });
57
+ if (hasChildren && open)
58
+ walk(node.children, depth + 1, node.id);
59
+ }
60
+ };
61
+ walk(nodes(), 0, null);
62
+ return out;
63
+ });
64
+ let active = $state(0);
65
+ $effect(() => { if (active >= rows.length)
66
+ active = Math.max(0, rows.length - 1); });
67
+ const checkedSet = $derived(new Set(config.checked?.() ?? []));
68
+ // Roving focus target; `focusTick` bumps only on keyboard moves.
69
+ let focusTick = $state(0);
70
+ function setActive(i, focus = false) { active = i; if (focus)
71
+ focusTick++; }
72
+ const checkStateOf = (node) => treeCheckState(node, checkedSet);
73
+ const isExpanded = (id) => expanded.has(id);
74
+ const isSelected = (id) => id === selected();
75
+ function toggleExpand(node) {
76
+ if (!node.children?.length)
77
+ return;
78
+ const next = new Set(expanded);
79
+ const willOpen = !next.has(node.id);
80
+ willOpen ? next.add(node.id) : next.delete(node.id);
81
+ expanded = next;
82
+ config.onToggle?.(node.id, willOpen);
83
+ }
84
+ const toggle = (id) => { const n = findNode(nodes(), id); if (n)
85
+ toggleExpand(n); };
86
+ function select(node) {
87
+ if (node.disabled)
88
+ return;
89
+ config.onSelect?.(node.id);
90
+ }
91
+ const selectId = (id) => { const n = findNode(nodes(), id); if (n)
92
+ select(n); };
93
+ function toggleCheck(node) {
94
+ if (node.disabled)
95
+ return;
96
+ const next = new Set(checkedSet);
97
+ const ids = [node.id, ...treeDescendantIds(node)];
98
+ const currentlyOn = checkStateOf(node) === 'checked';
99
+ for (const id of ids)
100
+ currentlyOn ? next.delete(id) : next.add(id);
101
+ config.onCheck?.([...next]);
102
+ }
103
+ function onKeydown(e) {
104
+ const item = rows[active];
105
+ if (!item)
106
+ return;
107
+ switch (e.key) {
108
+ case 'ArrowDown':
109
+ e.preventDefault();
110
+ setActive(Math.min(active + 1, rows.length - 1), true);
111
+ break;
112
+ case 'ArrowUp':
113
+ e.preventDefault();
114
+ setActive(Math.max(active - 1, 0), true);
115
+ break;
116
+ case 'ArrowRight':
117
+ e.preventDefault();
118
+ if (item.hasChildren && !item.open)
119
+ toggleExpand(item.node);
120
+ else if (item.hasChildren && item.open)
121
+ setActive(Math.min(active + 1, rows.length - 1), true);
122
+ break;
123
+ case 'ArrowLeft':
124
+ e.preventDefault();
125
+ if (item.hasChildren && item.open)
126
+ toggleExpand(item.node);
127
+ else if (item.parentId) {
128
+ const pi = rows.findIndex((r) => r.node.id === item.parentId);
129
+ if (pi >= 0)
130
+ setActive(pi, true);
131
+ }
132
+ break;
133
+ case 'Enter':
134
+ e.preventDefault();
135
+ select(item.node);
136
+ break;
137
+ case ' ':
138
+ e.preventDefault();
139
+ checkable() ? toggleCheck(item.node) : select(item.node);
140
+ break;
141
+ case 'Home':
142
+ e.preventDefault();
143
+ setActive(0, true);
144
+ break;
145
+ case 'End':
146
+ e.preventDefault();
147
+ setActive(rows.length - 1, true);
148
+ break;
149
+ }
150
+ }
151
+ return {
152
+ /** Internally-managed set of expanded node ids. */
153
+ get expanded() { return expanded; },
154
+ /** Selected node id (single-select highlight). */
155
+ get selected() { return selected(); },
156
+ /** Flattened list of visible rows. */
157
+ get rows() { return rows; },
158
+ /** Roving-focus row index. */
159
+ get activeIndex() { return active; },
160
+ /** Monotonic counter; changes only on keyboard navigation. */
161
+ get focusTick() { return focusTick; },
162
+ isExpanded,
163
+ isSelected,
164
+ checkStateOf,
165
+ toggle,
166
+ toggleExpand,
167
+ select: selectId,
168
+ toggleCheck,
169
+ setActive,
170
+ onKeydown,
171
+ /** Spread onto the tree container element. */
172
+ treeProps: () => ({
173
+ role: 'tree',
174
+ 'aria-label': config.ariaLabel?.(),
175
+ 'aria-multiselectable': checkable(),
176
+ }),
177
+ /** Spread onto the treeitem element for a flattened `row`. */
178
+ itemProps: (row) => {
179
+ const cs = checkable() ? checkStateOf(row.node) : 'unchecked';
180
+ const ariaChecked = checkable() ? (cs === 'checked' ? 'true' : cs === 'indeterminate' ? 'mixed' : 'false') : undefined;
181
+ return {
182
+ role: 'treeitem',
183
+ 'aria-level': row.depth + 1,
184
+ 'aria-selected': row.node.id === selected(),
185
+ 'aria-expanded': row.hasChildren ? row.open : undefined,
186
+ 'aria-checked': ariaChecked,
187
+ 'data-row': row.index,
188
+ tabindex: row.index === active ? 0 : -1,
189
+ onclick: () => { setActive(row.index); select(row.node); },
190
+ onkeydown: onKeydown,
191
+ };
192
+ },
193
+ };
194
+ }
@@ -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 {};