@joewinke/jatui 0.41.1 → 0.43.0

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 (75) hide show
  1. package/CLAUDE.md +32 -6
  2. package/THIRD_PARTY_NOTICES.md +14 -0
  3. package/dist/actions/dismissOutside.d.ts +52 -0
  4. package/dist/actions/dismissOutside.js +80 -0
  5. package/dist/components/BookingScheduler.svelte +412 -0
  6. package/dist/components/BookingScheduler.svelte.d.ts +66 -0
  7. package/dist/components/CalendarPicker.svelte +190 -205
  8. package/dist/components/CalendarPicker.svelte.d.ts +19 -1
  9. package/dist/components/DatePicker.svelte +419 -0
  10. package/dist/components/DatePicker.svelte.d.ts +44 -0
  11. package/dist/components/DateRangePicker.svelte +471 -5
  12. package/dist/components/DateRangePicker.svelte.d.ts +32 -3
  13. package/dist/components/EventCalendar.svelte +1324 -0
  14. package/dist/components/EventCalendar.svelte.d.ts +101 -0
  15. package/dist/components/FileDropzone.svelte +165 -3
  16. package/dist/components/FileDropzone.svelte.d.ts +9 -0
  17. package/dist/components/TextInput.svelte +71 -20
  18. package/dist/components/TextInput.svelte.d.ts +7 -0
  19. package/dist/components/ThumbnailGrid.svelte +106 -1
  20. package/dist/components/ThumbnailGrid.svelte.d.ts +7 -0
  21. package/dist/components/TimeSlotPicker.svelte +174 -108
  22. package/dist/components/TimeSlotPicker.svelte.d.ts +27 -0
  23. package/dist/components/basic/Alert.svelte +113 -16
  24. package/dist/components/basic/Alert.svelte.d.ts +17 -1
  25. package/dist/components/basic/Checkbox.svelte +53 -1
  26. package/dist/components/basic/Checkbox.svelte.d.ts +6 -0
  27. package/dist/components/basic/RadioGroup.svelte +59 -3
  28. package/dist/components/basic/RadioGroup.svelte.d.ts +15 -0
  29. package/dist/components/basic/Slider.svelte +257 -9
  30. package/dist/components/basic/Slider.svelte.d.ts +14 -2
  31. package/dist/components/basic/sliderMath.d.ts +29 -0
  32. package/dist/components/basic/sliderMath.js +64 -0
  33. package/dist/components/calendar/CalendarHeader.svelte +98 -0
  34. package/dist/components/calendar/CalendarHeader.svelte.d.ts +24 -0
  35. package/dist/components/calendar/MonthGrid.svelte +361 -0
  36. package/dist/components/calendar/MonthGrid.svelte.d.ts +55 -0
  37. package/dist/components/calendar/calendarGrid.d.ts +219 -0
  38. package/dist/components/calendar/calendarGrid.js +494 -0
  39. package/dist/components/calendar/createCalendarGrid.d.ts +25 -0
  40. package/dist/components/calendar/createCalendarGrid.js +57 -0
  41. package/dist/components/calendar/dayOverlap.d.ts +92 -0
  42. package/dist/components/calendar/dayOverlap.js +147 -0
  43. package/dist/components/calendar/eventCalendar.d.ts +75 -0
  44. package/dist/components/calendar/eventCalendar.js +187 -0
  45. package/dist/components/calendar/index.d.ts +32 -0
  46. package/dist/components/calendar/index.js +25 -0
  47. package/dist/components/calendar/monthPacking.d.ts +97 -0
  48. package/dist/components/calendar/monthPacking.js +254 -0
  49. package/dist/components/calendar/timeSlots.d.ts +57 -0
  50. package/dist/components/calendar/timeSlots.js +163 -0
  51. package/dist/components/field/FieldHint.svelte +29 -0
  52. package/dist/components/field/FieldHint.svelte.d.ts +17 -0
  53. package/dist/components/field/FieldLabel.svelte +100 -0
  54. package/dist/components/field/FieldLabel.svelte.d.ts +29 -0
  55. package/dist/components/form-cards/CheckGlyph.svelte +138 -0
  56. package/dist/components/form-cards/CheckGlyph.svelte.d.ts +29 -0
  57. package/dist/components/form-cards/CheckboxCard.svelte +120 -0
  58. package/dist/components/form-cards/CheckboxCard.svelte.d.ts +33 -0
  59. package/dist/components/form-cards/ChoiceCard.svelte +156 -0
  60. package/dist/components/form-cards/ChoiceCard.svelte.d.ts +42 -0
  61. package/dist/components/form-cards/SwitchCard.svelte +114 -0
  62. package/dist/components/form-cards/SwitchCard.svelte.d.ts +28 -0
  63. package/dist/components/toast/ToastHost.svelte +56 -3
  64. package/dist/components/toast/ToastHost.svelte.d.ts +9 -1
  65. package/dist/components/toast/ToastItem.svelte +75 -23
  66. package/dist/components/toast/ToastItem.svelte.d.ts +2 -0
  67. package/dist/components/uploadProgress.d.ts +30 -0
  68. package/dist/components/uploadProgress.js +47 -0
  69. package/dist/index.d.ts +29 -2
  70. package/dist/index.js +22 -1
  71. package/dist/stores/toast.d.ts +35 -0
  72. package/dist/stores/toast.js +15 -1
  73. package/dist/styles/motion.css +72 -0
  74. package/dist/styles/rail.css +51 -0
  75. package/package.json +6 -1
