@svgrid/grid 2.7.0 → 3.0.1

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 (264) hide show
  1. package/CHANGELOG.md +116 -88
  2. package/README.md +199 -199
  3. package/dist/FlexRender.svelte +96 -96
  4. package/dist/GridFooter.svelte +181 -181
  5. package/dist/SvAutoComplete.svelte +169 -169
  6. package/dist/SvAvatar.svelte +75 -75
  7. package/dist/SvCalendar.svelte +503 -503
  8. package/dist/SvCarousel.svelte +141 -141
  9. package/dist/SvCheckBox.svelte +102 -102
  10. package/dist/SvCircularProgress.svelte +109 -109
  11. package/dist/SvColorInput.svelte +181 -181
  12. package/dist/SvComboBox.svelte +279 -279
  13. package/dist/SvContextMenu.svelte +116 -116
  14. package/dist/SvCountryInput.svelte +163 -163
  15. package/dist/SvDrawer.svelte +254 -254
  16. package/dist/SvDropDownList.svelte +378 -378
  17. package/dist/SvDurationInput.svelte +126 -126
  18. package/dist/SvField.svelte +293 -293
  19. package/dist/SvForm.svelte +437 -437
  20. package/dist/SvGridCellEditor.svelte +748 -744
  21. package/dist/SvGridChart.svelte +1716 -1716
  22. package/dist/SvGridChartPanel.svelte +485 -485
  23. package/dist/SvGridChartView.svelte +70 -70
  24. package/dist/SvGridDropdown.svelte +728 -728
  25. package/dist/SvGridSelect.svelte +270 -270
  26. package/dist/SvGroupCell.svelte +116 -116
  27. package/dist/SvListBox.svelte +334 -334
  28. package/dist/SvMaskedInput.svelte +122 -122
  29. package/dist/SvMenu.svelte +124 -124
  30. package/dist/SvMenuList.svelte +146 -146
  31. package/dist/SvMultiSelect.svelte +293 -293
  32. package/dist/SvNumberInput.svelte +172 -172
  33. package/dist/SvOtpInput.svelte +158 -158
  34. package/dist/SvPasswordInput.svelte +151 -151
  35. package/dist/SvPhoneInput.svelte +133 -133
  36. package/dist/SvPopover.svelte +197 -197
  37. package/dist/SvProgress.svelte +116 -116
  38. package/dist/SvRadioGroup.svelte +107 -107
  39. package/dist/SvRating.svelte +112 -112
  40. package/dist/SvResult.svelte +73 -73
  41. package/dist/SvRichText.svelte +211 -211
  42. package/dist/SvRowGroupPanel.svelte +170 -170
  43. package/dist/SvScrollArea.svelte +61 -61
  44. package/dist/SvSlider.svelte +203 -203
  45. package/dist/SvSwitchButton.svelte +108 -108
  46. package/dist/SvTagsInput.svelte +115 -115
  47. package/dist/SvTextInput.svelte +147 -147
  48. package/dist/SvTimePicker.svelte +245 -245
  49. package/dist/SvToaster.svelte +159 -159
  50. package/dist/SvToggleButton.svelte +85 -85
  51. package/dist/SvTooltip.svelte +161 -161
  52. package/dist/SvTour.svelte +208 -208
  53. package/dist/SvTree.svelte +444 -444
  54. package/dist/SvTreeSelect.svelte +239 -239
  55. package/dist/cdn/{GridMenus-C4oLsJyj.js → GridMenus-CYzOvmNQ.js} +3 -3
  56. package/dist/cdn/{GridMenus-yEivjNwl.js → GridMenus-DFB6iwF8.js} +3 -3
  57. package/dist/cdn/{SvDateRangeInput-DflbiP7N.js → SvDateRangeInput-CaOuMs8O.js} +1 -1
  58. package/dist/cdn/{SvDateRangeInput-Bh0A0JkF.js → SvDateRangeInput-DMLKmGEc.js} +1 -1
  59. package/dist/cdn/{SvDateTimePicker-Bivn8dAP.js → SvDateTimePicker-CCbDZNZB.js} +1 -1
  60. package/dist/cdn/{SvDateTimePicker-BWwpfB_o.js → SvDateTimePicker-sonaH0oh.js} +1 -1
  61. package/dist/cdn/{SvGridCellEditor-Ba7rY3eu.js → SvGridCellEditor-BWnTi2N7.js} +2 -2
  62. package/dist/cdn/{SvGridCellEditor-AXL8cHGO.js → SvGridCellEditor-CjEJMJKc.js} +2 -2
  63. package/dist/cdn/{SvGridChart-CwhFz7GV.js → SvGridChart-BEJmNNx9.js} +1 -1
  64. package/dist/cdn/{SvGridChart-Bs2GIR2Q.js → SvGridChart-BwVos976.js} +1 -1
  65. package/dist/cdn/{SvGridChartPanel-1WXuStzM.js → SvGridChartPanel-BOknOkrP.js} +1 -1
  66. package/dist/cdn/{SvGridChartPanel-e5_nqNP9.js → SvGridChartPanel-D2PjII4O.js} +1 -1
  67. package/dist/cdn/{SvGridChartView-SmPW10dI.js → SvGridChartView-00LPUHL1.js} +1 -1
  68. package/dist/cdn/{SvGridChartView-eSPuJE6g.js → SvGridChartView-BhUEJ5ki.js} +1 -1
  69. package/dist/cdn/{SvGridDropdown-B1ZcLpgs.js → SvGridDropdown-D0VdjeR8.js} +1 -1
  70. package/dist/cdn/{SvGridDropdown-CtdbHcIS.js → SvGridDropdown-D13MtJ2j.js} +1 -1
  71. package/dist/cdn/{date-format-DZT7T1wf.js → date-format-BNii4zeD.js} +2 -2
  72. package/dist/cdn/{date-format-CcP1tafP.js → date-format-BnnHlqGw.js} +2 -2
  73. package/dist/cdn/{row-drag-touch-Ddaa-QeB.js → row-drag-touch-CycRR65n.js} +12 -10
  74. package/dist/cdn/{src-COC0vnpg.js → src-C8QjSaq-.js} +53 -55
  75. package/dist/cdn/{src-pYGV2rMR.js → src-D0hO2eYN.js} +52 -54
  76. package/dist/cdn/svgrid.js +7 -7
  77. package/dist/cdn/svgrid.svelte-external.js +7 -7
  78. package/dist/chart-export.js +8 -8
  79. package/dist/editing.js +22 -13
  80. package/dist/row-drag-touch.d.ts +5 -1
  81. package/dist/row-drag-touch.js +15 -4
  82. package/dist/row-drag.js +7 -3
  83. package/package.json +11 -11
  84. package/src/FlexRender.svelte +96 -96
  85. package/src/GridFooter.svelte +181 -181
  86. package/src/SvAutoComplete.svelte +169 -169
  87. package/src/SvAvatar.svelte +75 -75
  88. package/src/SvCalendar.svelte +503 -503
  89. package/src/SvCalendar.test.ts +226 -226
  90. package/src/SvCarousel.svelte +141 -141
  91. package/src/SvCheckBox.svelte +102 -102
  92. package/src/SvCircularProgress.svelte +109 -109
  93. package/src/SvColorInput.svelte +181 -181
  94. package/src/SvComboBox.svelte +279 -279
  95. package/src/SvContextMenu.svelte +116 -116
  96. package/src/SvCountryInput.svelte +163 -163
  97. package/src/SvDrawer.svelte +254 -254
  98. package/src/SvDropDownList.svelte +378 -378
  99. package/src/SvDurationInput.svelte +126 -126
  100. package/src/SvField.svelte +293 -293
  101. package/src/SvForm.svelte +437 -437
  102. package/src/SvForm.test.ts +411 -411
  103. package/src/SvGrid.types.ts +2092 -2092
  104. package/src/SvGridCellEditor.svelte +748 -744
  105. package/src/SvGridChart.svelte +1716 -1716
  106. package/src/SvGridChartPanel.svelte +485 -485
  107. package/src/SvGridChartView.svelte +70 -70
  108. package/src/SvGridDropdown.svelte +728 -728
  109. package/src/SvGridSelect.svelte +270 -270
  110. package/src/SvGroupCell.svelte +116 -116
  111. package/src/SvListBox.svelte +334 -334
  112. package/src/SvMaskedInput.svelte +122 -122
  113. package/src/SvMenu.svelte +124 -124
  114. package/src/SvMenu.test.ts +97 -97
  115. package/src/SvMenuList.svelte +146 -146
  116. package/src/SvMultiSelect.svelte +293 -293
  117. package/src/SvNumberInput.svelte +172 -172
  118. package/src/SvOtpInput.svelte +158 -158
  119. package/src/SvPasswordInput.svelte +151 -151
  120. package/src/SvPhoneInput.svelte +133 -133
  121. package/src/SvPopover.svelte +197 -197
  122. package/src/SvProgress.svelte +116 -116
  123. package/src/SvRadioGroup.svelte +107 -107
  124. package/src/SvRating.svelte +112 -112
  125. package/src/SvResult.svelte +73 -73
  126. package/src/SvRichText.svelte +211 -211
  127. package/src/SvRowGroupPanel.svelte +170 -170
  128. package/src/SvScrollArea.svelte +61 -61
  129. package/src/SvSlider.svelte +203 -203
  130. package/src/SvSwitchButton.svelte +108 -108
  131. package/src/SvTagsInput.svelte +115 -115
  132. package/src/SvTextInput.svelte +147 -147
  133. package/src/SvTimePicker.svelte +245 -245
  134. package/src/SvToaster.svelte +159 -159
  135. package/src/SvToaster.test.ts +95 -95
  136. package/src/SvToggleButton.svelte +85 -85
  137. package/src/SvTooltip.svelte +161 -161
  138. package/src/SvTour.svelte +208 -208
  139. package/src/SvTree.svelte +444 -444
  140. package/src/SvTreeSelect.svelte +239 -239
  141. package/src/a11y/dismissable.test.ts +119 -119
  142. package/src/a11y/dismissable.ts +114 -114
  143. package/src/a11y.contract.test.ts +49 -49
  144. package/src/a11y.test.ts +59 -59
  145. package/src/a11y.ts +61 -61
  146. package/src/ai.test.ts +502 -502
  147. package/src/ai.ts +1419 -1419
  148. package/src/build-api.coverage.test.ts +633 -633
  149. package/src/build-api.ts +846 -846
  150. package/src/builtin-editors.grid.test.ts +83 -83
  151. package/src/cell-formatting.ts +171 -171
  152. package/src/cell-render.test.ts +513 -513
  153. package/src/cell-render.ts +496 -496
  154. package/src/cell-values.ts +148 -148
  155. package/src/chart-export.ts +202 -202
  156. package/src/chart-view.svelte.ts +36 -36
  157. package/src/chart.ts +2321 -2321
  158. package/src/collaboration.test.ts +104 -104
  159. package/src/collaboration.ts +167 -167
  160. package/src/column-groups.ts +78 -78
  161. package/src/core.performance.test.ts +30 -30
  162. package/src/core.ts +1865 -1865
  163. package/src/createAutocomplete.svelte.ts +132 -132
  164. package/src/createCombobox.svelte.ts +191 -191
  165. package/src/createCountryInput.svelte.ts +157 -157
  166. package/src/createDropdownList.svelte.ts +168 -168
  167. package/src/createForm.svelte.ts +386 -386
  168. package/src/createGrid.svelte.ts +42 -42
  169. package/src/createGrid.test.ts +10 -10
  170. package/src/createGridState.svelte.ts +17 -17
  171. package/src/createListbox.svelte.ts +250 -250
  172. package/src/createMenu.svelte.ts +224 -224
  173. package/src/createPopoverSelect.svelte.ts +213 -213
  174. package/src/createSlider.svelte.ts +191 -191
  175. package/src/createTooltip.svelte.ts +144 -144
  176. package/src/createTree.svelte.ts +322 -322
  177. package/src/datetime/date-core.ts +206 -206
  178. package/src/datetime/date-restrict.ts +61 -61
  179. package/src/datetime/timezone.ts +135 -135
  180. package/src/dock-manager-model.ts +596 -596
  181. package/src/dock-model.ts +374 -374
  182. package/src/editing.test.ts +974 -974
  183. package/src/editing.ts +618 -609
  184. package/src/editor-contract.ts +171 -171
  185. package/src/editor-registry.grid.test.ts +144 -144
  186. package/src/editor-registry.ts +122 -122
  187. package/src/export-data-api.test.ts +126 -126
  188. package/src/export-format.test.ts +107 -107
  189. package/src/export-format.ts +601 -601
  190. package/src/filter-operators.ts +160 -160
  191. package/src/filtering/excel-filters.ts +325 -325
  192. package/src/flex-render.ts +3 -3
  193. package/src/form-field.ts +127 -127
  194. package/src/group-display.test.ts +167 -167
  195. package/src/group-display.ts +200 -200
  196. package/src/headless.ts +87 -87
  197. package/src/js-scroller.svelte.ts +173 -173
  198. package/src/keyboard-handlers.ts +270 -270
  199. package/src/keyboard.test.ts +59 -59
  200. package/src/keyboard.ts +97 -97
  201. package/src/list-nav.test.ts +49 -49
  202. package/src/list-nav.ts +29 -29
  203. package/src/list-option.test.ts +56 -56
  204. package/src/list-option.ts +179 -179
  205. package/src/menus.ts +597 -597
  206. package/src/merge-objects.ts +48 -48
  207. package/src/overlays.test.ts +90 -90
  208. package/src/positioning.ts +268 -268
  209. package/src/render-component.ts +28 -28
  210. package/src/row-drag-touch.ts +226 -211
  211. package/src/row-drag.test.ts +401 -353
  212. package/src/row-drag.ts +419 -415
  213. package/src/row-resize.test.ts +524 -524
  214. package/src/row-resize.ts +228 -228
  215. package/src/scheduler-ical.ts +181 -181
  216. package/src/scheduler-model.test.ts +562 -562
  217. package/src/scheduler-model.ts +873 -873
  218. package/src/selection.test.ts +885 -885
  219. package/src/server-data-source.test.ts +383 -383
  220. package/src/server-data-source.ts +469 -469
  221. package/src/sparkline.test.ts +68 -68
  222. package/src/sparkline.ts +169 -169
  223. package/src/spreadsheet.test.ts +488 -488
  224. package/src/spreadsheet.ts +312 -312
  225. package/src/static-functions.ts +11 -11
  226. package/src/subscribe.ts +38 -38
  227. package/src/summaries.ts +113 -113
  228. package/src/svgrid-wrapper.types.ts +563 -563
  229. package/src/svgrid.async-editor-options.test.ts +273 -273
  230. package/src/svgrid.auto-row-height.test.ts +204 -204
  231. package/src/svgrid.behavior.test.ts +910 -910
  232. package/src/svgrid.charting.test.ts +534 -534
  233. package/src/svgrid.comments-autocomplete.test.ts +127 -127
  234. package/src/svgrid.context-menu.test.ts +147 -147
  235. package/src/svgrid.features.test.ts +157 -157
  236. package/src/svgrid.filter-depth.test.ts +163 -163
  237. package/src/svgrid.filter-menu-listbox.svelte.test.ts +382 -377
  238. package/src/svgrid.filter-menu-scroll.test.ts +112 -112
  239. package/src/svgrid.grand-total.test.ts +188 -188
  240. package/src/svgrid.group-display-mode.test.ts +171 -171
  241. package/src/svgrid.group-footers.test.ts +121 -121
  242. package/src/svgrid.group-pagination.test.ts +153 -153
  243. package/src/svgrid.new-features.wrapper.test.ts +251 -251
  244. package/src/svgrid.tree-data.test.ts +186 -186
  245. package/src/svgrid.wrapper.test.ts +63 -63
  246. package/src/svgriddropdown.async-panel.svelte.test.ts +195 -195
  247. package/src/test-setup.ts +62 -62
  248. package/src/themes/index.ts +288 -288
  249. package/src/toast-store.svelte.ts +250 -250
  250. package/src/toast-store.test.ts +147 -147
  251. package/src/tree-row-model.test.ts +168 -168
  252. package/src/ui-buttons.test.ts +144 -144
  253. package/src/ui-inputs.test.ts +118 -118
  254. package/src/ui-localization.test.ts +113 -113
  255. package/src/ui-range.test.ts +70 -70
  256. package/src/ui-selection.test.ts +155 -155
  257. package/src/ui-tier1.test.ts +142 -142
  258. package/src/virtual.test.ts +88 -88
  259. package/src/virtualization/column-virtualizer.test.ts +27 -27
  260. package/src/virtualization/column-virtualizer.ts +30 -30
  261. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  262. package/src/virtualization/types.ts +30 -30
  263. package/src/virtualization/virtualizer.test.ts +47 -47
  264. package/src/virtualization/virtualizer.ts +322 -322
