@svgrid/grid 3.0.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.
- package/CHANGELOG.md +116 -88
- package/README.md +199 -199
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +181 -181
- package/dist/SvAutoComplete.svelte +169 -169
- package/dist/SvAvatar.svelte +75 -75
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +102 -102
- package/dist/SvCircularProgress.svelte +109 -109
- package/dist/SvColorInput.svelte +181 -181
- package/dist/SvComboBox.svelte +279 -279
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +163 -163
- package/dist/SvDrawer.svelte +254 -254
- package/dist/SvDropDownList.svelte +378 -378
- package/dist/SvDurationInput.svelte +126 -126
- package/dist/SvField.svelte +293 -293
- package/dist/SvForm.svelte +437 -437
- package/dist/SvGridCellEditor.svelte +748 -744
- package/dist/SvGridChart.svelte +1716 -1716
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +728 -728
- package/dist/SvGridSelect.svelte +270 -270
- package/dist/SvGroupCell.svelte +116 -116
- package/dist/SvListBox.svelte +334 -334
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMultiSelect.svelte +293 -293
- package/dist/SvNumberInput.svelte +172 -172
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +133 -133
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvProgress.svelte +116 -116
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRating.svelte +112 -112
- package/dist/SvResult.svelte +73 -73
- package/dist/SvRichText.svelte +211 -211
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +203 -203
- package/dist/SvSwitchButton.svelte +108 -108
- package/dist/SvTagsInput.svelte +115 -115
- package/dist/SvTextInput.svelte +147 -147
- package/dist/SvTimePicker.svelte +245 -245
- package/dist/SvToaster.svelte +159 -159
- package/dist/SvToggleButton.svelte +85 -85
- package/dist/SvTooltip.svelte +161 -161
- package/dist/SvTour.svelte +208 -208
- package/dist/SvTree.svelte +444 -444
- package/dist/SvTreeSelect.svelte +239 -239
- package/dist/cdn/{GridMenus-DMihUtma.js → GridMenus-CYzOvmNQ.js} +3 -3
- package/dist/cdn/{GridMenus-nmNDj1a3.js → GridMenus-DFB6iwF8.js} +3 -3
- package/dist/cdn/{SvDateRangeInput-DflbiP7N.js → SvDateRangeInput-CaOuMs8O.js} +1 -1
- package/dist/cdn/{SvDateRangeInput-Bh0A0JkF.js → SvDateRangeInput-DMLKmGEc.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-Bivn8dAP.js → SvDateTimePicker-CCbDZNZB.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-BWwpfB_o.js → SvDateTimePicker-sonaH0oh.js} +1 -1
- package/dist/cdn/{SvGridCellEditor-Ba7rY3eu.js → SvGridCellEditor-BWnTi2N7.js} +2 -2
- package/dist/cdn/{SvGridCellEditor-AXL8cHGO.js → SvGridCellEditor-CjEJMJKc.js} +2 -2
- package/dist/cdn/{SvGridChart-CwhFz7GV.js → SvGridChart-BEJmNNx9.js} +1 -1
- package/dist/cdn/{SvGridChart-Bs2GIR2Q.js → SvGridChart-BwVos976.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-1WXuStzM.js → SvGridChartPanel-BOknOkrP.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-e5_nqNP9.js → SvGridChartPanel-D2PjII4O.js} +1 -1
- package/dist/cdn/{SvGridChartView-SmPW10dI.js → SvGridChartView-00LPUHL1.js} +1 -1
- package/dist/cdn/{SvGridChartView-eSPuJE6g.js → SvGridChartView-BhUEJ5ki.js} +1 -1
- package/dist/cdn/{SvGridDropdown-B1ZcLpgs.js → SvGridDropdown-D0VdjeR8.js} +1 -1
- package/dist/cdn/{SvGridDropdown-CtdbHcIS.js → SvGridDropdown-D13MtJ2j.js} +1 -1
- package/dist/cdn/{date-format-DZT7T1wf.js → date-format-BNii4zeD.js} +2 -2
- package/dist/cdn/{date-format-CcP1tafP.js → date-format-BnnHlqGw.js} +2 -2
- package/dist/cdn/{src-B_YS5AOc.js → src-C8QjSaq-.js} +42 -44
- package/dist/cdn/{src-DYXXpuSk.js → src-D0hO2eYN.js} +41 -43
- package/dist/cdn/svgrid.js +7 -7
- package/dist/cdn/svgrid.svelte-external.js +7 -7
- package/dist/chart-export.js +8 -8
- package/dist/editing.js +22 -13
- package/package.json +11 -11
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +181 -181
- package/src/SvAutoComplete.svelte +169 -169
- package/src/SvAvatar.svelte +75 -75
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +102 -102
- package/src/SvCircularProgress.svelte +109 -109
- package/src/SvColorInput.svelte +181 -181
- package/src/SvComboBox.svelte +279 -279
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +163 -163
- package/src/SvDrawer.svelte +254 -254
- package/src/SvDropDownList.svelte +378 -378
- package/src/SvDurationInput.svelte +126 -126
- package/src/SvField.svelte +293 -293
- package/src/SvForm.svelte +437 -437
- package/src/SvForm.test.ts +411 -411
- package/src/SvGrid.types.ts +2092 -2092
- package/src/SvGridCellEditor.svelte +748 -744
- package/src/SvGridChart.svelte +1716 -1716
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +728 -728
- package/src/SvGridSelect.svelte +270 -270
- package/src/SvGroupCell.svelte +116 -116
- package/src/SvListBox.svelte +334 -334
- package/src/SvMaskedInput.svelte +122 -122
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -97
- package/src/SvMenuList.svelte +146 -146
- package/src/SvMultiSelect.svelte +293 -293
- package/src/SvNumberInput.svelte +172 -172
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +133 -133
- package/src/SvPopover.svelte +197 -197
- package/src/SvProgress.svelte +116 -116
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRating.svelte +112 -112
- package/src/SvResult.svelte +73 -73
- package/src/SvRichText.svelte +211 -211
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +203 -203
- package/src/SvSwitchButton.svelte +108 -108
- package/src/SvTagsInput.svelte +115 -115
- package/src/SvTextInput.svelte +147 -147
- package/src/SvTimePicker.svelte +245 -245
- package/src/SvToaster.svelte +159 -159
- package/src/SvToaster.test.ts +95 -95
- package/src/SvToggleButton.svelte +85 -85
- package/src/SvTooltip.svelte +161 -161
- package/src/SvTour.svelte +208 -208
- package/src/SvTree.svelte +444 -444
- package/src/SvTreeSelect.svelte +239 -239
- package/src/a11y/dismissable.test.ts +119 -119
- package/src/a11y/dismissable.ts +114 -114
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/ai.test.ts +502 -502
- package/src/ai.ts +1419 -1419
- package/src/build-api.coverage.test.ts +633 -633
- package/src/build-api.ts +846 -846
- package/src/builtin-editors.grid.test.ts +83 -83
- package/src/cell-formatting.ts +171 -171
- package/src/cell-render.test.ts +513 -513
- package/src/cell-render.ts +496 -496
- package/src/cell-values.ts +148 -148
- package/src/chart-export.ts +202 -202
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2321 -2321
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/column-groups.ts +78 -78
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1865 -1865
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCombobox.svelte.ts +191 -191
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +168 -168
- package/src/createForm.svelte.ts +386 -386
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createListbox.svelte.ts +250 -250
- package/src/createMenu.svelte.ts +224 -224
- package/src/createPopoverSelect.svelte.ts +213 -213
- package/src/createSlider.svelte.ts +191 -191
- package/src/createTooltip.svelte.ts +144 -144
- package/src/createTree.svelte.ts +322 -322
- package/src/datetime/date-core.ts +206 -206
- package/src/datetime/date-restrict.ts +61 -61
- package/src/datetime/timezone.ts +135 -135
- package/src/dock-manager-model.ts +596 -596
- package/src/dock-model.ts +374 -374
- package/src/editing.test.ts +974 -974
- package/src/editing.ts +618 -609
- package/src/editor-contract.ts +171 -171
- package/src/editor-registry.grid.test.ts +144 -144
- package/src/editor-registry.ts +122 -122
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +601 -601
- package/src/filter-operators.ts +160 -160
- package/src/filtering/excel-filters.ts +325 -325
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/group-display.test.ts +167 -167
- package/src/group-display.ts +200 -200
- package/src/headless.ts +87 -87
- package/src/js-scroller.svelte.ts +173 -173
- package/src/keyboard-handlers.ts +270 -270
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -49
- package/src/list-nav.ts +29 -29
- package/src/list-option.test.ts +56 -56
- package/src/list-option.ts +179 -179
- package/src/menus.ts +597 -597
- package/src/merge-objects.ts +48 -48
- package/src/overlays.test.ts +90 -90
- package/src/positioning.ts +268 -268
- package/src/render-component.ts +28 -28
- package/src/row-drag.test.ts +401 -401
- package/src/row-drag.ts +419 -419
- package/src/row-resize.test.ts +524 -524
- package/src/row-resize.ts +228 -228
- package/src/scheduler-ical.ts +181 -181
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +873 -873
- package/src/selection.test.ts +885 -885
- package/src/server-data-source.test.ts +383 -383
- package/src/server-data-source.ts +469 -469
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +488 -488
- package/src/spreadsheet.ts +312 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/summaries.ts +113 -113
- package/src/svgrid-wrapper.types.ts +563 -563
- package/src/svgrid.async-editor-options.test.ts +273 -273
- package/src/svgrid.auto-row-height.test.ts +204 -204
- package/src/svgrid.behavior.test.ts +910 -910
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.comments-autocomplete.test.ts +127 -127
- package/src/svgrid.context-menu.test.ts +147 -147
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-depth.test.ts +163 -163
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +382 -377
- package/src/svgrid.filter-menu-scroll.test.ts +112 -112
- package/src/svgrid.grand-total.test.ts +188 -188
- package/src/svgrid.group-display-mode.test.ts +171 -171
- package/src/svgrid.group-footers.test.ts +121 -121
- package/src/svgrid.group-pagination.test.ts +153 -153
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.tree-data.test.ts +186 -186
- package/src/svgrid.wrapper.test.ts +63 -63
- package/src/svgriddropdown.async-panel.svelte.test.ts +195 -195
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +288 -288
- package/src/toast-store.svelte.ts +250 -250
- package/src/toast-store.test.ts +147 -147
- package/src/tree-row-model.test.ts +168 -168
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- package/src/ui-localization.test.ts +113 -113
- package/src/ui-range.test.ts +70 -70
- package/src/ui-selection.test.ts +155 -155
- package/src/ui-tier1.test.ts +142 -142
- package/src/virtual.test.ts +88 -88
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- 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
|
+
}
|