@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,265 @@
1
+ <script lang="ts" module>
2
+ export type { DateTimeValue } from './createDateTimePicker.svelte'
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ /**
7
+ * SvDateTimePicker - a text input (formatted via the date-format token engine)
8
+ * plus a dropdown that composes SvCalendar + SvTimePicker behind DATE / TIME
9
+ * tabs. Parity target: Smart `smart-date-time-picker`. It is a styled renderer
10
+ * over the headless `createDateTimePicker` core (value math, parse/format,
11
+ * clamp, dropdown open/tab state, sub-picker wiring); the component keeps the
12
+ * render-only concerns - the portalled popover positioning (via popover.ts), DOM
13
+ * refs, and outside-click / reposition listeners - so the dropdown is never
14
+ * clipped by the grid's scroll container.
15
+ */
16
+ import SvCalendar from './SvCalendar.svelte'
17
+ import SvTimePicker from './SvTimePicker.svelte'
18
+ import { anchoredRect, portalToBody, type AnchoredRect } from './popover'
19
+ import { createDateTimePicker, type DateTimeValue, type DropDownDisplayMode } from './createDateTimePicker.svelte'
20
+ import type { DateLike } from './datetime/date-core'
21
+
22
+ type Props = {
23
+ value?: DateTimeValue
24
+ onChange?: (value: Date | null) => void
25
+ /** Fired when the value is finalized (Enter, blur, or a single-date pick).
26
+ * Used by the grid to save the cell. */
27
+ onCommit?: (value: Date | null) => void
28
+ /** Fired on Escape / dismiss without committing (grid cancels the edit). */
29
+ onCancel?: () => void
30
+ /** Display / parse mask (token engine). */
31
+ formatString?: string
32
+ min?: DateLike | null
33
+ max?: DateLike | null
34
+ nullable?: boolean
35
+ placeholder?: string
36
+ disabled?: boolean
37
+ readonly?: boolean
38
+ name?: string
39
+ locale?: string
40
+ firstDayOfWeek?: number
41
+ weekNumbers?: boolean
42
+ hourFormat?: '12-hour' | '24-hour'
43
+ minuteInterval?: number
44
+ /** Which tabs the dropdown shows. */
45
+ dropDownDisplayMode?: DropDownDisplayMode
46
+ /** Up/down spinner buttons that bump the value by `stepMinutes`. */
47
+ spinButtons?: boolean
48
+ stepMinutes?: number
49
+ /** Open the dropdown as soon as the field is focused (grid in-cell editing). */
50
+ autoOpen?: boolean
51
+ /** Animate the calendar's month/drill navigation (honors reduced-motion). */
52
+ animate?: boolean | 'slide' | 'fade'
53
+ }
54
+
55
+ let {
56
+ value = null,
57
+ onChange,
58
+ onCommit,
59
+ onCancel,
60
+ formatString = 'yyyy-MM-dd HH:mm',
61
+ min = null,
62
+ max = null,
63
+ nullable = true,
64
+ placeholder = 'Select date & time',
65
+ disabled = false,
66
+ readonly = false,
67
+ name,
68
+ locale = typeof navigator !== 'undefined' ? navigator.language : 'en-US',
69
+ firstDayOfWeek = 0,
70
+ weekNumbers = false,
71
+ hourFormat = '24-hour',
72
+ minuteInterval = 1,
73
+ dropDownDisplayMode = 'both',
74
+ spinButtons = false,
75
+ stepMinutes = 1,
76
+ autoOpen = false,
77
+ animate = false,
78
+ }: Props = $props()
79
+
80
+ // The headless core owns value math, parse/format, clamping, the dropdown
81
+ // open/tab state and the sub-picker wiring. Reactive inputs are getters;
82
+ // callbacks are closures.
83
+ const dtp = createDateTimePicker({
84
+ value: () => value,
85
+ onChange: (d) => onChange?.(d),
86
+ onCommit: (d) => onCommit?.(d),
87
+ onCancel: () => onCancel?.(),
88
+ formatString: () => formatString,
89
+ min: () => min,
90
+ max: () => max,
91
+ nullable: () => nullable,
92
+ locale: () => locale,
93
+ dropDownDisplayMode: () => dropDownDisplayMode,
94
+ spinButtons: () => spinButtons,
95
+ stepMinutes: () => stepMinutes,
96
+ disabled: () => disabled,
97
+ readonly: () => readonly,
98
+ })
99
+
100
+ // --- Portalled dropdown positioning (DOM-bound; stays in the component) -----
101
+ let triggerEl = $state<HTMLDivElement | null>(null)
102
+ let panelEl = $state<HTMLDivElement | null>(null)
103
+ let panelRect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false })
104
+
105
+ function updatePos() {
106
+ if (!triggerEl) return
107
+ panelRect = anchoredRect(triggerEl.getBoundingClientRect(), { estimatedHeight: 360, minWidth: 300 })
108
+ }
109
+
110
+ // Reposition on open + while open (scroll/resize), and dismiss on outside click.
111
+ $effect(() => {
112
+ if (!dtp.open) return
113
+ updatePos()
114
+ const reposition = () => updatePos()
115
+ window.addEventListener('scroll', reposition, true)
116
+ window.addEventListener('resize', reposition)
117
+ const onDown = (e: PointerEvent) => {
118
+ const t = e.target as Node | null
119
+ if (!t) return
120
+ if (triggerEl?.contains(t) || panelEl?.contains(t)) return
121
+ dtp.closePanel()
122
+ }
123
+ document.addEventListener('pointerdown', onDown, true)
124
+ return () => {
125
+ window.removeEventListener('scroll', reposition, true)
126
+ window.removeEventListener('resize', reposition)
127
+ document.removeEventListener('pointerdown', onDown, true)
128
+ }
129
+ })
130
+
131
+ function focusOpen(node: HTMLInputElement) {
132
+ if (!autoOpen) return
133
+ node.focus()
134
+ dtp.openPanel()
135
+ }
136
+
137
+ function onRootFocusOut(e: FocusEvent) {
138
+ const next = e.relatedTarget as Node | null
139
+ // Ignore focus moving within the field or into the portalled panel.
140
+ if (next && (triggerEl?.contains(next) || panelEl?.contains(next))) return
141
+ onCommit?.(dtp.current)
142
+ }
143
+ </script>
144
+
145
+ <div class="sv-dtp" class:sv-dtp--disabled={disabled} onfocusout={onRootFocusOut}>
146
+ <div class="sv-dtp__field" bind:this={triggerEl}>
147
+ {#if spinButtons}
148
+ <div class="sv-dtp__spin">
149
+ <button {...dtp.spinProps(1)}>▲</button>
150
+ <button {...dtp.spinProps(-1)}>▼</button>
151
+ </div>
152
+ {/if}
153
+ <input
154
+ class="sv-dtp__input"
155
+ bind:value={dtp.text}
156
+ {placeholder}
157
+ {...dtp.inputProps()}
158
+ use:focusOpen
159
+ />
160
+ {#if nullable && dtp.current && dtp.isInteractive}
161
+ <button class="sv-dtp__clear" {...dtp.clearProps()}>&times;</button>
162
+ {/if}
163
+ <button class="sv-dtp__toggle" {...dtp.toggleProps()}>
164
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" /><path d="M16 2v4M8 2v4M3 10h18" /></svg>
165
+ </button>
166
+ </div>
167
+
168
+ {#if dtp.open}
169
+ <div
170
+ class="sv-dtp__panel"
171
+ bind:this={panelEl}
172
+ use:portalToBody
173
+ style:position="fixed"
174
+ style:top={`${panelRect.top}px`}
175
+ style:left={`${panelRect.left}px`}
176
+ role="dialog"
177
+ aria-label="Choose date and time"
178
+ >
179
+ {#if dtp.showDateTab && dtp.showTimeTab}
180
+ <div class="sv-dtp__tabs" role="tablist">
181
+ <button class:is-active={dtp.tab === 'date'} {...dtp.tabProps('date')}>DATE</button>
182
+ <button class:is-active={dtp.tab === 'time'} {...dtp.tabProps('time')}>TIME</button>
183
+ </div>
184
+ {/if}
185
+ <div class="sv-dtp__body">
186
+ {#if dtp.tab === 'date' && dtp.showDateTab}
187
+ <SvCalendar
188
+ value={dtp.current}
189
+ selectionMode="one"
190
+ min={min}
191
+ max={max}
192
+ {firstDayOfWeek}
193
+ {weekNumbers}
194
+ {locale}
195
+ {animate}
196
+ onChange={dtp.onCalendarChange}
197
+ />
198
+ {:else if dtp.showTimeTab}
199
+ <SvTimePicker
200
+ value={dtp.current}
201
+ format={hourFormat}
202
+ {minuteInterval}
203
+ footer
204
+ onChange={dtp.onTimeChange}
205
+ />
206
+ {/if}
207
+ </div>
208
+ </div>
209
+ {/if}
210
+
211
+ {#if name}<input type="hidden" {name} value={dtp.current ? dtp.current.toISOString() : ''} />{/if}
212
+ </div>
213
+
214
+ <style>
215
+ .sv-dtp {
216
+ --_accent: var(--sg-accent, #2563eb);
217
+ --_bg: var(--sg-input-bg, var(--sg-bg, #fff));
218
+ --_fg: var(--sg-fg, #0f172a);
219
+ --_muted: var(--sg-muted, #64748b);
220
+ --_border: var(--sg-input-border, var(--sg-border, #cbd5e1));
221
+ --_radius: var(--sg-radius, 8px);
222
+ display: inline-block; position: relative; width: 240px; color: var(--_fg);
223
+ }
224
+ .sv-dtp--disabled { opacity: 0.6; }
225
+ .sv-dtp__field {
226
+ display: flex; align-items: center; gap: 2px;
227
+ background: var(--_bg); border: 1px solid var(--_border); border-radius: var(--_radius);
228
+ padding: 0 4px 0 0; height: 34px;
229
+ }
230
+ .sv-dtp__field:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
231
+ .sv-dtp__input {
232
+ flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit;
233
+ font: inherit; font-size: 13px; padding: 0 8px; height: 100%;
234
+ }
235
+ .sv-dtp__spin { display: flex; flex-direction: column; border-right: 1px solid var(--_border); }
236
+ .sv-dtp__spin button {
237
+ flex: 1; width: 20px; border: 0; background: none; color: var(--_muted); cursor: pointer;
238
+ font-size: 7px; line-height: 1; padding: 0;
239
+ }
240
+ .sv-dtp__spin button:hover:not(:disabled) { color: var(--_accent); }
241
+ .sv-dtp__clear, .sv-dtp__toggle {
242
+ display: grid; place-items: center; width: 26px; height: 26px; flex: none;
243
+ background: none; border: 0; color: var(--_muted); cursor: pointer; border-radius: 6px;
244
+ }
245
+ .sv-dtp__clear { font-size: 17px; line-height: 1; }
246
+ .sv-dtp__clear:hover, .sv-dtp__toggle:hover { color: var(--_accent); background: color-mix(in srgb, var(--_accent) 10%, transparent); }
247
+
248
+ /* Portalled panel - lives under <body>, styled globally. */
249
+ :global(.sv-dtp__panel) {
250
+ z-index: 2147483647;
251
+ background: var(--sg-bg, #fff);
252
+ border: 1px solid var(--sg-border, #e2e8f0);
253
+ border-radius: 12px;
254
+ box-shadow: 0 16px 48px -12px rgba(15, 23, 42, 0.35);
255
+ overflow: hidden;
256
+ }
257
+ :global(.sv-dtp__tabs) { display: flex; border-bottom: 1px solid var(--sg-border, #e2e8f0); }
258
+ :global(.sv-dtp__tabs button) {
259
+ flex: 1; padding: 9px; font: inherit; font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
260
+ background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer;
261
+ border-bottom: 2px solid transparent;
262
+ }
263
+ :global(.sv-dtp__tabs button.is-active) { color: var(--sg-accent, #2563eb); border-bottom-color: var(--sg-accent, #2563eb); }
264
+ :global(.sv-dtp__body) { display: grid; place-items: center; padding: 8px; }
265
+ </style>
@@ -0,0 +1,38 @@
1
+ export type { DateTimeValue } from './createDateTimePicker.svelte';
2
+ import { type DateTimeValue, type DropDownDisplayMode } from './createDateTimePicker.svelte';
3
+ import type { DateLike } from './datetime/date-core';
4
+ type Props = {
5
+ value?: DateTimeValue;
6
+ onChange?: (value: Date | null) => void;
7
+ /** Fired when the value is finalized (Enter, blur, or a single-date pick).
8
+ * Used by the grid to save the cell. */
9
+ onCommit?: (value: Date | null) => void;
10
+ /** Fired on Escape / dismiss without committing (grid cancels the edit). */
11
+ onCancel?: () => void;
12
+ /** Display / parse mask (token engine). */
13
+ formatString?: string;
14
+ min?: DateLike | null;
15
+ max?: DateLike | null;
16
+ nullable?: boolean;
17
+ placeholder?: string;
18
+ disabled?: boolean;
19
+ readonly?: boolean;
20
+ name?: string;
21
+ locale?: string;
22
+ firstDayOfWeek?: number;
23
+ weekNumbers?: boolean;
24
+ hourFormat?: '12-hour' | '24-hour';
25
+ minuteInterval?: number;
26
+ /** Which tabs the dropdown shows. */
27
+ dropDownDisplayMode?: DropDownDisplayMode;
28
+ /** Up/down spinner buttons that bump the value by `stepMinutes`. */
29
+ spinButtons?: boolean;
30
+ stepMinutes?: number;
31
+ /** Open the dropdown as soon as the field is focused (grid in-cell editing). */
32
+ autoOpen?: boolean;
33
+ /** Animate the calendar's month/drill navigation (honors reduced-motion). */
34
+ animate?: boolean | 'slide' | 'fade';
35
+ };
36
+ declare const SvDateTimePicker: import("svelte").Component<Props, {}, "">;
37
+ type SvDateTimePicker = ReturnType<typeof SvDateTimePicker>;
38
+ export default SvDateTimePicker;
@@ -0,0 +1,120 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvDropDownList - a single-select dropdown (trigger button + portalled list,
4
+ * no typing). Parity: Smart `smart-drop-down-list`. Controlled via `value` +
5
+ * `onChange`. Popover escapes scroll clipping via popover.ts.
6
+ *
7
+ * One styled renderer over the headless `createDropdownList` core (state +
8
+ * keyboard + ARIA); only portal/measure/scroll render concerns live here.
9
+ */
10
+ import { anchoredRect, portalToBody, type AnchoredRect } from './popover'
11
+ import type { ListOption } from './list-option'
12
+ import { createDropdownList } from './createDropdownList.svelte'
13
+
14
+ type Props = {
15
+ options: ReadonlyArray<ListOption>
16
+ value?: string | number | null
17
+ onChange?: (value: string | number) => void
18
+ placeholder?: string
19
+ disabled?: boolean
20
+ name?: string
21
+ size?: 'sm' | 'md' | 'lg'
22
+ ariaLabel?: string
23
+ autoOpen?: boolean
24
+ }
25
+
26
+ let { options, value = null, onChange, placeholder = 'Select…', disabled = false, name, size = 'md', ariaLabel, autoOpen = false }: Props = $props()
27
+
28
+ let triggerEl = $state<HTMLButtonElement | null>(null)
29
+ let panelEl = $state<HTMLDivElement | null>(null)
30
+ let rect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false })
31
+
32
+ const ddl = createDropdownList({
33
+ options: () => options,
34
+ value: () => value,
35
+ onChange: (v) => onChange?.(v),
36
+ disabled: () => disabled,
37
+ ariaLabel: () => ariaLabel,
38
+ focusTrigger: () => triggerEl?.focus(),
39
+ })
40
+
41
+ const selected = $derived(ddl.selected)
42
+
43
+ function updatePos() {
44
+ if (!triggerEl) return
45
+ rect = anchoredRect(triggerEl.getBoundingClientRect(), { estimatedHeight: Math.min(options.length, 8) * 34 + 8 })
46
+ }
47
+
48
+ $effect(() => {
49
+ if (!ddl.open) return
50
+ updatePos()
51
+ const rp = () => updatePos()
52
+ window.addEventListener('scroll', rp, true); window.addEventListener('resize', rp)
53
+ const od = (e: PointerEvent) => { const t = e.target as Node | null; if (t && (triggerEl?.contains(t) || panelEl?.contains(t))) return; ddl.close() }
54
+ document.addEventListener('pointerdown', od, true)
55
+ return () => { window.removeEventListener('scroll', rp, true); window.removeEventListener('resize', rp); document.removeEventListener('pointerdown', od, true) }
56
+ })
57
+ // Keep the active option scrolled into view (render concern).
58
+ $effect(() => {
59
+ if (!ddl.open) return
60
+ const i = ddl.activeIndex
61
+ queueMicrotask(() => panelEl?.querySelector<HTMLElement>(`[data-idx="${i}"]`)?.scrollIntoView({ block: 'nearest' }))
62
+ })
63
+ function focusOpen(node: HTMLButtonElement) { if (autoOpen) { node.focus(); ddl.openPanel() } }
64
+ </script>
65
+
66
+ <button
67
+ bind:this={triggerEl}
68
+ class="sv-ddl sv-ddl--{size}"
69
+ class:is-open={ddl.open}
70
+ class:is-disabled={disabled}
71
+ {...ddl.triggerProps()}
72
+ use:focusOpen
73
+ >
74
+ <span class="sv-ddl__value" class:is-placeholder={!selected}>{selected?.label ?? placeholder}</span>
75
+ <svg class="sv-ddl__chev" viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
76
+ </button>
77
+
78
+ {#if ddl.open}
79
+ <div bind:this={panelEl} class="sv-ddl__panel" use:portalToBody style:position="fixed" style:top={`${rect.top}px`} style:left={`${rect.left}px`} style:min-width={`${rect.width}px`} {...ddl.listboxProps()}>
80
+ {#each options as opt, i (opt.value)}
81
+ <!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
82
+ <div
83
+ class="sv-ddl__opt"
84
+ class:is-active={ddl.isActive(i)}
85
+ class:is-selected={ddl.isSelected(opt)}
86
+ class:is-disabled={opt.disabled}
87
+ {...ddl.optionProps(i)}
88
+ >{opt.label}</div>
89
+ {/each}
90
+ </div>
91
+ {/if}
92
+ {#if name}<input type="hidden" {name} value={value ?? ''} />{/if}
93
+
94
+ <style>
95
+ .sv-ddl {
96
+ --_accent: var(--sg-accent, #2563eb);
97
+ display: inline-flex; align-items: center; justify-content: space-between; gap: 8px; width: 200px;
98
+ background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); font: inherit; text-align: left;
99
+ border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
100
+ padding: 0 10px; cursor: pointer;
101
+ }
102
+ .sv-ddl--sm { height: 28px; font-size: 12px; }
103
+ .sv-ddl--md { height: 34px; font-size: 13px; }
104
+ .sv-ddl--lg { height: 40px; font-size: 15px; }
105
+ .sv-ddl.is-open, .sv-ddl:focus-visible { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); outline: none; }
106
+ .sv-ddl.is-disabled { opacity: 0.6; cursor: not-allowed; }
107
+ .sv-ddl__value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
108
+ .sv-ddl__value.is-placeholder { color: var(--sg-muted, #94a3b8); }
109
+ .sv-ddl__chev { color: var(--sg-muted, #64748b); flex: none; }
110
+
111
+ :global(.sv-ddl__panel) {
112
+ z-index: 2147483647; max-height: 288px; overflow-y: auto; padding: 4px;
113
+ background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
114
+ border: 1px solid var(--sg-border, #e2e8f0); border-radius: 10px; box-shadow: 0 16px 48px -12px rgba(15,23,42,0.35);
115
+ }
116
+ :global(.sv-ddl__opt) { padding: 7px 10px; border-radius: 6px; cursor: pointer; font-size: 13px; }
117
+ :global(.sv-ddl__opt.is-active) { background: var(--sg-row-hover-bg, #f1f5f9); }
118
+ :global(.sv-ddl__opt.is-selected) { color: var(--sg-accent, #2563eb); font-weight: 600; }
119
+ :global(.sv-ddl__opt.is-disabled) { opacity: 0.4; cursor: not-allowed; }
120
+ </style>
@@ -0,0 +1,15 @@
1
+ import type { ListOption } from './list-option';
2
+ type Props = {
3
+ options: ReadonlyArray<ListOption>;
4
+ value?: string | number | null;
5
+ onChange?: (value: string | number) => void;
6
+ placeholder?: string;
7
+ disabled?: boolean;
8
+ name?: string;
9
+ size?: 'sm' | 'md' | 'lg';
10
+ ariaLabel?: string;
11
+ autoOpen?: boolean;
12
+ };
13
+ declare const SvDropDownList: import("svelte").Component<Props, {}, "">;
14
+ type SvDropDownList = ReturnType<typeof SvDropDownList>;
15
+ export default SvDropDownList;
@@ -0,0 +1,147 @@
1
+ <script lang="ts" module>
2
+ export type FormFieldType =
3
+ | 'text' | 'email' | 'tel' | 'textarea' | 'number' | 'password'
4
+ | 'select' | 'checkbox' | 'switch' | 'date' | 'color' | 'rating'
5
+
6
+ export type FormField = {
7
+ name: string
8
+ label: string
9
+ type?: FormFieldType
10
+ required?: boolean
11
+ placeholder?: string
12
+ options?: Array<{ value: string | number; label: string }>
13
+ /** Return an error message, or null/undefined when valid. */
14
+ validate?: (value: any, values: Record<string, any>) => string | null | undefined
15
+ /** Span full width in the grid. */
16
+ full?: boolean
17
+ }
18
+ </script>
19
+
20
+ <script lang="ts">
21
+ /**
22
+ * SvForm - a schema-driven form that renders the SvGrid UI-kit controls with
23
+ * labels, required + custom validation, and a submit handler. Parity: Smart
24
+ * `smart-form`. Emits `onSubmit(values)` only when valid; `onChange` on edits.
25
+ */
26
+ import SvNumberInput from './SvNumberInput.svelte'
27
+ import SvPasswordInput from './SvPasswordInput.svelte'
28
+ import SvDropDownList from './SvDropDownList.svelte'
29
+ import SvCheckBox from './SvCheckBox.svelte'
30
+ import SvSwitchButton from './SvSwitchButton.svelte'
31
+ import SvDateTimePicker from './SvDateTimePicker.svelte'
32
+ import SvColorInput from './SvColorInput.svelte'
33
+ import SvRating from './SvRating.svelte'
34
+ import SvButton from './SvButton.svelte'
35
+
36
+ type Props = {
37
+ fields: ReadonlyArray<FormField>
38
+ initial?: Record<string, any>
39
+ onSubmit?: (values: Record<string, any>) => void
40
+ onChange?: (values: Record<string, any>) => void
41
+ submitLabel?: string
42
+ /** Columns in the responsive field grid. */
43
+ columns?: number
44
+ disabled?: boolean
45
+ }
46
+
47
+ let { fields, initial = {}, onSubmit, onChange, submitLabel = 'Submit', columns = 1, disabled = false }: Props = $props()
48
+
49
+ let values = $state<Record<string, any>>({})
50
+ let errors = $state<Record<string, string>>({})
51
+ let touched = $state<Record<string, boolean>>({})
52
+ let seededForm = false
53
+ $effect(() => { if (!seededForm) { seededForm = true; values = { ...initial } } })
54
+
55
+ function setValue(name: string, v: any) {
56
+ values = { ...values, [name]: v }
57
+ onChange?.(values)
58
+ if (touched[name]) validateField(name)
59
+ }
60
+
61
+ function validateField(name: string): boolean {
62
+ const field = fields.find((f) => f.name === name)
63
+ if (!field) return true
64
+ const v = values[name]
65
+ let err: string | null | undefined = null
66
+ if (field.required && (v == null || v === '' || (Array.isArray(v) && !v.length))) err = `${field.label} is required`
67
+ else if (field.validate) err = field.validate(v, values)
68
+ const next = { ...errors }
69
+ if (err) next[name] = err
70
+ else delete next[name]
71
+ errors = next
72
+ return !err
73
+ }
74
+
75
+ function submit(e: Event) {
76
+ e.preventDefault()
77
+ let ok = true
78
+ const t: Record<string, boolean> = {}
79
+ for (const f of fields) { t[f.name] = true; if (!validateField(f.name)) ok = false }
80
+ touched = t
81
+ if (ok) onSubmit?.({ ...values })
82
+ }
83
+
84
+ const dateVal = (v: any): Date | null => (v instanceof Date ? v : v ? new Date(v) : null)
85
+ </script>
86
+
87
+ <form class="sv-form" style:--cols={columns} onsubmit={submit} novalidate>
88
+ <div class="sv-form__grid">
89
+ {#each fields as field (field.name)}
90
+ {@const type = field.type ?? 'text'}
91
+ <div class="sv-form__field" class:is-full={field.full || columns === 1} class:has-error={errors[field.name]}>
92
+ {#if type !== 'checkbox' && type !== 'switch'}
93
+ <label class="sv-form__label" for={`f-${field.name}`}>
94
+ {field.label}{#if field.required}<span class="sv-form__req" aria-hidden="true"> *</span>{/if}
95
+ </label>
96
+ {/if}
97
+
98
+ {#if type === 'textarea'}
99
+ <textarea id={`f-${field.name}`} class="sv-form__control sv-form__textarea" rows="3" {disabled} placeholder={field.placeholder} value={values[field.name] ?? ''} oninput={(e) => setValue(field.name, e.currentTarget.value)} onblur={() => { touched[field.name] = true; validateField(field.name) }}></textarea>
100
+ {:else if type === 'number'}
101
+ <SvNumberInput value={values[field.name] ?? null} {disabled} onChange={(v) => setValue(field.name, v)} />
102
+ {:else if type === 'password'}
103
+ <SvPasswordInput value={values[field.name] ?? ''} {disabled} onChange={(v) => setValue(field.name, v)} />
104
+ {:else if type === 'select'}
105
+ <SvDropDownList options={field.options ?? []} value={values[field.name] ?? null} {disabled} placeholder={field.placeholder} onChange={(v) => setValue(field.name, v)} />
106
+ {:else if type === 'checkbox'}
107
+ <SvCheckBox checked={!!values[field.name]} {disabled} onChange={(v) => setValue(field.name, v)}>{field.label}</SvCheckBox>
108
+ {:else if type === 'switch'}
109
+ <div class="sv-form__inline"><SvSwitchButton checked={!!values[field.name]} {disabled} onChange={(v) => setValue(field.name, v)} ariaLabel={field.label} /><span>{field.label}</span></div>
110
+ {:else if type === 'date'}
111
+ <SvDateTimePicker value={dateVal(values[field.name])} dropDownDisplayMode="calendar" formatString="yyyy-MM-dd" {disabled} onChange={(v) => setValue(field.name, v)} />
112
+ {:else if type === 'color'}
113
+ <SvColorInput value={values[field.name] ?? '#3b82f6'} {disabled} onChange={(v) => setValue(field.name, v)} />
114
+ {:else if type === 'rating'}
115
+ <SvRating value={values[field.name] ?? 0} {disabled} onChange={(v) => setValue(field.name, v)} />
116
+ {:else}
117
+ <input id={`f-${field.name}`} class="sv-form__control" type={type} {disabled} placeholder={field.placeholder} value={values[field.name] ?? ''} oninput={(e) => setValue(field.name, e.currentTarget.value)} onblur={() => { touched[field.name] = true; validateField(field.name) }} />
118
+ {/if}
119
+
120
+ {#if errors[field.name]}<span class="sv-form__error" role="alert">{errors[field.name]}</span>{/if}
121
+ </div>
122
+ {/each}
123
+ </div>
124
+ <div class="sv-form__actions">
125
+ <SvButton type="submit" {disabled}>{submitLabel}</SvButton>
126
+ </div>
127
+ </form>
128
+
129
+ <style>
130
+ .sv-form { --_accent: var(--sg-accent, #2563eb); width: 100%; }
131
+ .sv-form__grid { display: grid; grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr)); gap: 14px 18px; }
132
+ .sv-form__field { display: flex; flex-direction: column; gap: 5px; }
133
+ .sv-form__field.is-full { grid-column: 1 / -1; }
134
+ .sv-form__label { font-size: 12.5px; font-weight: 600; color: var(--sg-fg, #0f172a); }
135
+ .sv-form__req { color: var(--sg-danger, #dc2626); }
136
+ .sv-form__control {
137
+ width: 100%; box-sizing: border-box; height: 34px; padding: 0 10px; font: inherit; font-size: 13px;
138
+ background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
139
+ border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px); outline: none;
140
+ }
141
+ .sv-form__textarea { height: auto; padding: 8px 10px; resize: vertical; }
142
+ .sv-form__control:focus { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
143
+ .sv-form__field.has-error .sv-form__control { border-color: var(--sg-danger, #dc2626); }
144
+ .sv-form__inline { display: flex; align-items: center; gap: 8px; font-size: 13px; }
145
+ .sv-form__error { font-size: 11.5px; color: var(--sg-danger, #dc2626); }
146
+ .sv-form__actions { margin-top: 16px; }
147
+ </style>
@@ -0,0 +1,29 @@
1
+ export type FormFieldType = 'text' | 'email' | 'tel' | 'textarea' | 'number' | 'password' | 'select' | 'checkbox' | 'switch' | 'date' | 'color' | 'rating';
2
+ export type FormField = {
3
+ name: string;
4
+ label: string;
5
+ type?: FormFieldType;
6
+ required?: boolean;
7
+ placeholder?: string;
8
+ options?: Array<{
9
+ value: string | number;
10
+ label: string;
11
+ }>;
12
+ /** Return an error message, or null/undefined when valid. */
13
+ validate?: (value: any, values: Record<string, any>) => string | null | undefined;
14
+ /** Span full width in the grid. */
15
+ full?: boolean;
16
+ };
17
+ type Props = {
18
+ fields: ReadonlyArray<FormField>;
19
+ initial?: Record<string, any>;
20
+ onSubmit?: (values: Record<string, any>) => void;
21
+ onChange?: (values: Record<string, any>) => void;
22
+ submitLabel?: string;
23
+ /** Columns in the responsive field grid. */
24
+ columns?: number;
25
+ disabled?: boolean;
26
+ };
27
+ declare const SvForm: import("svelte").Component<Props, {}, "">;
28
+ type SvForm = ReturnType<typeof SvForm>;
29
+ export default SvForm;