@@ -1,206 +1,206 @@
1
- /**
2
- * date-core - framework-free date math shared by SvCalendar, SvTimePicker and
3
- * SvDateTimePicker. No Svelte, no DOM: pure functions over the native `Date`
4
- * so it is trivially unit- and mutation-testable and reusable standalone.
5
- *
6
- * Conventions:
7
- * - A "day" is compared by local calendar date (year/month/date), ignoring the
8
- * time-of-day, unless a helper says otherwise.
9
- * - `firstDayOfWeek` is 0-6 (0 = Sunday), matching `Date.getDay()` and Smart's
10
- * `firstDayOfWeek`.
11
- */
12
-
13
- /** Anything the date helpers accept: a `Date`, epoch milliseconds, or a parseable string. */
14
- export type DateLike = Date | number | string
15
-
16
- /** Coerce a Date | epoch-ms | parseable string to a Date, or null if invalid. */
17
- export function toDate(value: DateLike | null | undefined): Date | null {
18
- if (value == null) return null
19
- const d = value instanceof Date ? new Date(value.getTime()) : new Date(value)
20
- return isNaN(d.getTime()) ? null : d
21
- }
22
-
23
- /** A new Date at local midnight of the same calendar day (time stripped). */
24
- export function startOfDay(d: Date): Date {
25
- return new Date(d.getFullYear(), d.getMonth(), d.getDate())
26
- }
27
-
28
- /** First day (midnight) of the month `d` falls in. */
29
- export function startOfMonth(d: Date): Date {
30
- return new Date(d.getFullYear(), d.getMonth(), 1)
31
- }
32
-
33
- /** Last day (midnight) of the month `d` falls in. */
34
- export function endOfMonth(d: Date): Date {
35
- return new Date(d.getFullYear(), d.getMonth() + 1, 0)
36
- }
37
-
38
- /** Number of days in the month `d` falls in. */
39
- export function daysInMonth(d: Date): number {
40
- return endOfMonth(d).getDate()
41
- }
42
-
43
- /**
44
- * Start of the week containing `d`, honoring `firstDayOfWeek` (0-6). Returns a
45
- * local-midnight Date on or before `d`.
46
- */
47
- export function startOfWeek(d: Date, firstDayOfWeek = 0): Date {
48
- const start = startOfDay(d)
49
- const diff = (start.getDay() - firstDayOfWeek + 7) % 7
50
- start.setDate(start.getDate() - diff)
51
- return start
52
- }
53
-
54
- /** True when both dates are the same local calendar day. */
55
- export function isSameDay(a: Date | null, b: Date | null): boolean {
56
- if (!a || !b) return false
57
- return (
58
- a.getFullYear() === b.getFullYear() &&
59
- a.getMonth() === b.getMonth() &&
60
- a.getDate() === b.getDate()
61
- )
62
- }
63
-
64
- /** True when both dates are in the same year+month. */
65
- export function isSameMonth(a: Date | null, b: Date | null): boolean {
66
- if (!a || !b) return false
67
- return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth()
68
- }
69
-
70
- /** -1 / 0 / 1 comparison by calendar day (time-of-day ignored). */
71
- export function compareDay(a: Date, b: Date): number {
72
- const av = startOfDay(a).getTime()
73
- const bv = startOfDay(b).getTime()
74
- return av < bv ? -1 : av > bv ? 1 : 0
75
- }
76
-
77
- /** Add `n` days (can be negative), returning a new Date. */
78
- export function addDays(d: Date, n: number): Date {
79
- const r = new Date(d.getTime())
80
- r.setDate(r.getDate() + n)
81
- return r
82
- }
83
-
84
- /**
85
- * Add `n` calendar months, clamping the day-of-month so 31 Jan + 1 month = 28/29
86
- * Feb rather than spilling into March (matches how calendars paginate).
87
- */
88
- export function addMonths(d: Date, n: number): Date {
89
- const day = d.getDate()
90
- const r = new Date(d.getFullYear(), d.getMonth() + n, 1, d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds())
91
- r.setDate(Math.min(day, daysInMonth(r)))
92
- return r
93
- }
94
-
95
- /** Add `n` years, clamping Feb-29 to Feb-28 on non-leap targets. */
96
- export function addYears(d: Date, n: number): Date {
97
- return addMonths(d, n * 12)
98
- }
99
-
100
- /** Clamp `d` to the inclusive [min, max] range (either bound may be null). */
101
- export function clampDate(d: Date, min: Date | null, max: Date | null): Date {
102
- if (min && d.getTime() < min.getTime()) return new Date(min.getTime())
103
- if (max && d.getTime() > max.getTime()) return new Date(max.getTime())
104
- return d
105
- }
106
-
107
- /**
108
- * ISO 8601 week number (1-53) for `d`. Week 1 is the week containing the first
109
- * Thursday of the year; weeks start Monday. Independent of `firstDayOfWeek`,
110
- * matching Smart's `weekNumbers` column which is always ISO.
111
- */
112
- export function isoWeek(d: Date): number {
113
- // Shift to the Thursday of this week, then count weeks from Jan 1.
114
- const date = startOfDay(d)
115
- const day = (date.getDay() + 6) % 7 // Mon=0..Sun=6
116
- date.setDate(date.getDate() - day + 3) // Thursday of this week
117
- const firstThursday = new Date(date.getFullYear(), 0, 4)
118
- const firstDay = (firstThursday.getDay() + 6) % 7
119
- firstThursday.setDate(firstThursday.getDate() - firstDay + 3)
120
- const diffMs = date.getTime() - firstThursday.getTime()
121
- return 1 + Math.round(diffMs / (7 * 24 * 3600 * 1000))
122
- }
123
-
124
- /**
125
- * The [start, end] years of the decade block that `year` belongs to, as Smart
126
- * renders it: a 12-cell page running from the decade start minus 1 to plus 10
127
- * (one leading + one trailing year for context). We expose the canonical
128
- * decade [Y0, Y0+9] and let the view pad.
129
- */
130
- export function decadeRange(year: number): { start: number; end: number } {
131
- const start = Math.floor(year / 10) * 10
132
- return { start, end: start + 9 }
133
- }
134
-
135
- /** The [start, end] years of the century block (100-year) `year` belongs to. */
136
- export function centuryRange(year: number): { start: number; end: number } {
137
- const start = Math.floor(year / 100) * 100
138
- return { start, end: start + 99 }
139
- }
140
-
141
- /** One cell of a month grid, including the leading and trailing days from adjacent months. */
142
- export type MonthMatrixCell = {
143
- date: Date
144
- /** In the displayed month (vs. leading/trailing days of adjacent months). */
145
- inMonth: boolean
146
- /** ISO week number of the row this cell sits in. */
147
- week: number
148
- }
149
-
150
- /**
151
- * Build a month grid of `weeks` rows x 7 columns starting at `firstDayOfWeek`,
152
- * padded with the trailing days of the previous month and leading days of the
153
- * next so every row is full. `weeks` defaults to 6 (the max any month needs),
154
- * matching Smart's fixed-height calendar.
155
- */
156
- export function monthMatrix(
157
- monthDate: Date,
158
- firstDayOfWeek = 0,
159
- weeks = 6,
160
- ): MonthMatrixCell[][] {
161
- const first = startOfMonth(monthDate)
162
- const gridStart = startOfWeek(first, firstDayOfWeek)
163
- const rows: MonthMatrixCell[][] = []
164
- let cursor = gridStart
165
- for (let r = 0; r < weeks; r++) {
166
- const row: MonthMatrixCell[] = []
167
- const weekNo = isoWeek(cursor)
168
- for (let c = 0; c < 7; c++) {
169
- row.push({
170
- date: cursor,
171
- inMonth: cursor.getMonth() === first.getMonth() && cursor.getFullYear() === first.getFullYear(),
172
- week: weekNo,
173
- })
174
- cursor = addDays(cursor, 1)
175
- }
176
- rows.push(row)
177
- }
178
- return rows
179
- }
180
-
181
- /**
182
- * Ordered weekday indexes (0-6) for a header row starting at `firstDayOfWeek`.
183
- * e.g. firstDayOfWeek=1 -> [1,2,3,4,5,6,0].
184
- */
185
- export function weekdayOrder(firstDayOfWeek = 0): number[] {
186
- return Array.from({ length: 7 }, (_, i) => (firstDayOfWeek + i) % 7)
187
- }
188
-
189
- /** Merge a calendar day with a time-of-day taken from `time`, returning a new Date. */
190
- export function withTime(day: Date, time: Date): Date {
191
- return new Date(
192
- day.getFullYear(),
193
- day.getMonth(),
194
- day.getDate(),
195
- time.getHours(),
196
- time.getMinutes(),
197
- time.getSeconds(),
198
- time.getMilliseconds(),
199
- )
200
- }
201
-
202
- /** Snap a minute value to the nearest lower multiple of `interval` (>=1). */
203
- export function snapMinute(minute: number, interval: number): number {
204
- const step = Math.max(1, Math.floor(interval))
205
- return Math.floor(minute / step) * step
206
- }
1
+ /**
2
+ * date-core - framework-free date math shared by SvCalendar, SvTimePicker and
3
+ * SvDateTimePicker. No Svelte, no DOM: pure functions over the native `Date`
4
+ * so it is trivially unit- and mutation-testable and reusable standalone.
5
+ *
6
+ * Conventions:
7
+ * - A "day" is compared by local calendar date (year/month/date), ignoring the
8
+ * time-of-day, unless a helper says otherwise.
9
+ * - `firstDayOfWeek` is 0-6 (0 = Sunday), matching `Date.getDay()` and Smart's
10
+ * `firstDayOfWeek`.
11
+ */
12
+
13
+ /** Anything the date helpers accept: a `Date`, epoch milliseconds, or a parseable string. */
14
+ export type DateLike = Date | number | string
15
+
16
+ /** Coerce a Date | epoch-ms | parseable string to a Date, or null if invalid. */
17
+ export function toDate(value: DateLike | null | undefined): Date | null {
18
+ if (value == null) return null
19
+ const d = value instanceof Date ? new Date(value.getTime()) : new Date(value)
20
+ return isNaN(d.getTime()) ? null : d
21
+ }
22
+
23
+ /** A new Date at local midnight of the same calendar day (time stripped). */
24
+ export function startOfDay(d: Date): Date {
25
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate())
26
+ }
27
+
28
+ /** First day (midnight) of the month `d` falls in. */
29
+ export function startOfMonth(d: Date): Date {
30
+ return new Date(d.getFullYear(), d.getMonth(), 1)
31
+ }
32
+
33
+ /** Last day (midnight) of the month `d` falls in. */
34
+ export function endOfMonth(d: Date): Date {
35
+ return new Date(d.getFullYear(), d.getMonth() + 1, 0)
36
+ }
37
+
38
+ /** Number of days in the month `d` falls in. */
39
+ export function daysInMonth(d: Date): number {
40
+ return endOfMonth(d).getDate()
41
+ }
42
+
43
+ /**
44
+ * Start of the week containing `d`, honoring `firstDayOfWeek` (0-6). Returns a
45
+ * local-midnight Date on or before `d`.
46
+ */
47
+ export function startOfWeek(d: Date, firstDayOfWeek = 0): Date {
48
+ const start = startOfDay(d)
49
+ const diff = (start.getDay() - firstDayOfWeek + 7) % 7
50
+ start.setDate(start.getDate() - diff)
51
+ return start
52
+ }
53
+
54
+ /** True when both dates are the same local calendar day. */
55
+ export function isSameDay(a: Date | null, b: Date | null): boolean {
56
+ if (!a || !b) return false
57
+ return (
58
+ a.getFullYear() === b.getFullYear() &&
59
+ a.getMonth() === b.getMonth() &&
60
+ a.getDate() === b.getDate()
61
+ )
62
+ }
63
+
64
+ /** True when both dates are in the same year+month. */
65
+ export function isSameMonth(a: Date | null, b: Date | null): boolean {
66
+ if (!a || !b) return false
67
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth()
68
+ }
69
+
70
+ /** -1 / 0 / 1 comparison by calendar day (time-of-day ignored). */
71
+ export function compareDay(a: Date, b: Date): number {
72
+ const av = startOfDay(a).getTime()
73
+ const bv = startOfDay(b).getTime()
74
+ return av < bv ? -1 : av > bv ? 1 : 0
75
+ }
76
+
77
+ /** Add `n` days (can be negative), returning a new Date. */
78
+ export function addDays(d: Date, n: number): Date {
79
+ const r = new Date(d.getTime())
80
+ r.setDate(r.getDate() + n)
81
+ return r
82
+ }
83
+
84
+ /**
85
+ * Add `n` calendar months, clamping the day-of-month so 31 Jan + 1 month = 28/29
86
+ * Feb rather than spilling into March (matches how calendars paginate).
87
+ */
88
+ export function addMonths(d: Date, n: number): Date {
89
+ const day = d.getDate()
90
+ const r = new Date(d.getFullYear(), d.getMonth() + n, 1, d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds())
91
+ r.setDate(Math.min(day, daysInMonth(r)))
92
+ return r
93
+ }
94
+
95
+ /** Add `n` years, clamping Feb-29 to Feb-28 on non-leap targets. */
96
+ export function addYears(d: Date, n: number): Date {
97
+ return addMonths(d, n * 12)
98
+ }
99
+
100
+ /** Clamp `d` to the inclusive [min, max] range (either bound may be null). */
101
+ export function clampDate(d: Date, min: Date | null, max: Date | null): Date {
102
+ if (min && d.getTime() < min.getTime()) return new Date(min.getTime())
103
+ if (max && d.getTime() > max.getTime()) return new Date(max.getTime())
104
+ return d
105
+ }
106
+
107
+ /**
108
+ * ISO 8601 week number (1-53) for `d`. Week 1 is the week containing the first
109
+ * Thursday of the year; weeks start Monday. Independent of `firstDayOfWeek`,
110
+ * matching Smart's `weekNumbers` column which is always ISO.
111
+ */
112
+ export function isoWeek(d: Date): number {
113
+ // Shift to the Thursday of this week, then count weeks from Jan 1.
114
+ const date = startOfDay(d)
115
+ const day = (date.getDay() + 6) % 7 // Mon=0..Sun=6
116
+ date.setDate(date.getDate() - day + 3) // Thursday of this week
117
+ const firstThursday = new Date(date.getFullYear(), 0, 4)
118
+ const firstDay = (firstThursday.getDay() + 6) % 7
119
+ firstThursday.setDate(firstThursday.getDate() - firstDay + 3)
120
+ const diffMs = date.getTime() - firstThursday.getTime()
121
+ return 1 + Math.round(diffMs / (7 * 24 * 3600 * 1000))
122
+ }
123
+
124
+ /**
125
+ * The [start, end] years of the decade block that `year` belongs to, as Smart
126
+ * renders it: a 12-cell page running from the decade start minus 1 to plus 10
127
+ * (one leading + one trailing year for context). We expose the canonical
128
+ * decade [Y0, Y0+9] and let the view pad.
129
+ */
130
+ export function decadeRange(year: number): { start: number; end: number } {
131
+ const start = Math.floor(year / 10) * 10
132
+ return { start, end: start + 9 }
133
+ }
134
+
135
+ /** The [start, end] years of the century block (100-year) `year` belongs to. */
136
+ export function centuryRange(year: number): { start: number; end: number } {
137
+ const start = Math.floor(year / 100) * 100
138
+ return { start, end: start + 99 }
139
+ }
140
+
141
+ /** One cell of a month grid, including the leading and trailing days from adjacent months. */
142
+ export type MonthMatrixCell = {
143
+ date: Date
144
+ /** In the displayed month (vs. leading/trailing days of adjacent months). */
145
+ inMonth: boolean
146
+ /** ISO week number of the row this cell sits in. */
147
+ week: number
148
+ }
149
+
150
+ /**
151
+ * Build a month grid of `weeks` rows x 7 columns starting at `firstDayOfWeek`,
152
+ * padded with the trailing days of the previous month and leading days of the
153
+ * next so every row is full. `weeks` defaults to 6 (the max any month needs),
154
+ * matching Smart's fixed-height calendar.
155
+ */
156
+ export function monthMatrix(
157
+ monthDate: Date,
158
+ firstDayOfWeek = 0,
159
+ weeks = 6,
160
+ ): MonthMatrixCell[][] {
161
+ const first = startOfMonth(monthDate)
162
+ const gridStart = startOfWeek(first, firstDayOfWeek)
163
+ const rows: MonthMatrixCell[][] = []
164
+ let cursor = gridStart
165
+ for (let r = 0; r < weeks; r++) {
166
+ const row: MonthMatrixCell[] = []
167
+ const weekNo = isoWeek(cursor)
168
+ for (let c = 0; c < 7; c++) {
169
+ row.push({
170
+ date: cursor,
171
+ inMonth: cursor.getMonth() === first.getMonth() && cursor.getFullYear() === first.getFullYear(),
172
+ week: weekNo,
173
+ })
174
+ cursor = addDays(cursor, 1)
175
+ }
176
+ rows.push(row)
177
+ }
178
+ return rows
179
+ }
180
+
181
+ /**
182
+ * Ordered weekday indexes (0-6) for a header row starting at `firstDayOfWeek`.
183
+ * e.g. firstDayOfWeek=1 -> [1,2,3,4,5,6,0].
184
+ */
185
+ export function weekdayOrder(firstDayOfWeek = 0): number[] {
186
+ return Array.from({ length: 7 }, (_, i) => (firstDayOfWeek + i) % 7)
187
+ }
188
+
189
+ /** Merge a calendar day with a time-of-day taken from `time`, returning a new Date. */
190
+ export function withTime(day: Date, time: Date): Date {
191
+ return new Date(
192
+ day.getFullYear(),
193
+ day.getMonth(),
194
+ day.getDate(),
195
+ time.getHours(),
196
+ time.getMinutes(),
197
+ time.getSeconds(),
198
+ time.getMilliseconds(),
199
+ )
200
+ }
201
+
202
+ /** Snap a minute value to the nearest lower multiple of `interval` (>=1). */
203
+ export function snapMinute(minute: number, interval: number): number {
204
+ const step = Math.max(1, Math.floor(interval))
205
+ return Math.floor(minute / step) * step
206
+ }
@@ -1,61 +1,61 @@
1
- /**
2
- * date-restrict - predicates for calendar constraints: min/max bounds,
3
- * `restrictedDates` (non-selectable), and `importantDates` (highlighted but
4
- * still selectable). Mirrors Smart's calendar constraint model. Framework-free.
5
- */
6
- import { compareDay, isSameDay, toDate, type DateLike } from './date-core'
7
-
8
- /** Which dates a picker allows: bounds, an explicit disabled set, and disabled weekdays. */
9
- export type RestrictOptions = {
10
- min?: DateLike | null
11
- max?: DateLike | null
12
- /** Individual dates (or predicate) that cannot be selected. */
13
- restrictedDates?: ReadonlyArray<DateLike> | ((d: Date) => boolean) | null
14
- }
15
-
16
- function normalizeList(list: ReadonlyArray<DateLike> | null | undefined): Date[] {
17
- if (!list) return []
18
- const out: Date[] = []
19
- for (const item of list) {
20
- const d = toDate(item)
21
- if (d) out.push(d)
22
- }
23
- return out
24
- }
25
-
26
- /** True when `d` is outside the inclusive [min, max] day range. */
27
- export function isOutOfRange(d: Date, min?: DateLike | null, max?: DateLike | null): boolean {
28
- const lo = toDate(min ?? null)
29
- const hi = toDate(max ?? null)
30
- if (lo && compareDay(d, lo) < 0) return true
31
- if (hi && compareDay(d, hi) > 0) return true
32
- return false
33
- }
34
-
35
- /** True when `d` is explicitly restricted (by list membership or predicate). */
36
- export function isRestricted(
37
- d: Date,
38
- restrictedDates?: ReadonlyArray<DateLike> | ((day: Date) => boolean) | null,
39
- ): boolean {
40
- if (!restrictedDates) return false
41
- if (typeof restrictedDates === 'function') return restrictedDates(d)
42
- return normalizeList(restrictedDates).some((r) => isSameDay(r, d))
43
- }
44
-
45
- /**
46
- * True when `d` cannot be selected: out of [min, max] OR restricted. This is
47
- * the single predicate the calendar/pickers use to disable a day.
48
- */
49
- export function isDisabledDay(d: Date, opts: RestrictOptions): boolean {
50
- return isOutOfRange(d, opts.min, opts.max) || isRestricted(d, opts.restrictedDates)
51
- }
52
-
53
- /** True when `d` is flagged important (highlighted). Never affects selectability. */
54
- export function isImportant(
55
- d: Date,
56
- importantDates?: ReadonlyArray<DateLike> | ((day: Date) => boolean) | null,
57
- ): boolean {
58
- if (!importantDates) return false
59
- if (typeof importantDates === 'function') return importantDates(d)
60
- return normalizeList(importantDates).some((r) => isSameDay(r, d))
61
- }
1
+ /**
2
+ * date-restrict - predicates for calendar constraints: min/max bounds,
3
+ * `restrictedDates` (non-selectable), and `importantDates` (highlighted but
4
+ * still selectable). Mirrors Smart's calendar constraint model. Framework-free.
5
+ */
6
+ import { compareDay, isSameDay, toDate, type DateLike } from './date-core'
7
+
8
+ /** Which dates a picker allows: bounds, an explicit disabled set, and disabled weekdays. */
9
+ export type RestrictOptions = {
10
+ min?: DateLike | null
11
+ max?: DateLike | null
12
+ /** Individual dates (or predicate) that cannot be selected. */
13
+ restrictedDates?: ReadonlyArray<DateLike> | ((d: Date) => boolean) | null
14
+ }
15
+
16
+ function normalizeList(list: ReadonlyArray<DateLike> | null | undefined): Date[] {
17
+ if (!list) return []
18
+ const out: Date[] = []
19
+ for (const item of list) {
20
+ const d = toDate(item)
21
+ if (d) out.push(d)
22
+ }
23
+ return out
24
+ }
25
+
26
+ /** True when `d` is outside the inclusive [min, max] day range. */
27
+ export function isOutOfRange(d: Date, min?: DateLike | null, max?: DateLike | null): boolean {
28
+ const lo = toDate(min ?? null)
29
+ const hi = toDate(max ?? null)
30
+ if (lo && compareDay(d, lo) < 0) return true
31
+ if (hi && compareDay(d, hi) > 0) return true
32
+ return false
33
+ }
34
+
35
+ /** True when `d` is explicitly restricted (by list membership or predicate). */
36
+ export function isRestricted(
37
+ d: Date,
38
+ restrictedDates?: ReadonlyArray<DateLike> | ((day: Date) => boolean) | null,
39
+ ): boolean {
40
+ if (!restrictedDates) return false
41
+ if (typeof restrictedDates === 'function') return restrictedDates(d)
42
+ return normalizeList(restrictedDates).some((r) => isSameDay(r, d))
43
+ }
44
+
45
+ /**
46
+ * True when `d` cannot be selected: out of [min, max] OR restricted. This is
47
+ * the single predicate the calendar/pickers use to disable a day.
48
+ */
49
+ export function isDisabledDay(d: Date, opts: RestrictOptions): boolean {
50
+ return isOutOfRange(d, opts.min, opts.max) || isRestricted(d, opts.restrictedDates)
51
+ }
52
+
53
+ /** True when `d` is flagged important (highlighted). Never affects selectability. */
54
+ export function isImportant(
55
+ d: Date,
56
+ importantDates?: ReadonlyArray<DateLike> | ((day: Date) => boolean) | null,
57
+ ): boolean {
58
+ if (!importantDates) return false
59
+ if (typeof importantDates === 'function') return importantDates(d)
60
+ return normalizeList(importantDates).some((r) => isSameDay(r, d))
61
+ }