@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,201 @@
1
+ <script lang="ts" module>
2
+ export type { TimeValue } from './createTimePicker.svelte'
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ /**
7
+ * SvTimePicker - an analog clock-dial time picker (parity: Smart
8
+ * `smart-time-picker`). 12/24-hour, minute snapping, hour->minute auto-switch,
9
+ * pointer-drag + keyboard. It is a styled renderer over the headless
10
+ * `createTimePicker` core: the core owns the value math, dial geometry, keyboard
11
+ * and ARIA; this component keeps the DOM-bound pointer capture + hit-testing.
12
+ * All visuals come from `--sg-*` tokens so every theme applies for free.
13
+ */
14
+ import { createTimePicker, type TimeValue, type TimeFormat, type TimeSelection } from './createTimePicker.svelte'
15
+
16
+ type Props = {
17
+ /** Date, "HH:MM[:SS]" string, or epoch ms. */
18
+ value?: TimeValue
19
+ /** Fires with a Date (today's date carrying the picked time). */
20
+ onChange?: (value: Date) => void
21
+ format?: TimeFormat
22
+ minuteInterval?: number
23
+ /** After picking an hour, jump the dial to minutes. Default true. */
24
+ autoSwitchToMinutes?: boolean
25
+ /** Show the Now footer button. */
26
+ footer?: boolean
27
+ disabled?: boolean
28
+ readonly?: boolean
29
+ name?: string
30
+ /** Which dial opens first. */
31
+ selection?: TimeSelection
32
+ }
33
+
34
+ let {
35
+ value = null,
36
+ onChange,
37
+ format = '24-hour',
38
+ minuteInterval = 1,
39
+ autoSwitchToMinutes = true,
40
+ footer = false,
41
+ disabled = false,
42
+ readonly = false,
43
+ name,
44
+ selection = 'hour',
45
+ }: Props = $props()
46
+
47
+ // The headless core owns all state, geometry, keyboard + ARIA. Reactive inputs
48
+ // are getters; callbacks are closures.
49
+ const tp = createTimePicker({
50
+ value: () => value,
51
+ onChange: (d) => onChange?.(d),
52
+ format: () => format,
53
+ minuteInterval: () => minuteInterval,
54
+ autoSwitchToMinutes: () => autoSwitchToMinutes,
55
+ disabled: () => disabled,
56
+ readonly: () => readonly,
57
+ selection: () => selection,
58
+ })
59
+
60
+ // --- Pointer drag on the dial (DOM-bound; stays in the component) -----------
61
+ // The core exposes SIZE/C + a pure `pointerSelect(angle, dist)`; here we only
62
+ // turn a pointer event into that angle via the live element rect.
63
+ let dialEl: SVGSVGElement | null = null
64
+ let dragging = false
65
+
66
+ function angleFromEvent(e: PointerEvent): { angle: number; dist: number } {
67
+ const rect = dialEl!.getBoundingClientRect()
68
+ const scale = tp.SIZE / rect.width
69
+ const x = (e.clientX - rect.left) * scale - tp.C
70
+ const y = (e.clientY - rect.top) * scale - tp.C
71
+ let deg = (Math.atan2(y, x) * 180) / Math.PI + 90
72
+ if (deg < 0) deg += 360
73
+ return { angle: deg, dist: Math.hypot(x, y) }
74
+ }
75
+
76
+ function onDialPointerDown(e: PointerEvent) {
77
+ if (!tp.isInteractive) return
78
+ dragging = true
79
+ dialEl?.setPointerCapture(e.pointerId)
80
+ const { angle, dist } = angleFromEvent(e)
81
+ tp.pointerSelect(angle, dist)
82
+ }
83
+ function onDialPointerMove(e: PointerEvent) {
84
+ if (!dragging) return
85
+ const { angle, dist } = angleFromEvent(e)
86
+ tp.pointerSelect(angle, dist)
87
+ }
88
+ function onDialPointerUp(e: PointerEvent) {
89
+ if (!dragging) return
90
+ dragging = false
91
+ dialEl?.releasePointerCapture(e.pointerId)
92
+ tp.endPointer()
93
+ }
94
+ </script>
95
+
96
+ <div class="sv-tp" class:sv-tp--disabled={disabled} role="group" aria-label="Time picker">
97
+ <div class="sv-tp__head">
98
+ <button class="sv-tp__seg" class:is-active={tp.selection === 'hour'} {...tp.segProps('hour')}>
99
+ {String(tp.displayHour).padStart(2, '0')}
100
+ </button>
101
+ <span class="sv-tp__colon">:</span>
102
+ <button class="sv-tp__seg" class:is-active={tp.selection === 'minute'} {...tp.segProps('minute')}>
103
+ {tp.mm}
104
+ </button>
105
+ {#if tp.is12}
106
+ <div class="sv-tp__ampm">
107
+ <button class:is-active={!tp.isPm} {...tp.ampmProps(false)}>AM</button>
108
+ <button class:is-active={tp.isPm} {...tp.ampmProps(true)}>PM</button>
109
+ </div>
110
+ {/if}
111
+ </div>
112
+
113
+ <svg
114
+ bind:this={dialEl}
115
+ class="sv-tp__dial"
116
+ viewBox={`0 0 ${tp.SIZE} ${tp.SIZE}`}
117
+ width="100%"
118
+ {...tp.dialProps()}
119
+ onpointerdown={onDialPointerDown}
120
+ onpointermove={onDialPointerMove}
121
+ onpointerup={onDialPointerUp}
122
+ onpointercancel={onDialPointerUp}
123
+ >
124
+ <circle class="sv-tp__face" cx={tp.C} cy={tp.C} r={tp.C - 4} />
125
+ <!-- hand -->
126
+ <line class="sv-tp__hand" x1={tp.C} y1={tp.C} x2={tp.handEnd.x} y2={tp.handEnd.y} />
127
+ <circle class="sv-tp__hub" cx={tp.C} cy={tp.C} r="3" />
128
+ <circle class="sv-tp__knob" cx={tp.handEnd.x} cy={tp.handEnd.y} r="15" />
129
+ {#if tp.selection === 'hour'}
130
+ {#each tp.hourTicks as t (t.ring + '-' + t.value)}
131
+ <text class="sv-tp__num" class:is-active={tp.isActiveHour(t)} class:is-inner={t.ring === 'inner'} x={t.x} y={t.y} dominant-baseline="central" text-anchor="middle">{t.label}</text>
132
+ {/each}
133
+ {:else}
134
+ {#each tp.minuteTicks as t (t.value)}
135
+ <text class="sv-tp__num" class:is-active={t.value === tp.minutes} x={t.x} y={t.y} dominant-baseline="central" text-anchor="middle">{t.label}</text>
136
+ {/each}
137
+ {/if}
138
+ </svg>
139
+
140
+ {#if footer}
141
+ <div class="sv-tp__footer">
142
+ <button class="sv-tp__now" {...tp.nowProps()}>Now</button>
143
+ </div>
144
+ {/if}
145
+
146
+ {#if name}<input type="hidden" {name} value={`${String(tp.hours).padStart(2, '0')}:${tp.mm}`} />{/if}
147
+ </div>
148
+
149
+ <style>
150
+ .sv-tp {
151
+ --_accent: var(--sg-accent, #2563eb);
152
+ --_bg: var(--sg-bg, #fff);
153
+ --_fg: var(--sg-fg, #0f172a);
154
+ --_muted: var(--sg-muted, #64748b);
155
+ --_border: var(--sg-border, #e2e8f0);
156
+ --_face: var(--sg-header-bg, #f1f5f9);
157
+ --_radius: var(--sg-radius, 8px);
158
+ display: inline-flex; flex-direction: column; gap: 10px; align-items: center;
159
+ width: 260px; padding: 12px;
160
+ background: var(--_bg); color: var(--_fg);
161
+ border: 1px solid var(--_border); border-radius: calc(var(--_radius) + 4px);
162
+ user-select: none;
163
+ }
164
+ .sv-tp--disabled { opacity: 0.55; pointer-events: none; }
165
+
166
+ .sv-tp__head { display: flex; align-items: center; gap: 2px; font-variant-numeric: tabular-nums; }
167
+ .sv-tp__seg {
168
+ font-size: 30px; font-weight: 700; line-height: 1;
169
+ background: none; border: 0; color: var(--_muted); cursor: pointer;
170
+ padding: 2px 6px; border-radius: var(--_radius);
171
+ }
172
+ .sv-tp__seg.is-active { color: var(--_accent); background: color-mix(in srgb, var(--_accent) 12%, transparent); }
173
+ .sv-tp__colon { font-size: 28px; font-weight: 700; color: var(--_muted); }
174
+ .sv-tp__ampm { display: flex; flex-direction: column; margin-left: 8px; gap: 2px; }
175
+ .sv-tp__ampm button {
176
+ font-size: 11px; font-weight: 700; padding: 3px 8px;
177
+ background: none; border: 1px solid var(--_border); border-radius: 6px; color: var(--_muted); cursor: pointer;
178
+ }
179
+ .sv-tp__ampm button.is-active { background: var(--_accent); color: var(--sg-on-accent, #fff); border-color: var(--_accent); }
180
+
181
+ .sv-tp__dial { touch-action: none; cursor: pointer; outline: none; display: block; width: 100%; height: auto; aspect-ratio: 1; }
182
+ .sv-tp__dial:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: 2px; border-radius: 50%; }
183
+ .sv-tp__face { fill: var(--_face); }
184
+ .sv-tp__hand { stroke: var(--_accent); stroke-width: 2; }
185
+ .sv-tp__hub { fill: var(--_accent); }
186
+ .sv-tp__knob { fill: var(--_accent); opacity: 0.9; }
187
+ .sv-tp__num {
188
+ font-size: 13px; fill: var(--_fg); pointer-events: none;
189
+ font-family: inherit;
190
+ }
191
+ .sv-tp__num.is-inner { font-size: 11px; fill: var(--_muted); }
192
+ .sv-tp__num.is-active { fill: var(--sg-on-accent, #fff); font-weight: 700; }
193
+
194
+ .sv-tp__footer { width: 100%; display: flex; justify-content: center; border-top: 1px solid var(--_border); padding-top: 8px; }
195
+ .sv-tp__now {
196
+ padding: 5px 14px; font: inherit; font-size: 12px; font-weight: 600;
197
+ background: none; border: 1px solid var(--_border); border-radius: var(--_radius);
198
+ color: var(--_accent); cursor: pointer;
199
+ }
200
+ .sv-tp__now:hover { background: color-mix(in srgb, var(--_accent) 10%, transparent); }
201
+ </style>
@@ -0,0 +1,30 @@
1
+ export type { TimeValue } from './createTimePicker.svelte';
2
+ /**
3
+ * SvTimePicker - an analog clock-dial time picker (parity: Smart
4
+ * `smart-time-picker`). 12/24-hour, minute snapping, hour->minute auto-switch,
5
+ * pointer-drag + keyboard. It is a styled renderer over the headless
6
+ * `createTimePicker` core: the core owns the value math, dial geometry, keyboard
7
+ * and ARIA; this component keeps the DOM-bound pointer capture + hit-testing.
8
+ * All visuals come from `--sg-*` tokens so every theme applies for free.
9
+ */
10
+ import { type TimeValue, type TimeFormat, type TimeSelection } from './createTimePicker.svelte';
11
+ type Props = {
12
+ /** Date, "HH:MM[:SS]" string, or epoch ms. */
13
+ value?: TimeValue;
14
+ /** Fires with a Date (today's date carrying the picked time). */
15
+ onChange?: (value: Date) => void;
16
+ format?: TimeFormat;
17
+ minuteInterval?: number;
18
+ /** After picking an hour, jump the dial to minutes. Default true. */
19
+ autoSwitchToMinutes?: boolean;
20
+ /** Show the Now footer button. */
21
+ footer?: boolean;
22
+ disabled?: boolean;
23
+ readonly?: boolean;
24
+ name?: string;
25
+ /** Which dial opens first. */
26
+ selection?: TimeSelection;
27
+ };
28
+ declare const SvTimePicker: import("svelte").Component<Props, {}, "">;
29
+ type SvTimePicker = ReturnType<typeof SvTimePicker>;
30
+ export default SvTimePicker;
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvToggleButton - a button with a pressed on/off state (aria-pressed).
4
+ * Parity: Smart `smart-toggle-button`. Controlled via `pressed` + `onChange`.
5
+ */
6
+ import type { Snippet } from 'svelte'
7
+ import { createToggle } from './createToggle.svelte'
8
+
9
+ type Props = {
10
+ pressed?: boolean
11
+ onChange?: (pressed: boolean) => void
12
+ disabled?: boolean
13
+ size?: 'sm' | 'md' | 'lg'
14
+ ariaLabel?: string
15
+ children?: Snippet
16
+ }
17
+
18
+ let { pressed = false, onChange, disabled = false, size = 'md', ariaLabel, children }: Props = $props()
19
+
20
+ // The styled toggle is just a renderer over the headless core.
21
+ const t = createToggle({
22
+ pressed: () => pressed,
23
+ onChange: (v) => onChange?.(v),
24
+ disabled: () => disabled,
25
+ ariaLabel: () => ariaLabel,
26
+ })
27
+ </script>
28
+
29
+ <button
30
+ class="sv-toggle sv-toggle--{size}"
31
+ class:is-pressed={pressed}
32
+ {...t.buttonProps()}
33
+ >
34
+ {#if children}{@render children()}{/if}
35
+ </button>
36
+
37
+ <style>
38
+ .sv-toggle {
39
+ --_accent: var(--sg-accent, #2563eb);
40
+ --_border: var(--sg-border, #cbd5e1);
41
+ --_radius: var(--sg-radius, 8px);
42
+ display: inline-flex; align-items: center; justify-content: center; gap: 6px;
43
+ font: inherit; font-weight: 600; line-height: 1;
44
+ background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
45
+ border: 1px solid var(--_border); border-radius: var(--_radius); cursor: pointer; user-select: none;
46
+ transition: background 0.12s, border-color 0.12s, color 0.12s;
47
+ }
48
+ .sv-toggle--sm { padding: 5px 10px; font-size: 12px; }
49
+ .sv-toggle--md { padding: 8px 13px; font-size: 13px; }
50
+ .sv-toggle--lg { padding: 11px 17px; font-size: 15px; }
51
+ .sv-toggle:hover:not([disabled]):not(.is-pressed) { background: var(--sg-row-hover-bg, #f1f5f9); }
52
+ .sv-toggle.is-pressed {
53
+ background: var(--_accent); color: var(--sg-on-accent, #fff); border-color: var(--_accent);
54
+ }
55
+ .sv-toggle[disabled] { opacity: 0.55; cursor: not-allowed; }
56
+ .sv-toggle:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: 2px; }
57
+ </style>
@@ -0,0 +1,16 @@
1
+ /**
2
+ * SvToggleButton - a button with a pressed on/off state (aria-pressed).
3
+ * Parity: Smart `smart-toggle-button`. Controlled via `pressed` + `onChange`.
4
+ */
5
+ import type { Snippet } from 'svelte';
6
+ type Props = {
7
+ pressed?: boolean;
8
+ onChange?: (pressed: boolean) => void;
9
+ disabled?: boolean;
10
+ size?: 'sm' | 'md' | 'lg';
11
+ ariaLabel?: string;
12
+ children?: Snippet;
13
+ };
14
+ declare const SvToggleButton: import("svelte").Component<Props, {}, "">;
15
+ type SvToggleButton = ReturnType<typeof SvToggleButton>;
16
+ export default SvToggleButton;
@@ -0,0 +1,131 @@
1
+ <script lang="ts" module>
2
+ export type { TreeNode } from './createTree.svelte'
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ /**
7
+ * SvTree - a WAI-ARIA tree view. Expand/collapse, single-select highlight, and
8
+ * optional cascading tri-state checkboxes. Parity: Smart `smart-tree`. Keyboard:
9
+ * up/down move, left collapse/parent, right expand/child, Enter/Space select.
10
+ *
11
+ * The behavior (flattening, cascade math, keyboard, ARIA) lives in the headless
12
+ * `createTree` core; this component is just one styled renderer over it.
13
+ */
14
+ import { createTree, type TreeNode } from './createTree.svelte'
15
+
16
+ type Props = {
17
+ nodes: ReadonlyArray<TreeNode>
18
+ /** Selected node id (single-select highlight). */
19
+ selected?: string | null
20
+ onSelect?: (id: string) => void
21
+ /** Initially/controlled expanded ids. */
22
+ expandedIds?: string[]
23
+ onToggle?: (id: string, expanded: boolean) => void
24
+ /** Show cascading checkboxes; `checked` is the set of checked ids. */
25
+ checkable?: boolean
26
+ checked?: string[]
27
+ onCheck?: (ids: string[]) => void
28
+ ariaLabel?: string
29
+ }
30
+
31
+ let {
32
+ nodes,
33
+ selected = null,
34
+ onSelect,
35
+ expandedIds,
36
+ onToggle,
37
+ checkable = false,
38
+ checked = [],
39
+ onCheck,
40
+ ariaLabel,
41
+ }: Props = $props()
42
+
43
+ const tree = createTree({
44
+ nodes: () => nodes,
45
+ selected: () => selected,
46
+ onSelect: (id) => onSelect?.(id),
47
+ expandedIds: () => expandedIds,
48
+ onToggle: (id, exp) => onToggle?.(id, exp),
49
+ checkable: () => checkable,
50
+ checked: () => checked,
51
+ onCheck: (ids) => onCheck?.(ids),
52
+ ariaLabel: () => ariaLabel,
53
+ })
54
+
55
+ // DOM focus movement is a render concern: follow the core's roving focus row.
56
+ let treeEl: HTMLDivElement | null = null
57
+ let lastFocusTick = 0
58
+ $effect(() => {
59
+ if (tree.focusTick !== lastFocusTick) {
60
+ lastFocusTick = tree.focusTick
61
+ const i = tree.activeIndex
62
+ queueMicrotask(() => treeEl?.querySelector<HTMLElement>(`[data-row="${i}"]`)?.focus())
63
+ }
64
+ })
65
+ </script>
66
+
67
+ <div bind:this={treeEl} class="sv-tree" {...tree.treeProps()}>
68
+ {#each tree.rows as item (item.node.id)}
69
+ {@const cs = checkable ? tree.checkStateOf(item.node) : 'unchecked'}
70
+ <div
71
+ class="sv-tree__row"
72
+ class:is-selected={item.node.id === selected}
73
+ class:is-disabled={item.node.disabled}
74
+ style:padding-left={`${item.depth * 18 + 6}px`}
75
+ {...tree.itemProps(item)}
76
+ >
77
+ <button
78
+ type="button"
79
+ class="sv-tree__twist"
80
+ class:is-open={item.open}
81
+ class:is-leaf={!item.hasChildren}
82
+ tabindex="-1"
83
+ aria-hidden={!item.hasChildren}
84
+ onclick={(e) => { e.stopPropagation(); tree.toggleExpand(item.node) }}
85
+ >
86
+ {#if item.hasChildren}
87
+ <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>
88
+ {/if}
89
+ </button>
90
+ {#if checkable}
91
+ <button type="button" class="sv-tree__check is-{cs}" tabindex="-1" aria-hidden="true" onclick={(e) => { e.stopPropagation(); tree.toggleCheck(item.node) }}>
92
+ {#if cs === 'checked'}<svg viewBox="0 0 16 16"><path d="M3.5 8.5l3 3 6-6.5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" /></svg>
93
+ {:else if cs === 'indeterminate'}<svg viewBox="0 0 16 16"><path d="M4 8h8" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" /></svg>{/if}
94
+ </button>
95
+ {/if}
96
+ <span class="sv-tree__label">{item.node.label}</span>
97
+ </div>
98
+ {/each}
99
+ </div>
100
+
101
+ <style>
102
+ .sv-tree {
103
+ --_accent: var(--sg-accent, #2563eb);
104
+ width: 260px; padding: 4px; user-select: none;
105
+ background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
106
+ border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 8px);
107
+ font-size: 13px;
108
+ }
109
+ .sv-tree__row {
110
+ display: flex; align-items: center; gap: 4px; height: 30px; padding-right: 8px;
111
+ border-radius: 6px; cursor: pointer; outline: none;
112
+ }
113
+ .sv-tree__row:hover:not(.is-disabled) { background: var(--sg-row-hover-bg, #f1f5f9); }
114
+ .sv-tree__row.is-selected { background: color-mix(in srgb, var(--_accent) 15%, transparent); color: var(--_accent); font-weight: 600; }
115
+ .sv-tree__row.is-disabled { opacity: 0.45; cursor: not-allowed; }
116
+ .sv-tree__row:focus-visible { box-shadow: inset 0 0 0 2px var(--sg-focus-ring, var(--_accent)); }
117
+ .sv-tree__twist {
118
+ display: grid; place-items: center; width: 18px; height: 18px; flex: none;
119
+ background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; transition: transform 0.12s;
120
+ }
121
+ .sv-tree__twist.is-open { transform: rotate(90deg); }
122
+ .sv-tree__twist.is-leaf { visibility: hidden; }
123
+ .sv-tree__check {
124
+ display: grid; place-items: center; width: 16px; height: 16px; flex: none; padding: 0;
125
+ border: 1.5px solid var(--sg-border, #cbd5e1); border-radius: 4px; background: var(--sg-input-bg, #fff);
126
+ color: var(--sg-on-accent, #fff); cursor: pointer;
127
+ }
128
+ .sv-tree__check.is-checked, .sv-tree__check.is-indeterminate { background: var(--_accent); border-color: var(--_accent); }
129
+ .sv-tree__check svg { width: 100%; height: 100%; }
130
+ .sv-tree__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
131
+ </style>
@@ -0,0 +1,27 @@
1
+ export type { TreeNode } from './createTree.svelte';
2
+ /**
3
+ * SvTree - a WAI-ARIA tree view. Expand/collapse, single-select highlight, and
4
+ * optional cascading tri-state checkboxes. Parity: Smart `smart-tree`. Keyboard:
5
+ * up/down move, left collapse/parent, right expand/child, Enter/Space select.
6
+ *
7
+ * The behavior (flattening, cascade math, keyboard, ARIA) lives in the headless
8
+ * `createTree` core; this component is just one styled renderer over it.
9
+ */
10
+ import { type TreeNode } from './createTree.svelte';
11
+ type Props = {
12
+ nodes: ReadonlyArray<TreeNode>;
13
+ /** Selected node id (single-select highlight). */
14
+ selected?: string | null;
15
+ onSelect?: (id: string) => void;
16
+ /** Initially/controlled expanded ids. */
17
+ expandedIds?: string[];
18
+ onToggle?: (id: string, expanded: boolean) => void;
19
+ /** Show cascading checkboxes; `checked` is the set of checked ids. */
20
+ checkable?: boolean;
21
+ checked?: string[];
22
+ onCheck?: (ids: string[]) => void;
23
+ ariaLabel?: string;
24
+ };
25
+ declare const SvTree: import("svelte").Component<Props, {}, "">;
26
+ type SvTree = ReturnType<typeof SvTree>;
27
+ export default SvTree;
package/dist/build-api.js CHANGED
@@ -1,8 +1,35 @@
1
1
  import "./sv-grid-scrollbar";
2
- import { isGroupRow, columnDefMatchesId, } from "./cell-values";
2
+ import { projectGridRows, serializeDelimited, serializeJson, serializeMarkdown, downloadTextFile, copyTextToClipboard, } from "./export-format";
3
+ import { isGroupRow, getColumnAlign, columnDefMatchesId, } from "./cell-values";
3
4
  export function createGridApi(ctx) {
4
5
  function buildApi() {
5
6
  const findColumn = (columnId) => ctx.grid.getAllColumns().find((column) => column.id === columnId);
7
+ // --- Free data export (CSV / TSV / JSON + clipboard) helpers ----------
8
+ // Snapshot the VISIBLE columns (field + label + format + align) so the
9
+ // exporter reproduces on-screen values. Mirrors getColumns()'s visible set.
10
+ const exportColumns = () => ctx.allColumns
11
+ .map((c) => ({
12
+ field: c.columnDef.field,
13
+ header: typeof c.columnDef.header === "string" ? c.columnDef.header : c.id,
14
+ format: c.columnDef.format,
15
+ align: getColumnAlign(c),
16
+ }))
17
+ .filter((c) => !!c.field);
18
+ // Rows for the requested scope: current view / checked / full dataset.
19
+ const exportScopeRows = (scope) => {
20
+ if (scope === "all")
21
+ return ctx.internalData;
22
+ const out = [];
23
+ for (const row of ctx.allRows) {
24
+ if (isGroupRow(row))
25
+ continue;
26
+ if (scope === "selected" && !ctx.rowSelectionState[row.id])
27
+ continue;
28
+ out.push(row.original);
29
+ }
30
+ return out;
31
+ };
32
+ const projectForExport = (scope, columns, rawValues) => projectGridRows(exportScopeRows(scope), exportColumns(), { columns, rawValues });
6
33
  return {
7
34
  getCellValue(rowIndex, columnId) {
8
35
  const row = ctx.internalData[rowIndex];
@@ -281,6 +308,12 @@ export function createGridApi(ctx) {
281
308
  ? c.columnDef.header
282
309
  : c.id,
283
310
  visible,
311
+ // Surface the format + alignment so exporters can reproduce the
312
+ // on-screen display value (currency symbol, date pattern, etc.)
313
+ // instead of dumping raw values. `getColumnAlign` applies the same
314
+ // editorType-based default the body uses.
315
+ format: c.columnDef.format,
316
+ align: getColumnAlign(c),
284
317
  });
285
318
  // Visible columns first, in their current visual order...
286
319
  const out = ctx.allColumns.map((c) => describe(c, true));
@@ -294,6 +327,50 @@ export function createGridApi(ctx) {
294
327
  }
295
328
  return out;
296
329
  },
330
+ // ---- Free data export (CSV / TSV / JSON + clipboard) -----------------
331
+ async exportCsv(options = {}) {
332
+ const { records, fields } = projectForExport(options.rows ?? "displayed", options.columns, options.rawValues);
333
+ const text = await serializeDelimited(records, fields, {
334
+ csv: { delimiter: ",", bom: options.bom ?? true },
335
+ signal: options.signal,
336
+ onProgress: options.onProgress,
337
+ });
338
+ if (options.download !== false)
339
+ downloadTextFile(text, `${options.filename ?? "grid"}.csv`, "text/csv;charset=utf-8");
340
+ return text;
341
+ },
342
+ async exportTsv(options = {}) {
343
+ const { records, fields } = projectForExport(options.rows ?? "displayed", options.columns, options.rawValues);
344
+ const text = await serializeDelimited(records, fields, {
345
+ csv: { delimiter: "\t", bom: options.bom ?? true },
346
+ signal: options.signal,
347
+ onProgress: options.onProgress,
348
+ });
349
+ if (options.download !== false)
350
+ downloadTextFile(text, `${options.filename ?? "grid"}.tsv`, "text/tab-separated-values;charset=utf-8");
351
+ return text;
352
+ },
353
+ async exportJson(options = {}) {
354
+ const { records, fields } = projectForExport(options.rows ?? "displayed", options.columns, options.rawValues);
355
+ const text = await serializeJson(records, fields, {
356
+ signal: options.signal,
357
+ onProgress: options.onProgress,
358
+ });
359
+ if (options.download !== false)
360
+ downloadTextFile(text, `${options.filename ?? "grid"}.json`, "application/json");
361
+ return text;
362
+ },
363
+ async copyToClipboard(options = {}) {
364
+ const { records, fields, align } = projectForExport(options.rows ?? "displayed", options.columns, options.rawValues);
365
+ const format = options.format ?? "tsv";
366
+ const text = format === "markdown"
367
+ ? await serializeMarkdown(records, fields, { align })
368
+ : await serializeDelimited(records, fields, {
369
+ csv: { delimiter: format === "csv" ? "," : "\t", bom: false },
370
+ });
371
+ await copyTextToClipboard(text);
372
+ return text;
373
+ },
297
374
  clearRowSelection() {
298
375
  // Wipe the internal selection map AND emit the change so any
299
376
  // consumer holding a derived `selectedRows` resets too.