@@ -0,0 +1,361 @@
1
+ <script lang="ts" module>
2
+ import type { Snippet } from 'svelte';
3
+ import type { DateRange, Weekday } from './calendarGrid';
4
+
5
+ /** What a `day` snippet receives for each rendered cell. */
6
+ export interface MonthGridDay {
7
+ date: Date;
8
+ iso: string;
9
+ inMonth: boolean;
10
+ disabled: boolean;
11
+ selected: boolean;
12
+ today: boolean;
13
+ inRange: boolean;
14
+ }
15
+
16
+ export interface MonthGridProps {
17
+ /** Any date inside the month to show. */
18
+ month: Date;
19
+ /** The roving-focus day. Only the cell for this day is in the tab order. */
20
+ focusedDate: Date;
21
+ /** First column: 0 = Sunday … 6 = Saturday. */
22
+ weekStart?: Weekday;
23
+ locale?: string;
24
+ /** Single selection. */
25
+ selected?: Date | null;
26
+ /** Range band (pass the hover preview as `end` while picking). */
27
+ range?: DateRange | null;
28
+ isDisabled?: (date: Date) => boolean;
29
+ /** Keyboard clamps (selection rules belong to `isDisabled`). */
30
+ min?: Date | null;
31
+ max?: Date | null;
32
+ /** Render leading/trailing days from the adjacent months (dimmed). */
33
+ showOutsideDays?: boolean;
34
+ /** Always six rows, so the panel never changes height. */
35
+ fixedWeeks?: boolean;
36
+ /** Hide the weekday header row. */
37
+ hideWeekdays?: boolean;
38
+ /** Tint every enabled in-month day (booking calendars: "these days are open"). */
39
+ highlightEnabled?: boolean;
40
+ size?: 'sm' | 'md' | 'lg';
41
+ /** Accessible name; defaults to "March 2026". */
42
+ ariaLabel?: string;
43
+ rtl?: boolean;
44
+ /** Click / Enter / Space on an enabled day. */
45
+ onSelect?: (date: Date) => void;
46
+ /** Focus moved (keyboard or pointer). The host shifts `month` when it leaves the view. */
47
+ onFocusDate?: (date: Date) => void;
48
+ /** Pointer over a day (null on leave) — drives the range hover preview. */
49
+ onHover?: (date: Date | null) => void;
50
+ /** Extra words for a day's accessible name ("3 slots left"); the `day` snippet is visual only. */
51
+ describeDay?: (date: Date) => string | undefined;
52
+ /** Extra content under the day number (availability dots, counts). */
53
+ day?: Snippet<[MonthGridDay]>;
54
+ class?: string;
55
+ }
56
+ </script>
57
+
58
+ <script lang="ts">
59
+ import { tick } from 'svelte';
60
+ import {
61
+ buildMonthMatrix,
62
+ formatLongDate,
63
+ formatMonthYear,
64
+ isSameDay,
65
+ nextFocusDate,
66
+ rangeCellState,
67
+ toIsoDate,
68
+ weekdayLabels
69
+ } from './calendarGrid';
70
+
71
+ let {
72
+ month,
73
+ focusedDate,
74
+ weekStart = 0,
75
+ locale,
76
+ selected = null,
77
+ range = null,
78
+ isDisabled = () => false,
79
+ min = null,
80
+ max = null,
81
+ showOutsideDays = true,
82
+ fixedWeeks = false,
83
+ hideWeekdays = false,
84
+ highlightEnabled = false,
85
+ size = 'md',
86
+ ariaLabel,
87
+ rtl = false,
88
+ onSelect,
89
+ onFocusDate,
90
+ onHover,
91
+ describeDay,
92
+ day,
93
+ class: className = ''
94
+ }: MonthGridProps = $props();
95
+
96
+ let gridEl = $state<HTMLTableElement | null>(null);
97
+
98
+ const weeks = $derived(buildMonthMatrix(month, weekStart, fixedWeeks));
99
+ const labels = $derived(weekdayLabels(weekStart, locale, size === 'sm' ? 'narrow' : 'short'));
100
+ const focusIso = $derived(toIsoDate(focusedDate));
101
+ const label = $derived(ariaLabel ?? formatMonthYear(month, locale));
102
+ // The focused day may sit in a sibling month panel (two-month range view). When
103
+ // it is not in THIS panel, the first in-month day keeps the panel reachable by Tab.
104
+ // Each date appears once per matrix, so the iso alone identifies the tab stop.
105
+ const tabIso = $derived.by(() => {
106
+ const cells = weeks.flat();
107
+ if (cells.some((c) => c.inMonth && c.iso === focusIso)) return focusIso;
108
+ return cells.find((c) => c.inMonth)?.iso ?? '';
109
+ });
110
+
111
+ const today = new Date();
112
+
113
+ /** Move DOM focus to `date`'s cell, searching the whole calendar so a sibling panel can take it. */
114
+ async function focusCell(date: Date) {
115
+ await tick();
116
+ const iso = toIsoDate(date);
117
+ const root = gridEl?.closest('[data-calendar-root]') ?? gridEl;
118
+ const cell = root?.querySelector<HTMLElement>(`[data-iso="${iso}"]:not([data-outside])`) ??
119
+ root?.querySelector<HTMLElement>(`[data-iso="${iso}"]`);
120
+ cell?.focus();
121
+ }
122
+
123
+ function handleKeydown(e: KeyboardEvent, date: Date, disabled: boolean) {
124
+ if (e.key === 'Enter' || e.key === ' ') {
125
+ e.preventDefault();
126
+ if (!disabled) onSelect?.(date);
127
+ return;
128
+ }
129
+ if (e.altKey || e.ctrlKey || e.metaKey) return;
130
+ const next = nextFocusDate(date, e.key, { weekStart, min, max, rtl, shiftKey: e.shiftKey });
131
+ if (!next) return;
132
+ e.preventDefault();
133
+ onFocusDate?.(next);
134
+ void focusCell(next);
135
+ }
136
+
137
+ function handleClick(date: Date, disabled: boolean) {
138
+ onFocusDate?.(date);
139
+ if (!disabled) onSelect?.(date);
140
+ }
141
+ </script>
142
+
143
+ <table
144
+ bind:this={gridEl}
145
+ class="jatui-month-grid jatui-month-grid--{size} {className}"
146
+ role="grid"
147
+ aria-label={label}
148
+ dir={rtl ? 'rtl' : undefined}
149
+ onpointerleave={() => onHover?.(null)}
150
+ >
151
+ {#if !hideWeekdays}
152
+ <thead>
153
+ <tr>
154
+ {#each labels as l (l.long)}
155
+ <th scope="col" abbr={l.long} class="jatui-month-grid__weekday">{l.short}</th>
156
+ {/each}
157
+ </tr>
158
+ </thead>
159
+ {/if}
160
+ <tbody>
161
+ {#each weeks as week (week[0].iso)}
162
+ <tr>
163
+ {#each week as cell (cell.iso)}
164
+ {#if !cell.inMonth && !showOutsideDays}
165
+ <td role="gridcell" aria-hidden="true" class="jatui-month-grid__cell"></td>
166
+ {:else}
167
+ {@const disabled = isDisabled(cell.date)}
168
+ {@const isSelected = isSameDay(cell.date, selected)}
169
+ {@const band = rangeCellState(cell.date, cell.column, range)}
170
+ {@const isToday = isSameDay(cell.date, today)}
171
+ {@const solid = isSelected || band.isStart || band.isEnd}
172
+ <td
173
+ role="gridcell"
174
+ aria-selected={solid || band.inRange ? 'true' : 'false'}
175
+ class="jatui-month-grid__cell"
176
+ class:is-band={band.inRange && !(band.isStart && band.isEnd)}
177
+ class:band-start={band.bandStart}
178
+ class:band-end={band.bandEnd}
179
+ class:band-from-start={band.isStart}
180
+ class:band-to-end={band.isEnd}
181
+ >
182
+ <button
183
+ type="button"
184
+ class="jatui-month-grid__day"
185
+ class:is-solid={solid}
186
+ class:is-today={isToday}
187
+ class:is-outside={!cell.inMonth}
188
+ class:is-disabled={disabled}
189
+ class:is-open={highlightEnabled && cell.inMonth && !disabled}
190
+ data-iso={cell.iso}
191
+ data-outside={cell.inMonth ? undefined : ''}
192
+ tabindex={cell.iso === tabIso ? 0 : -1}
193
+ aria-label={[formatLongDate(cell.date, locale), describeDay?.(cell.date)].filter(Boolean).join(', ')}
194
+ aria-disabled={disabled ? 'true' : undefined}
195
+ aria-current={isToday ? 'date' : undefined}
196
+ onclick={() => handleClick(cell.date, disabled)}
197
+ onkeydown={(e) => handleKeydown(e, cell.date, disabled)}
198
+ onpointerenter={() => onHover?.(cell.date)}
199
+ >
200
+ <span class="jatui-month-grid__num">{cell.date.getDate()}</span>
201
+ {#if day}
202
+ <span class="jatui-month-grid__extra" aria-hidden="true">{@render day({
203
+ date: cell.date,
204
+ iso: cell.iso,
205
+ inMonth: cell.inMonth,
206
+ disabled,
207
+ selected: solid,
208
+ today: isToday,
209
+ inRange: band.inRange
210
+ })}</span>
211
+ {/if}
212
+ </button>
213
+ </td>
214
+ {/if}
215
+ {/each}
216
+ </tr>
217
+ {/each}
218
+ </tbody>
219
+ </table>
220
+
221
+ <style>
222
+ .jatui-month-grid {
223
+ --_cell: 2.25rem;
224
+ --_accent: var(--jatui-cal-accent, var(--jatui-selection-solid, var(--color-primary)));
225
+ --_accent-fg: var(--jatui-cal-accent-fg, var(--jatui-selection-solid-fg, var(--color-primary-content)));
226
+ --_band: var(--jatui-cal-band, var(--jatui-selection-bg, color-mix(in oklab, var(--color-primary) 14%, transparent)));
227
+ border-collapse: separate;
228
+ border-spacing: 0 2px;
229
+ table-layout: fixed;
230
+ width: 100%;
231
+ min-width: calc(var(--_cell) * 7);
232
+ }
233
+ .jatui-month-grid--sm {
234
+ --_cell: 2rem;
235
+ }
236
+ .jatui-month-grid--lg {
237
+ --_cell: 2.75rem;
238
+ }
239
+ .jatui-month-grid__weekday {
240
+ padding-block: 0.25rem;
241
+ text-align: center;
242
+ font-size: 0.75rem;
243
+ font-weight: 500;
244
+ color: color-mix(in oklab, var(--color-base-content) 55%, transparent);
245
+ }
246
+ .jatui-month-grid__cell {
247
+ position: relative;
248
+ padding: 0;
249
+ text-align: center;
250
+ }
251
+ /* The range band lives on the cell, so it bridges the gap between days. */
252
+ .jatui-month-grid__cell.is-band::before {
253
+ content: '';
254
+ position: absolute;
255
+ inset-block: 0;
256
+ inset-inline: 0;
257
+ background: var(--_band);
258
+ pointer-events: none;
259
+ }
260
+ .jatui-month-grid__cell.is-band.band-from-start::before {
261
+ inset-inline-start: 50%;
262
+ }
263
+ .jatui-month-grid__cell.is-band.band-to-end::before {
264
+ inset-inline-end: 50%;
265
+ }
266
+ .jatui-month-grid__cell.is-band.band-start:not(.band-from-start)::before {
267
+ border-start-start-radius: var(--radius-field, 0.5rem);
268
+ border-end-start-radius: var(--radius-field, 0.5rem);
269
+ }
270
+ .jatui-month-grid__cell.is-band.band-end:not(.band-to-end)::before {
271
+ border-start-end-radius: var(--radius-field, 0.5rem);
272
+ border-end-end-radius: var(--radius-field, 0.5rem);
273
+ }
274
+ .jatui-month-grid__day {
275
+ position: relative;
276
+ display: inline-flex;
277
+ flex-direction: column;
278
+ align-items: center;
279
+ justify-content: center;
280
+ gap: 1px;
281
+ width: var(--_cell);
282
+ max-width: 100%;
283
+ aspect-ratio: 1;
284
+ border-radius: var(--radius-field, 0.5rem);
285
+ font-size: 0.875rem;
286
+ font-variant-numeric: tabular-nums;
287
+ color: var(--color-base-content);
288
+ background: transparent;
289
+ cursor: pointer;
290
+ /* Both layers always render; selection fades on colour and radius only. */
291
+ transition:
292
+ background-color var(--motion-fast, 120ms) var(--motion-standard, ease),
293
+ color var(--motion-fast, 120ms) var(--motion-standard, ease);
294
+ }
295
+ .jatui-month-grid__day:hover:not(.is-solid):not(.is-disabled) {
296
+ background: var(--jatui-hover-bg, color-mix(in oklab, var(--color-base-content) 7%, transparent));
297
+ }
298
+ .jatui-month-grid__day:focus-visible {
299
+ outline: var(--jatui-focus-ring-width, 2px) solid var(--jatui-focus-ring, var(--color-primary));
300
+ outline-offset: calc(var(--jatui-focus-ring-width, 2px) * -1 + 1px);
301
+ z-index: 1;
302
+ }
303
+ .jatui-month-grid__extra {
304
+ display: inline-flex;
305
+ line-height: 0;
306
+ }
307
+ .jatui-month-grid__day.is-today:not(.is-solid) {
308
+ font-weight: 700;
309
+ color: var(--_accent);
310
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--_accent) 55%, transparent);
311
+ }
312
+ .jatui-month-grid__day.is-open:not(.is-solid) {
313
+ background: var(--_band);
314
+ color: var(--_accent);
315
+ font-weight: 600;
316
+ }
317
+ .jatui-month-grid__day.is-open:not(.is-solid):hover {
318
+ background: color-mix(in oklab, var(--_accent) 26%, transparent);
319
+ }
320
+ .jatui-month-grid__day.is-solid {
321
+ background: var(--_accent);
322
+ color: var(--_accent-fg);
323
+ font-weight: 600;
324
+ }
325
+ .jatui-month-grid__day.is-outside:not(.is-solid) {
326
+ color: color-mix(in oklab, var(--color-base-content) 40%, transparent);
327
+ }
328
+ .jatui-month-grid__day.is-disabled {
329
+ cursor: not-allowed;
330
+ color: color-mix(in oklab, var(--color-base-content) 30%, transparent);
331
+ }
332
+ .jatui-month-grid__day.is-disabled .jatui-month-grid__num {
333
+ text-decoration: line-through;
334
+ text-decoration-color: color-mix(in oklab, var(--color-base-content) 25%, transparent);
335
+ }
336
+ @media (prefers-reduced-motion: reduce) {
337
+ .jatui-month-grid__day {
338
+ transition: none;
339
+ }
340
+ }
341
+ :global([data-motion='calm']) .jatui-month-grid__day {
342
+ transition-duration: 0ms;
343
+ }
344
+ @media (forced-colors: active) {
345
+ .jatui-month-grid__day.is-open:not(.is-solid) {
346
+ background: var(--_band);
347
+ color: var(--_accent);
348
+ font-weight: 600;
349
+ }
350
+ .jatui-month-grid__day.is-open:not(.is-solid):hover {
351
+ background: color-mix(in oklab, var(--_accent) 26%, transparent);
352
+ }
353
+ .jatui-month-grid__day.is-solid {
354
+ outline: 2px solid Highlight;
355
+ }
356
+ .jatui-month-grid__cell.is-band::before {
357
+ background: Highlight;
358
+ opacity: 0.25;
359
+ }
360
+ }
361
+ </style>
@@ -0,0 +1,55 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { DateRange, Weekday } from './calendarGrid';
3
+ /** What a `day` snippet receives for each rendered cell. */
4
+ export interface MonthGridDay {
5
+ date: Date;
6
+ iso: string;
7
+ inMonth: boolean;
8
+ disabled: boolean;
9
+ selected: boolean;
10
+ today: boolean;
11
+ inRange: boolean;
12
+ }
13
+ export interface MonthGridProps {
14
+ /** Any date inside the month to show. */
15
+ month: Date;
16
+ /** The roving-focus day. Only the cell for this day is in the tab order. */
17
+ focusedDate: Date;
18
+ /** First column: 0 = Sunday … 6 = Saturday. */
19
+ weekStart?: Weekday;
20
+ locale?: string;
21
+ /** Single selection. */
22
+ selected?: Date | null;
23
+ /** Range band (pass the hover preview as `end` while picking). */
24
+ range?: DateRange | null;
25
+ isDisabled?: (date: Date) => boolean;
26
+ /** Keyboard clamps (selection rules belong to `isDisabled`). */
27
+ min?: Date | null;
28
+ max?: Date | null;
29
+ /** Render leading/trailing days from the adjacent months (dimmed). */
30
+ showOutsideDays?: boolean;
31
+ /** Always six rows, so the panel never changes height. */
32
+ fixedWeeks?: boolean;
33
+ /** Hide the weekday header row. */
34
+ hideWeekdays?: boolean;
35
+ /** Tint every enabled in-month day (booking calendars: "these days are open"). */
36
+ highlightEnabled?: boolean;
37
+ size?: 'sm' | 'md' | 'lg';
38
+ /** Accessible name; defaults to "March 2026". */
39
+ ariaLabel?: string;
40
+ rtl?: boolean;
41
+ /** Click / Enter / Space on an enabled day. */
42
+ onSelect?: (date: Date) => void;
43
+ /** Focus moved (keyboard or pointer). The host shifts `month` when it leaves the view. */
44
+ onFocusDate?: (date: Date) => void;
45
+ /** Pointer over a day (null on leave) — drives the range hover preview. */
46
+ onHover?: (date: Date | null) => void;
47
+ /** Extra words for a day's accessible name ("3 slots left"); the `day` snippet is visual only. */
48
+ describeDay?: (date: Date) => string | undefined;
49
+ /** Extra content under the day number (availability dots, counts). */
50
+ day?: Snippet<[MonthGridDay]>;
51
+ class?: string;
52
+ }
53
+ declare const MonthGrid: import("svelte").Component<MonthGridProps, {}, "">;
54
+ type MonthGrid = ReturnType<typeof MonthGrid>;
55
+ export default MonthGrid;
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Calendar grid core — the pure half of every jatui date surface.
3
+ *
4
+ * CalendarPicker, DatePicker, DateRangePicker (calendar mode), BookingScheduler
5
+ * and EventCalendar's month switcher all render the same month panel. This
6
+ * module owns everything about that panel that is not markup, so it can be
7
+ * unit-tested without a DOM:
8
+ *
9
+ * - Local-calendar date math on plain `Date`s (always local midnight) and the
10
+ * ISO `YYYY-MM-DD` strings jatui uses at its edges (native `<input
11
+ * type="date">` values, `AvailabilityCounts` keys).
12
+ * - Locale-aware week start (`Intl.Locale#getWeekInfo`, with a region table
13
+ * fallback for engines that lack it).
14
+ * - The month matrix (leading/trailing days from the adjacent months).
15
+ * - The APG date-grid keyboard model: arrows, Home/End, PageUp/PageDown
16
+ * (+Shift for years), clamped to min/max, mirrored for RTL.
17
+ * - Range state: normalising, membership, band edges, day counts, presets.
18
+ * - Typed entry: parse and format in the locale's own field order, so en-US
19
+ * reads MM/DD/YYYY and en-GB DD/MM/YYYY (never a hardcoded order).
20
+ *
21
+ * No `@internationalized/date`: everything here is plain `Date` + `Intl`.
22
+ */
23
+ /** 0 = Sunday … 6 = Saturday (the `Date#getDay` numbering). */
24
+ export type Weekday = 0 | 1 | 2 | 3 | 4 | 5 | 6;
25
+ /** An inclusive date range. Either end may be open while a range is being picked. */
26
+ export interface DateRange {
27
+ start: Date | null;
28
+ end: Date | null;
29
+ }
30
+ /** The ISO-string form of a range — what DateRangePicker binds. */
31
+ export interface IsoDateRange {
32
+ start: string | null;
33
+ end: string | null;
34
+ }
35
+ /** Local midnight of `d`. */
36
+ export declare function startOfDay(d: Date): Date;
37
+ export declare function isSameDay(a: Date | null | undefined, b: Date | null | undefined): boolean;
38
+ export declare function isSameMonth(a: Date, b: Date): boolean;
39
+ /** `d` plus `n` calendar days (DST-safe: works on the date fields, not ms). */
40
+ export declare function addDays(d: Date, n: number): Date;
41
+ /**
42
+ * `d` plus `n` months, clamping the day so Jan 31 + 1 month is Feb 28/29
43
+ * rather than rolling into March.
44
+ */
45
+ export declare function addMonths(d: Date, n: number): Date;
46
+ export declare function startOfMonth(d: Date): Date;
47
+ export declare function endOfMonth(d: Date): Date;
48
+ export declare function daysInMonth(year: number, month: number): number;
49
+ /** Whole calendar days from `a` to `b` (positive when `b` is later). */
50
+ export declare function dayDiff(a: Date, b: Date): number;
51
+ /** First day of the week containing `d`. */
52
+ export declare function startOfWeek(d: Date, weekStart?: Weekday): Date;
53
+ export declare function endOfWeek(d: Date, weekStart?: Weekday): Date;
54
+ /** Compare by calendar day: negative, 0 or positive. */
55
+ export declare function compareDays(a: Date, b: Date): number;
56
+ /** Clamp a day into [min, max] (either bound optional). */
57
+ export declare function clampDay(d: Date, min?: Date | null, max?: Date | null): Date;
58
+ /** `YYYY-MM-DD` for the LOCAL calendar day (never `toISOString`, which shifts to UTC). */
59
+ export declare function toIsoDate(d: Date): string;
60
+ /**
61
+ * Parse `YYYY-MM-DD` as LOCAL midnight (`new Date('2026-03-04')` is UTC
62
+ * midnight, a day early west of Greenwich). Anything else, or an impossible
63
+ * date like 2026-02-30, returns null.
64
+ */
65
+ export declare function parseIsoDate(s: string | null | undefined): Date | null;
66
+ /** Accept a Date, an ISO string or nothing; always hand back local midnight or null. */
67
+ export declare function toDay(v: Date | string | null | undefined): Date | null;
68
+ /**
69
+ * The first day of the week for `locale` (default: the runtime locale).
70
+ * `Intl.Locale#getWeekInfo` (or the older `weekInfo` getter) when the engine
71
+ * has it, else the CLDR region table above.
72
+ */
73
+ export declare function weekStartForLocale(locale?: string): Weekday;
74
+ /** Resolve a `weekStart` prop: a number wins, `'locale'` (or undefined) asks the locale. */
75
+ export declare function resolveWeekStart(weekStart: Weekday | 'locale' | undefined, locale?: string): Weekday;
76
+ /** Weekday column labels in display order, e.g. `['Mo', 'Tu', …]` for a Monday start. */
77
+ export declare function weekdayLabels(weekStart?: Weekday, locale?: string, width?: 'narrow' | 'short' | 'long'): {
78
+ short: string;
79
+ long: string;
80
+ }[];
81
+ export declare function formatMonthYear(d: Date, locale?: string): string;
82
+ /** "Wednesday, March 4, 2026" — the accessible name of a day cell. */
83
+ export declare function formatLongDate(d: Date, locale?: string): string;
84
+ /** "Mar 4, 2026" — trigger labels and range chips. */
85
+ export declare function formatMediumDate(d: Date, locale?: string): string;
86
+ export interface MonthCell {
87
+ date: Date;
88
+ iso: string;
89
+ /** Belongs to the displayed month (false for leading/trailing days). */
90
+ inMonth: boolean;
91
+ /** 0-6 column in display order. */
92
+ column: number;
93
+ }
94
+ /**
95
+ * The weeks of `month` (any date inside it), each exactly seven cells, padded
96
+ * with days from the adjacent months. `fixedWeeks` always returns six rows so
97
+ * a panel never changes height between months.
98
+ */
99
+ export declare function buildMonthMatrix(month: Date, weekStart?: Weekday, fixedWeeks?: boolean): MonthCell[][];
100
+ export interface GridKeyOptions {
101
+ weekStart?: Weekday;
102
+ min?: Date | null;
103
+ max?: Date | null;
104
+ /** Mirror Left/Right for right-to-left layouts. */
105
+ rtl?: boolean;
106
+ }
107
+ /**
108
+ * The day focus moves to for `key`, or null when the key is not a grid key.
109
+ *
110
+ * | Key | Move |
111
+ * |---|---|
112
+ * | ←/→ | ∓1 day (mirrored in RTL) |
113
+ * | ↑/↓ | ∓1 week |
114
+ * | Home/End | first/last day of the week |
115
+ * | PageUp/PageDown | ∓1 month (Shift: ∓1 year), day clamped to the month length |
116
+ *
117
+ * The result is clamped to [min, max]. Disabled dates stay focusable (APG:
118
+ * they are announced as unavailable rather than skipped), so the caller only
119
+ * gates selection.
120
+ */
121
+ export declare function nextFocusDate(current: Date, key: string, opts?: GridKeyOptions & {
122
+ shiftKey?: boolean;
123
+ }): Date | null;
124
+ export interface DateRules {
125
+ min?: Date | string | null;
126
+ max?: Date | string | null;
127
+ /** Return false to disable a date. */
128
+ isDateEnabled?: (date: Date) => boolean;
129
+ /** Disable Saturdays and Sundays. */
130
+ weekdaysOnly?: boolean;
131
+ }
132
+ /** One predicate out of min/max/weekday/custom rules. */
133
+ export declare function makeIsDisabled(rules: DateRules): (date: Date) => boolean;
134
+ /** Order a range's ends (a range picked right-to-left is still start ≤ end). */
135
+ export declare function normalizeRange(range: DateRange): DateRange;
136
+ export declare function isInRange(d: Date, range: DateRange | null | undefined): boolean;
137
+ /** Inclusive day count of a complete range, else 0. */
138
+ export declare function rangeDayCount(range: DateRange | null | undefined): number;
139
+ /**
140
+ * How a cell paints inside a range band.
141
+ *
142
+ * `bandStart`/`bandEnd` round the band's own ends AND the row ends (the first
143
+ * and last column), so a range wrapping onto the next week reads as two
144
+ * capsules rather than one bar running off the grid edge.
145
+ */
146
+ export interface RangeCellState {
147
+ inRange: boolean;
148
+ isStart: boolean;
149
+ isEnd: boolean;
150
+ bandStart: boolean;
151
+ bandEnd: boolean;
152
+ }
153
+ export declare function rangeCellState(d: Date, column: number, range: DateRange | null | undefined): RangeCellState;
154
+ /**
155
+ * The next range after clicking `date`, picking ends in order:
156
+ * no start (or a complete range) → start a new range; a start → close it
157
+ * (swapping when the second click is earlier).
158
+ */
159
+ export declare function pickRangeDate(range: DateRange, date: Date): DateRange;
160
+ export declare function toIsoRange(range: DateRange): IsoDateRange;
161
+ export declare function fromIsoRange(range: IsoDateRange | null | undefined): DateRange;
162
+ export interface RangePreset {
163
+ /** Stable id (used for the active highlight). */
164
+ id: string;
165
+ label: string;
166
+ /** The range, or a function of "now" so presets stay current. `start: null` = open ("All time"). */
167
+ range: IsoDateRange | ((now: Date) => IsoDateRange);
168
+ }
169
+ /** Resolve a preset's range against `now`. */
170
+ export declare function resolvePreset(preset: RangePreset, now?: Date): IsoDateRange;
171
+ /** Today, Yesterday, Last 7 days, Last 30 days, This month, Last month, This year, Last year. */
172
+ export declare function defaultRangePresets(): RangePreset[];
173
+ /** The id of the preset `range` equals, if any. */
174
+ export declare function matchPreset(range: IsoDateRange, presets: RangePreset[], now?: Date): string | null;
175
+ type Field = 'year' | 'month' | 'day';
176
+ /** The locale's numeric field order, e.g. `['month','day','year']` for en-US. */
177
+ export declare function dateFieldOrder(locale?: string): Field[];
178
+ /** The separator the locale uses between numeric date fields ("/", ".", "-"). */
179
+ export declare function dateSeparator(locale?: string): string;
180
+ /** A placeholder hint, e.g. "MM/DD/YYYY" (en-US) or "DD.MM.YYYY" (de-DE). */
181
+ export declare function datePlaceholder(locale?: string): string;
182
+ /** Format for the typed-entry field: numeric, zero-padded, locale order. */
183
+ export declare function formatTypedDate(d: Date | null, locale?: string): string;
184
+ /**
185
+ * Parse what a person typed. Accepts ISO `YYYY-MM-DD` anywhere, else three
186
+ * numeric groups in the locale's field order with any separator. Two-digit
187
+ * years land in 2000–2099. Impossible dates (31/02) return null.
188
+ */
189
+ export declare function parseTypedDate(text: string, locale?: string): Date | null;
190
+ export type CalendarAccent = 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error' | 'neutral';
191
+ /**
192
+ * Inline custom properties that re-tint a month panel. `'primary'` (and
193
+ * anything unknown) returns '' so the jatui selection tokens apply as-is.
194
+ * The old `accentColor` prop interpolated `bg-${accentColor}` classes, which
195
+ * Tailwind's scanner never emits; this replaces it with token vars.
196
+ */
197
+ export declare function calendarAccentStyle(accent: string | undefined): string;
198
+ /**
199
+ * "Mar 4 – 10, 2026" via `Intl.DateTimeFormat#formatRange` (which collapses
200
+ * the shared month/year), "From Mar 4, 2026" / "Until …" for open ends, '' for
201
+ * an empty range.
202
+ */
203
+ export declare function formatRangeLabel(range: DateRange, locale?: string): string;
204
+ /** The default calendar-mode presets plus the "All time" (open) sentinel. */
205
+ export declare function defaultRangePresetsWithAll(): RangePreset[];
206
+ /**
207
+ * The first visible month after focus moves to `date`, for `months` panels
208
+ * starting at `viewMonth`: unchanged while the day is on screen, otherwise
209
+ * the minimum shift that brings it back (it lands in the first panel when
210
+ * moving backwards, the last when moving forwards).
211
+ */
212
+ export declare function followFocus(viewMonth: Date, date: Date, months?: number): Date;
213
+ /** Whether the panels may step back / forward without leaving [min, max]. */
214
+ export declare function canStepMonth(viewMonth: Date, dir: -1 | 1, opts?: {
215
+ min?: Date | null;
216
+ max?: Date | null;
217
+ months?: number;
218
+ }): boolean;
219
+ export {};