@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,409 @@
1
+ <script lang="ts" module>
2
+ // Public types now live with the headless core and are re-exported here so the
3
+ // component's module entry point (and @svgrid/grid) keeps the same surface.
4
+ export type { CalendarValue, CalendarPreset, CalendarAnimation, DisplayMode } from './createCalendar.svelte'
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ /**
9
+ * SvCalendar - a themeable, accessible month/year/decade calendar. It is a thin
10
+ * styled renderer over the headless `createCalendar` core (the same split as
11
+ * `createSvGrid` / <SvGrid>): the core owns the reactive state + interaction +
12
+ * ARIA (built on the framework-free ./datetime engine), while this component
13
+ * keeps the render-only concerns - the WAAPI view-change animation, mouse-wheel
14
+ * navigation, and DOM refs. All visuals come from the grid's `--sg-*` tokens.
15
+ *
16
+ * Parity target: Smart `smart-calendar` (selectionModes, min/max, restricted /
17
+ * important dates, week numbers, firstDayOfWeek, drill navigation, keyboard).
18
+ */
19
+ import { untrack } from 'svelte'
20
+ import {
21
+ createCalendar,
22
+ type CalendarValue,
23
+ type CalendarPreset,
24
+ type CalendarAnimation,
25
+ type DisplayMode,
26
+ } from './createCalendar.svelte'
27
+ import type { SelectionMode } from './datetime/date-selection'
28
+ import type { RestrictOptions } from './datetime/date-restrict'
29
+ import type { DateLike } from './datetime/date-core'
30
+
31
+ type NameFormat = 'narrow' | 'short' | 'long'
32
+
33
+ type Props = {
34
+ /** Selected value(s). Single Date for one/zeroOrOne, array for multi modes. */
35
+ value?: CalendarValue
36
+ /** Fires with the full selected-day list on every change. */
37
+ onChange?: (dates: Date[]) => void
38
+ /** Fires when the visible month/year/decade page changes. */
39
+ onNavigate?: (viewDate: Date, displayMode: DisplayMode) => void
40
+ selectionMode?: SelectionMode
41
+ min?: DateLike | null
42
+ max?: DateLike | null
43
+ /** Non-selectable dates (list or predicate). */
44
+ restrictedDates?: RestrictOptions['restrictedDates']
45
+ /** Highlighted (but still selectable) dates. */
46
+ importantDates?: ReadonlyArray<DateLike> | ((d: Date) => boolean) | null
47
+ /** 0 = Sunday .. 6 = Saturday. */
48
+ firstDayOfWeek?: number
49
+ weeks?: number
50
+ weekNumbers?: boolean
51
+ /** Number of month panels side by side. */
52
+ months?: number
53
+ hideDayNames?: boolean
54
+ hideOtherMonthDays?: boolean
55
+ dayNameFormat?: NameFormat
56
+ monthNameFormat?: NameFormat
57
+ /** Show the Today / Clear footer. */
58
+ footer?: boolean
59
+ disabled?: boolean
60
+ readonly?: boolean
61
+ locale?: string
62
+ name?: string
63
+ /** Which drill level to open on. */
64
+ displayMode?: DisplayMode
65
+ /** Animate month/year navigation + drill (opt-in). `true` = 'slide'. This is
66
+ * an explicit opt-in, so it plays regardless of the OS "reduce motion"
67
+ * setting - pass 'none' (or gate it yourself) to suppress it. */
68
+ animate?: boolean | CalendarAnimation
69
+ /** Change the visible month/year/decade with the mouse wheel over the grid. */
70
+ wheelNavigation?: boolean
71
+ /** Per-day tooltip text (shown as the native title on the day button). */
72
+ dateTooltip?: (date: Date) => string | null | undefined
73
+ /** One-click shortcuts (e.g. Today, Last 7 days) shown in a side rail. */
74
+ presets?: ReadonlyArray<CalendarPreset>
75
+ }
76
+
77
+ let {
78
+ value = null,
79
+ onChange,
80
+ onNavigate,
81
+ selectionMode = 'one',
82
+ min = null,
83
+ max = null,
84
+ restrictedDates = null,
85
+ importantDates = null,
86
+ firstDayOfWeek = 0,
87
+ weeks = 6,
88
+ weekNumbers = false,
89
+ months = 1,
90
+ hideDayNames = false,
91
+ hideOtherMonthDays = false,
92
+ dayNameFormat = 'short',
93
+ monthNameFormat = 'long',
94
+ footer = false,
95
+ disabled = false,
96
+ readonly = false,
97
+ locale = typeof navigator !== 'undefined' ? navigator.language : 'en-US',
98
+ name,
99
+ displayMode = 'month',
100
+ animate = false,
101
+ wheelNavigation = false,
102
+ dateTooltip,
103
+ presets,
104
+ }: Props = $props()
105
+
106
+ // The headless core owns all reactive state, selection, navigation, keyboard
107
+ // and ARIA. Reactive inputs are passed as getters; callbacks as closures.
108
+ const cal = createCalendar({
109
+ value: () => value,
110
+ onChange: (d) => onChange?.(d),
111
+ onNavigate: (v, m) => onNavigate?.(v, m),
112
+ selectionMode: () => selectionMode,
113
+ min: () => min,
114
+ max: () => max,
115
+ restrictedDates: () => restrictedDates,
116
+ importantDates: () => importantDates,
117
+ firstDayOfWeek: () => firstDayOfWeek,
118
+ weeks: () => weeks,
119
+ months: () => months,
120
+ displayMode: () => displayMode,
121
+ disabled: () => disabled,
122
+ readonly: () => readonly,
123
+ locale: () => locale,
124
+ dayNameFormat: () => dayNameFormat,
125
+ monthNameFormat: () => monthNameFormat,
126
+ dateTooltip: () => dateTooltip,
127
+ })
128
+
129
+ // --- View-change animation (framework-native Web Animations API) -----------
130
+ // A render concern kept out of the core: we animate a wrapper element instead
131
+ // of re-keying the grid, so day cells stay mounted (keyboard focus +
132
+ // interactivity are preserved). The core exposes `navToken` (bumps on each
133
+ // navigation) + `navDir` (why it changed); an effect replays a short keyframe
134
+ // on the new content.
135
+ const animKind = $derived<CalendarAnimation>(animate === true ? 'slide' : animate || 'none')
136
+ let animEl = $state<HTMLElement | undefined>()
137
+ let animPrimed = false // skip the very first (mount) run
138
+ // Note: `animate` is an explicit opt-in, so we honor it even under the OS
139
+ // "reduce motion" setting. Pass animate="none" (or gate it yourself on
140
+ // prefers-reduced-motion) if you want it suppressed for that audience.
141
+ function playAnim() {
142
+ if (animKind === 'none' || !animEl || typeof animEl.animate !== 'function') return
143
+ const slide = animKind === 'slide'
144
+ // A month grid barely changes shape between months, so the movement has to be
145
+ // pronounced to read as a transition. Slide the new content a clear distance +
146
+ // fade; drills zoom. A mid-keyframe holds the offset briefly for legibility.
147
+ const D = 34
148
+ const navDir = cal.navDir
149
+ const from =
150
+ !slide ? { opacity: 0, transform: 'scale(0.98)' }
151
+ : navDir === 'next' ? { transform: `translateX(${D}px)`, opacity: 0 }
152
+ : navDir === 'prev' ? { transform: `translateX(-${D}px)`, opacity: 0 }
153
+ : navDir === 'drillDown' ? { transform: 'scale(0.85)', opacity: 0 }
154
+ : navDir === 'drillUp' ? { transform: 'scale(1.14)', opacity: 0 }
155
+ : { opacity: 0 }
156
+ animEl.animate([from, { transform: 'none', opacity: 1 }], {
157
+ duration: slide && (navDir === 'next' || navDir === 'prev') ? 300 : 220,
158
+ easing: 'cubic-bezier(0.16, 1, 0.3, 1)',
159
+ })
160
+ }
161
+ $effect(() => {
162
+ cal.navToken // re-run after each navigation's DOM update
163
+ if (!animPrimed) { animPrimed = true; return }
164
+ untrack(playAnim)
165
+ })
166
+
167
+ // Mouse-wheel navigation (opt-in). Non-passive so we can prevent page scroll.
168
+ function wheelNav(node: HTMLElement) {
169
+ const onWheel = (e: WheelEvent) => {
170
+ if (!wheelNavigation || disabled) return
171
+ e.preventDefault()
172
+ cal.step(e.deltaY > 0 || e.deltaX > 0 ? 1 : -1)
173
+ }
174
+ node.addEventListener('wheel', onWheel, { passive: false })
175
+ return { destroy: () => node.removeEventListener('wheel', onWheel) }
176
+ }
177
+ </script>
178
+
179
+ <div
180
+ class="sv-cal"
181
+ class:sv-cal--disabled={disabled}
182
+ class:sv-cal--with-presets={presets && presets.length}
183
+ class:sv-cal--band={selectionMode === 'range' || selectionMode === 'week'}
184
+ role="group"
185
+ aria-label="Calendar"
186
+ aria-disabled={disabled}
187
+ >
188
+ {#if presets && presets.length}
189
+ <div class="sv-cal__presets" role="group" aria-label="Shortcuts">
190
+ {#each presets as p, i (i)}
191
+ <button class="sv-cal__preset" {...cal.presetProps(p)}>{p.label}</button>
192
+ {/each}
193
+ </div>
194
+ {/if}
195
+
196
+ <div class="sv-cal__main" use:wheelNav>
197
+ <div class="sv-cal__header">
198
+ <button class="sv-cal__nav" {...cal.navProps(-1)}>
199
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6" /></svg>
200
+ </button>
201
+ <button class="sv-cal__title" {...cal.titleProps()}>{cal.title}</button>
202
+ <button class="sv-cal__nav" {...cal.navProps(1)}>
203
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6" /></svg>
204
+ </button>
205
+ </div>
206
+
207
+ <div class="sv-cal__view" bind:this={animEl}>
208
+ {#if cal.mode === 'month'}
209
+ <div class="sv-cal__panels" {...cal.gridProps()}>
210
+ {#each cal.panels as panel (panel.month.getTime())}
211
+ <div class="sv-cal__panel">
212
+ {#if months > 1}<div class="sv-cal__panel-title">{panel.title}</div>{/if}
213
+ {#if !hideDayNames}
214
+ <div class="sv-cal__weekdays" role="row">
215
+ {#if weekNumbers}<span class="sv-cal__wk-head" aria-hidden="true">#</span>{/if}
216
+ {#each cal.weekdayHeaders as wh (wh.weekday)}
217
+ <span class="sv-cal__weekday" role="columnheader">{wh.label}</span>
218
+ {/each}
219
+ </div>
220
+ {/if}
221
+ {#each panel.matrix as week, wi (wi)}
222
+ <div class="sv-cal__week" role="row">
223
+ {#if weekNumbers}<span class="sv-cal__wk" aria-hidden="true">{week[0]?.week}</span>{/if}
224
+ {#each week as cell (cell.date.getTime())}
225
+ {@const st = cal.dayState(cell.date, panel.month)}
226
+ {#if st.outside && hideOtherMonthDays}
227
+ <span class="sv-cal__day sv-cal__day--empty" role="gridcell" aria-hidden="true"></span>
228
+ {:else}
229
+ <button
230
+ class="sv-cal__day"
231
+ class:is-selected={st.selected}
232
+ class:is-today={st.today}
233
+ class:is-outside={st.outside}
234
+ class:is-important={st.important}
235
+ class:is-preview={st.preview}
236
+ class:is-focused={st.focused}
237
+ {...cal.dayProps(cell, panel.month)}
238
+ >{cell.date.getDate()}</button>
239
+ {/if}
240
+ {/each}
241
+ </div>
242
+ {/each}
243
+ </div>
244
+ {/each}
245
+ </div>
246
+ {:else if cal.mode === 'year'}
247
+ <div class="sv-cal__grid-mode" {...cal.gridProps()}>
248
+ {#each cal.yearCells as yc (yc.index)}
249
+ <button
250
+ class="sv-cal__cell"
251
+ class:is-today={yc.current}
252
+ class:is-selected={yc.selected}
253
+ {...cal.monthCellProps(yc.index)}
254
+ >{yc.label}</button>
255
+ {/each}
256
+ </div>
257
+ {:else}
258
+ <div class="sv-cal__grid-mode" {...cal.gridProps()}>
259
+ {#each cal.decadeCells as dc (dc.year)}
260
+ <button
261
+ class="sv-cal__cell"
262
+ class:is-outside={dc.outside}
263
+ class:is-today={dc.current}
264
+ class:is-selected={dc.selected}
265
+ {...cal.yearCellProps(dc.year)}
266
+ >{dc.year}</button>
267
+ {/each}
268
+ </div>
269
+ {/if}
270
+ </div>
271
+
272
+ {#if footer}
273
+ <div class="sv-cal__footer">
274
+ <button type="button" class="sv-cal__foot-btn" onclick={cal.goToday} disabled={disabled}>Today</button>
275
+ {#if cal.canClear}
276
+ <button type="button" class="sv-cal__foot-btn" onclick={cal.clearSelection} disabled={disabled}>Clear</button>
277
+ {/if}
278
+ </div>
279
+ {/if}
280
+ </div>
281
+
282
+ {#if name}<input type="hidden" {name} value={cal.selectedDates.map((d) => d.toISOString()).join(',')} />{/if}
283
+ </div>
284
+
285
+ <style>
286
+ .sv-cal {
287
+ --_accent: var(--sg-accent, #2563eb);
288
+ --_bg: var(--sg-bg, #fff);
289
+ --_fg: var(--sg-fg, #0f172a);
290
+ --_muted: var(--sg-muted, #64748b);
291
+ --_border: var(--sg-border, #e2e8f0);
292
+ --_hover: var(--sg-row-hover-bg, rgba(37, 99, 235, 0.08));
293
+ --_radius: var(--sg-radius, 8px);
294
+ display: inline-flex;
295
+ gap: 10px;
296
+ padding: 10px;
297
+ width: max-content;
298
+ background: var(--_bg);
299
+ color: var(--_fg);
300
+ border: 1px solid var(--_border);
301
+ border-radius: calc(var(--_radius) + 4px);
302
+ font-size: 13px;
303
+ user-select: none;
304
+ }
305
+ .sv-cal--disabled { opacity: 0.55; pointer-events: none; }
306
+ .sv-cal__main { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
307
+ /* Clip so animated content slides in from the edge (not past the border). Day
308
+ focus outlines use outline-offset:-2px, so they stay inside and aren't clipped. */
309
+ .sv-cal__view { display: block; overflow: hidden; }
310
+ /* Quick-shortcut rail (Today / Last 7 days / ...) shown when `presets` is set. */
311
+ .sv-cal__presets {
312
+ display: flex; flex-direction: column; gap: 2px;
313
+ padding-right: 10px; border-right: 1px solid var(--_border);
314
+ min-width: 118px; align-self: stretch;
315
+ }
316
+ .sv-cal__preset {
317
+ text-align: left; padding: 6px 10px; font: inherit; font-size: 12px; font-weight: 550;
318
+ background: none; border: 0; border-radius: var(--_radius); color: inherit; cursor: pointer; white-space: nowrap;
319
+ }
320
+ .sv-cal__preset:hover:not(:disabled) { background: var(--_hover); color: var(--_accent); }
321
+ .sv-cal__preset:disabled { opacity: 0.5; cursor: default; }
322
+
323
+ .sv-cal__header { display: flex; align-items: center; gap: 4px; }
324
+ .sv-cal__title {
325
+ flex: 1; text-align: center; font-weight: 650; font-size: 13.5px;
326
+ background: none; border: 0; color: inherit; cursor: pointer;
327
+ padding: 6px 8px; border-radius: var(--_radius);
328
+ }
329
+ .sv-cal__title:hover:not(:disabled) { background: var(--_hover); }
330
+ .sv-cal__title:disabled { cursor: default; }
331
+ .sv-cal__nav {
332
+ display: grid; place-items: center; width: 30px; height: 30px;
333
+ background: none; border: 0; color: var(--_muted); cursor: pointer; border-radius: var(--_radius);
334
+ }
335
+ .sv-cal__nav:hover:not(:disabled) { background: var(--_hover); color: var(--_fg); }
336
+ .sv-cal__nav:disabled { opacity: 0.4; cursor: default; }
337
+
338
+ .sv-cal__panels { display: flex; gap: 14px; }
339
+ .sv-cal__panel-title { text-align: center; font-weight: 600; font-size: 12.5px; margin-bottom: 4px; color: var(--_muted); }
340
+ .sv-cal__weekdays, .sv-cal__week {
341
+ display: grid;
342
+ grid-template-columns: repeat(7, 34px);
343
+ }
344
+ .sv-cal__weekdays:has(.sv-cal__wk-head), .sv-cal__week:has(.sv-cal__wk) {
345
+ grid-template-columns: 26px repeat(7, 34px);
346
+ }
347
+ .sv-cal__weekday, .sv-cal__wk-head {
348
+ display: grid; place-items: center; height: 28px;
349
+ font-size: 11px; font-weight: 600; color: var(--_muted); text-transform: uppercase; letter-spacing: 0.02em;
350
+ }
351
+ .sv-cal__wk {
352
+ display: grid; place-items: center; height: 34px;
353
+ font-size: 10.5px; color: var(--_muted); opacity: 0.7;
354
+ }
355
+ .sv-cal__day {
356
+ display: grid; place-items: center; width: 34px; height: 34px; box-sizing: border-box;
357
+ background: none; border: 0; color: inherit; cursor: pointer;
358
+ border-radius: var(--_radius); font-size: 12.5px; position: relative;
359
+ }
360
+ .sv-cal__day--empty { visibility: hidden; }
361
+ /* Hover only NON-selected days with the neutral hover fill; hovering a selected
362
+ day keeps the accent (a bare :hover would swap in the neutral bg while the
363
+ text stays --sg-on-accent -> invisible on themes like shadcn). */
364
+ .sv-cal__day:hover:not(:disabled):not(.is-selected) { background: var(--_hover); }
365
+ .sv-cal__day.is-selected:hover:not(:disabled) { background: color-mix(in srgb, var(--_accent) 86%, var(--_fg)); }
366
+ .sv-cal__day.is-outside { color: var(--_muted); opacity: 0.55; }
367
+ .sv-cal__day.is-today { font-weight: 750; box-shadow: inset 0 0 0 1px var(--_accent); }
368
+ .sv-cal__day.is-important::after {
369
+ content: ''; position: absolute; bottom: 4px; left: 50%; transform: translateX(-50%);
370
+ width: 4px; height: 4px; border-radius: 50%; background: var(--sg-danger, #dc2626);
371
+ }
372
+ .sv-cal__day.is-preview { background: color-mix(in srgb, var(--_accent) 16%, transparent); border-radius: 0; }
373
+ .sv-cal__day.is-selected {
374
+ background: var(--_accent); color: var(--sg-on-accent, #fff); font-weight: 650;
375
+ }
376
+ /* Discrete selection (single / many / ...): inset each selected pill so adjacent
377
+ picked days keep a visible gap. Range / week modes stay a connected band. */
378
+ .sv-cal:not(.sv-cal--band) .sv-cal__day.is-selected { background-clip: content-box; padding: 2px; }
379
+ .sv-cal__day.is-selected.is-important::after { background: var(--sg-on-accent, #fff); }
380
+ .sv-cal__day:disabled { color: var(--_muted); opacity: 0.35; cursor: default; text-decoration: line-through; }
381
+ .sv-cal__day.is-focused:focus-visible,
382
+ .sv-cal__day:focus-visible,
383
+ .sv-cal__cell:focus-visible {
384
+ outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: -2px;
385
+ }
386
+
387
+ .sv-cal__grid-mode {
388
+ display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px;
389
+ min-width: 252px;
390
+ }
391
+ .sv-cal__cell {
392
+ height: 44px; display: grid; place-items: center;
393
+ background: none; border: 0; color: inherit; cursor: pointer;
394
+ border-radius: var(--_radius); font-size: 12.5px;
395
+ }
396
+ .sv-cal__cell:hover:not(:disabled):not(.is-selected) { background: var(--_hover); }
397
+ .sv-cal__cell.is-selected:hover:not(:disabled) { background: color-mix(in srgb, var(--_accent) 86%, var(--_fg)); }
398
+ .sv-cal__cell.is-outside { color: var(--_muted); opacity: 0.5; }
399
+ .sv-cal__cell.is-today { box-shadow: inset 0 0 0 1px var(--_accent); font-weight: 700; }
400
+ .sv-cal__cell.is-selected { background: var(--_accent); color: var(--sg-on-accent, #fff); font-weight: 650; }
401
+
402
+ .sv-cal__footer { display: flex; justify-content: center; gap: 8px; border-top: 1px solid var(--_border); padding-top: 8px; margin-top: 2px; }
403
+ .sv-cal__foot-btn {
404
+ padding: 5px 12px; font: inherit; font-size: 12px; font-weight: 600;
405
+ background: none; border: 1px solid var(--_border); border-radius: var(--_radius);
406
+ color: var(--_accent); cursor: pointer;
407
+ }
408
+ .sv-cal__foot-btn:hover:not(:disabled) { background: var(--_hover); }
409
+ </style>
@@ -0,0 +1,52 @@
1
+ export type { CalendarValue, CalendarPreset, CalendarAnimation, DisplayMode } from './createCalendar.svelte';
2
+ import { type CalendarValue, type CalendarPreset, type CalendarAnimation, type DisplayMode } from './createCalendar.svelte';
3
+ import type { SelectionMode } from './datetime/date-selection';
4
+ import type { RestrictOptions } from './datetime/date-restrict';
5
+ import type { DateLike } from './datetime/date-core';
6
+ type NameFormat = 'narrow' | 'short' | 'long';
7
+ type Props = {
8
+ /** Selected value(s). Single Date for one/zeroOrOne, array for multi modes. */
9
+ value?: CalendarValue;
10
+ /** Fires with the full selected-day list on every change. */
11
+ onChange?: (dates: Date[]) => void;
12
+ /** Fires when the visible month/year/decade page changes. */
13
+ onNavigate?: (viewDate: Date, displayMode: DisplayMode) => void;
14
+ selectionMode?: SelectionMode;
15
+ min?: DateLike | null;
16
+ max?: DateLike | null;
17
+ /** Non-selectable dates (list or predicate). */
18
+ restrictedDates?: RestrictOptions['restrictedDates'];
19
+ /** Highlighted (but still selectable) dates. */
20
+ importantDates?: ReadonlyArray<DateLike> | ((d: Date) => boolean) | null;
21
+ /** 0 = Sunday .. 6 = Saturday. */
22
+ firstDayOfWeek?: number;
23
+ weeks?: number;
24
+ weekNumbers?: boolean;
25
+ /** Number of month panels side by side. */
26
+ months?: number;
27
+ hideDayNames?: boolean;
28
+ hideOtherMonthDays?: boolean;
29
+ dayNameFormat?: NameFormat;
30
+ monthNameFormat?: NameFormat;
31
+ /** Show the Today / Clear footer. */
32
+ footer?: boolean;
33
+ disabled?: boolean;
34
+ readonly?: boolean;
35
+ locale?: string;
36
+ name?: string;
37
+ /** Which drill level to open on. */
38
+ displayMode?: DisplayMode;
39
+ /** Animate month/year navigation + drill (opt-in). `true` = 'slide'. This is
40
+ * an explicit opt-in, so it plays regardless of the OS "reduce motion"
41
+ * setting - pass 'none' (or gate it yourself) to suppress it. */
42
+ animate?: boolean | CalendarAnimation;
43
+ /** Change the visible month/year/decade with the mouse wheel over the grid. */
44
+ wheelNavigation?: boolean;
45
+ /** Per-day tooltip text (shown as the native title on the day button). */
46
+ dateTooltip?: (date: Date) => string | null | undefined;
47
+ /** One-click shortcuts (e.g. Today, Last 7 days) shown in a side rail. */
48
+ presets?: ReadonlyArray<CalendarPreset>;
49
+ };
50
+ declare const SvCalendar: import("svelte").Component<Props, {}, "">;
51
+ type SvCalendar = ReturnType<typeof SvCalendar>;
52
+ export default SvCalendar;
@@ -0,0 +1,82 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvCheckBox - a themed checkbox with an indeterminate state and optional
4
+ * label. Parity: Smart `smart-check-box`. Controlled via `checked` + `onChange`.
5
+ */
6
+ import type { Snippet } from 'svelte'
7
+ import { createCheckbox } from './createCheckbox.svelte'
8
+
9
+ type Props = {
10
+ checked?: boolean
11
+ indeterminate?: boolean
12
+ onChange?: (checked: boolean) => void
13
+ disabled?: boolean
14
+ size?: 'sm' | 'md' | 'lg'
15
+ name?: string
16
+ value?: string
17
+ ariaLabel?: string
18
+ children?: Snippet
19
+ }
20
+
21
+ let {
22
+ checked = false,
23
+ indeterminate = false,
24
+ onChange,
25
+ disabled = false,
26
+ size = 'md',
27
+ name,
28
+ value,
29
+ ariaLabel,
30
+ children,
31
+ }: Props = $props()
32
+
33
+ // The styled checkbox is just a renderer over the headless core. The label
34
+ // fallback depends on `children` (a render concern), so it is computed here.
35
+ const cb = createCheckbox({
36
+ checked: () => checked,
37
+ indeterminate: () => indeterminate,
38
+ onChange: (v) => onChange?.(v),
39
+ disabled: () => disabled,
40
+ ariaLabel: () => ariaLabel ?? (children ? undefined : 'checkbox'),
41
+ })
42
+ </script>
43
+
44
+ <label class="sv-check sv-check--{size}" class:is-disabled={disabled}>
45
+ <button
46
+ class="sv-check__box"
47
+ class:is-checked={checked && !indeterminate}
48
+ class:is-indeterminate={indeterminate}
49
+ {...cb.boxProps()}
50
+ >
51
+ {#if indeterminate}
52
+ <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M4 8h8" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" /></svg>
53
+ {:else if checked}
54
+ <svg viewBox="0 0 16 16" aria-hidden="true"><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>
55
+ {/if}
56
+ </button>
57
+ {#if children}<span class="sv-check__label">{@render children()}</span>{/if}
58
+ {#if name}<input type="hidden" {name} value={checked ? (value ?? 'true') : ''} />{/if}
59
+ </label>
60
+
61
+ <style>
62
+ .sv-check {
63
+ --_accent: var(--sg-accent, #2563eb);
64
+ --_sz: 18px;
65
+ display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none;
66
+ font: inherit; font-size: 13px; color: var(--sg-fg, #0f172a);
67
+ }
68
+ .sv-check--sm { --_sz: 15px; font-size: 12px; }
69
+ .sv-check--lg { --_sz: 22px; font-size: 15px; }
70
+ .sv-check.is-disabled { opacity: 0.55; cursor: not-allowed; }
71
+ .sv-check__box {
72
+ width: var(--_sz); height: var(--_sz); flex: none; padding: 0; cursor: inherit;
73
+ display: grid; place-items: center;
74
+ background: var(--sg-input-bg, #fff); color: var(--sg-on-accent, #fff);
75
+ border: 1.5px solid var(--sg-border, #cbd5e1); border-radius: 5px; transition: background 0.12s, border-color 0.12s;
76
+ }
77
+ .sv-check__box svg { width: 100%; height: 100%; }
78
+ .sv-check__box.is-checked, .sv-check__box.is-indeterminate {
79
+ background: var(--_accent); border-color: var(--_accent);
80
+ }
81
+ .sv-check__box:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: 2px; }
82
+ </style>
@@ -0,0 +1,19 @@
1
+ /**
2
+ * SvCheckBox - a themed checkbox with an indeterminate state and optional
3
+ * label. Parity: Smart `smart-check-box`. Controlled via `checked` + `onChange`.
4
+ */
5
+ import type { Snippet } from 'svelte';
6
+ type Props = {
7
+ checked?: boolean;
8
+ indeterminate?: boolean;
9
+ onChange?: (checked: boolean) => void;
10
+ disabled?: boolean;
11
+ size?: 'sm' | 'md' | 'lg';
12
+ name?: string;
13
+ value?: string;
14
+ ariaLabel?: string;
15
+ children?: Snippet;
16
+ };
17
+ declare const SvCheckBox: import("svelte").Component<Props, {}, "">;
18
+ type SvCheckBox = ReturnType<typeof SvCheckBox>;
19
+ export default SvCheckBox;