@happyvertical/smrt-ui 0.51.39 → 0.52.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.
- package/AGENTS.md +14 -2
- package/README.md +257 -5
- package/dist/actions/__tests__/swipe-dismiss.test.js +69 -0
- package/dist/actions/swipe-dismiss.d.ts +42 -0
- package/dist/actions/swipe-dismiss.d.ts.map +1 -0
- package/dist/actions/swipe-dismiss.js +69 -0
- package/dist/components/calendar/Calendar.svelte +5 -0
- package/dist/components/calendar/Calendar.svelte.d.ts.map +1 -1
- package/dist/components/calendar/CalendarView.svelte +1250 -0
- package/dist/components/calendar/CalendarView.svelte.d.ts +66 -0
- package/dist/components/calendar/CalendarView.svelte.d.ts.map +1 -0
- package/dist/components/calendar/DayView.svelte +5 -0
- package/dist/components/calendar/DayView.svelte.d.ts.map +1 -1
- package/dist/components/calendar/__tests__/CalendarView.test.js +226 -0
- package/dist/components/calendar/__tests__/calendar-model.test.js +138 -0
- package/dist/components/calendar/calendar-model.d.ts +178 -0
- package/dist/components/calendar/calendar-model.d.ts.map +1 -0
- package/dist/components/calendar/calendar-model.js +362 -0
- package/dist/components/calendar/index.d.ts +12 -1
- package/dist/components/calendar/index.d.ts.map +1 -1
- package/dist/components/calendar/index.js +11 -1
- package/dist/components/data/CollectionList.svelte +23 -5
- package/dist/components/data/CollectionList.svelte.d.ts +8 -2
- package/dist/components/data/CollectionList.svelte.d.ts.map +1 -1
- package/dist/components/data/DataTable.svelte +118 -40
- package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
- package/dist/components/data/DataTableController.d.ts +4 -0
- package/dist/components/data/DataTableController.d.ts.map +1 -1
- package/dist/components/data/DataTableController.js +11 -4
- package/dist/components/data/ListSortSelect.svelte +121 -0
- package/dist/components/data/ListSortSelect.svelte.d.ts +22 -0
- package/dist/components/data/ListSortSelect.svelte.d.ts.map +1 -0
- package/dist/components/data/SortableHeader.svelte +169 -0
- package/dist/components/data/SortableHeader.svelte.d.ts +30 -0
- package/dist/components/data/SortableHeader.svelte.d.ts.map +1 -0
- package/dist/components/data/__tests__/CollectionList.test.js +37 -0
- package/dist/components/data/__tests__/DataTable.test.js +76 -0
- package/dist/components/data/__tests__/DataTableController.test.js +35 -0
- package/dist/components/data/__tests__/SortableHeader.test.js +101 -0
- package/dist/components/data/__tests__/link-step-surface.test.js +172 -0
- package/dist/components/data/__tests__/list-sort-state.svelte.test.js +71 -0
- package/dist/components/data/__tests__/list-sort.test.js +91 -0
- package/dist/components/data/__tests__/surface-navigation.test.js +121 -0
- package/dist/components/data/data-surface.d.ts +1 -0
- package/dist/components/data/data-surface.d.ts.map +1 -1
- package/dist/components/data/data-surface.js +1 -0
- package/dist/components/data/index.d.ts +6 -0
- package/dist/components/data/index.d.ts.map +1 -1
- package/dist/components/data/index.js +6 -0
- package/dist/components/data/link-surface.d.ts +79 -0
- package/dist/components/data/link-surface.d.ts.map +1 -0
- package/dist/components/data/link-surface.js +155 -0
- package/dist/components/data/list-sort-state.svelte.d.ts +39 -0
- package/dist/components/data/list-sort-state.svelte.d.ts.map +1 -0
- package/dist/components/data/list-sort-state.svelte.js +47 -0
- package/dist/components/data/list-sort.d.ts +94 -0
- package/dist/components/data/list-sort.d.ts.map +1 -0
- package/dist/components/data/list-sort.js +182 -0
- package/dist/components/data/step-surface.d.ts +73 -0
- package/dist/components/data/step-surface.d.ts.map +1 -0
- package/dist/components/data/step-surface.js +177 -0
- package/dist/components/data/surface-navigation.d.ts +49 -0
- package/dist/components/data/surface-navigation.d.ts.map +1 -0
- package/dist/components/data/surface-navigation.js +100 -0
- package/dist/components/data/types.d.ts +19 -0
- package/dist/components/data/types.d.ts.map +1 -1
- package/dist/components/feedback/PhoneSheet.svelte +224 -0
- package/dist/components/feedback/PhoneSheet.svelte.d.ts +35 -0
- package/dist/components/feedback/PhoneSheet.svelte.d.ts.map +1 -0
- package/dist/components/feedback/WorkingStrip.svelte +426 -0
- package/dist/components/feedback/WorkingStrip.svelte.d.ts +37 -0
- package/dist/components/feedback/WorkingStrip.svelte.d.ts.map +1 -0
- package/dist/components/feedback/__tests__/PhoneSheet.test.js +101 -0
- package/dist/components/feedback/__tests__/WorkingStrip.test.js +172 -0
- package/dist/components/feedback/index.d.ts +4 -0
- package/dist/components/feedback/index.d.ts.map +1 -1
- package/dist/components/feedback/index.js +5 -0
- package/dist/components/feedback/working-status.d.ts +34 -0
- package/dist/components/feedback/working-status.d.ts.map +1 -0
- package/dist/components/feedback/working-status.js +5 -0
- package/dist/components/forms/Checkbox.svelte +26 -2
- package/dist/components/forms/Combobox.svelte +68 -12
- package/dist/components/forms/Combobox.svelte.d.ts +6 -0
- package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
- package/dist/components/forms/DictationButton.svelte +128 -0
- package/dist/components/forms/DictationButton.svelte.d.ts +19 -0
- package/dist/components/forms/DictationButton.svelte.d.ts.map +1 -0
- package/dist/components/forms/DictationStatus.svelte +164 -0
- package/dist/components/forms/DictationStatus.svelte.d.ts +11 -0
- package/dist/components/forms/DictationStatus.svelte.d.ts.map +1 -0
- package/dist/components/forms/Fieldset.svelte +5 -1
- package/dist/components/forms/Fieldset.svelte.d.ts +2 -0
- package/dist/components/forms/Fieldset.svelte.d.ts.map +1 -1
- package/dist/components/forms/FormActionBar.svelte +84 -0
- package/dist/components/forms/FormActionBar.svelte.d.ts +23 -0
- package/dist/components/forms/FormActionBar.svelte.d.ts.map +1 -0
- package/dist/components/forms/Input.svelte +9 -0
- package/dist/components/forms/Radio.svelte +15 -4
- package/dist/components/forms/SearchInput.svelte +207 -0
- package/dist/components/forms/SearchInput.svelte.d.ts +22 -0
- package/dist/components/forms/SearchInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/Select.svelte +9 -0
- package/dist/components/forms/StagedControlReview.svelte +21 -12
- package/dist/components/forms/StagedControlReview.svelte.d.ts.map +1 -1
- package/dist/components/forms/Switch.svelte +11 -5
- package/dist/components/forms/TagsInput.svelte +8 -1
- package/dist/components/forms/Textarea.svelte +8 -0
- package/dist/components/forms/__tests__/Combobox.test.js +87 -0
- package/dist/components/forms/__tests__/ControlHitArea.test.js +73 -0
- package/dist/components/forms/__tests__/DictationButton.test.js +176 -0
- package/dist/components/forms/__tests__/Fieldset.test.js +37 -0
- package/dist/components/forms/__tests__/FormActionBar.test.js +22 -0
- package/dist/components/forms/__tests__/SearchInput.test.js +92 -0
- package/dist/components/forms/__tests__/StagedControlReview.test.js +37 -3
- package/dist/components/forms/__tests__/combobox-labels.fixture.svelte +19 -0
- package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts +11 -0
- package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/composite-registration.fixture.svelte +29 -0
- package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/control-interaction.test.js +19 -1
- package/dist/components/forms/__tests__/control-proposals.test.js +63 -0
- package/dist/components/forms/__tests__/dictation-recording.test.js +582 -0
- package/dist/components/forms/__tests__/dictation.test.js +486 -0
- package/dist/components/forms/__tests__/long-press.test.js +175 -0
- package/dist/components/forms/__tests__/place-picker.fixture.svelte +33 -0
- package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts +12 -0
- package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/audio-capture.d.ts +69 -0
- package/dist/components/forms/audio-capture.d.ts.map +1 -0
- package/dist/components/forms/audio-capture.js +193 -0
- package/dist/components/forms/control-interaction.d.ts +6 -0
- package/dist/components/forms/control-interaction.d.ts.map +1 -1
- package/dist/components/forms/control-interaction.js +12 -1
- package/dist/components/forms/control-proposals.d.ts +55 -0
- package/dist/components/forms/control-proposals.d.ts.map +1 -0
- package/dist/components/forms/control-proposals.js +188 -0
- package/dist/components/forms/dictation-transcribe.d.ts +36 -0
- package/dist/components/forms/dictation-transcribe.d.ts.map +1 -0
- package/dist/components/forms/dictation-transcribe.js +65 -0
- package/dist/components/forms/dictation.svelte.d.ts +200 -0
- package/dist/components/forms/dictation.svelte.d.ts.map +1 -0
- package/dist/components/forms/dictation.svelte.js +686 -0
- package/dist/components/forms/index.d.ts +14 -1
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +14 -1
- package/dist/components/forms/insert-text.d.ts +12 -0
- package/dist/components/forms/insert-text.d.ts.map +1 -0
- package/dist/components/forms/insert-text.js +38 -0
- package/dist/components/forms/long-press.d.ts +88 -0
- package/dist/components/forms/long-press.d.ts.map +1 -0
- package/dist/components/forms/long-press.js +213 -0
- package/dist/components/forms/ready-beep.d.ts +24 -0
- package/dist/components/forms/ready-beep.d.ts.map +1 -0
- package/dist/components/forms/ready-beep.js +82 -0
- package/dist/components/layout/PageHeader.svelte +151 -17
- package/dist/components/layout/PageHeader.svelte.d.ts +47 -7
- package/dist/components/layout/PageHeader.svelte.d.ts.map +1 -1
- package/dist/components/layout/__tests__/PageHeader.test.js +87 -1
- package/dist/components/layout/index.d.ts +1 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/components/layout/index.js +1 -0
- package/dist/components/layout/page-header-context.d.ts +25 -0
- package/dist/components/layout/page-header-context.d.ts.map +1 -0
- package/dist/components/layout/page-header-context.js +16 -0
- package/dist/components/nav/Tabs.svelte +241 -0
- package/dist/components/nav/Tabs.svelte.d.ts +15 -0
- package/dist/components/nav/Tabs.svelte.d.ts.map +1 -1
- package/dist/components/nav/__tests__/LinkTabs.test.js +117 -0
- package/dist/components/nav/index.d.ts +1 -0
- package/dist/components/nav/index.d.ts.map +1 -1
- package/dist/components/nav/index.js +1 -0
- package/dist/components/nav/tabs-overflow.d.ts +12 -0
- package/dist/components/nav/tabs-overflow.d.ts.map +1 -0
- package/dist/components/nav/tabs-overflow.js +17 -0
- package/dist/components/nav/types.d.ts +8 -0
- package/dist/components/nav/types.d.ts.map +1 -1
- package/dist/components/ui/Button.svelte +7 -0
- package/dist/components/ui/Card.svelte +14 -5
- package/dist/components/ui/Dropdown.svelte +55 -1
- package/dist/components/ui/Dropdown.svelte.d.ts +8 -0
- package/dist/components/ui/Dropdown.svelte.d.ts.map +1 -1
- package/dist/components/ui/IconToggle.svelte +139 -0
- package/dist/components/ui/IconToggle.svelte.d.ts +32 -0
- package/dist/components/ui/IconToggle.svelte.d.ts.map +1 -0
- package/dist/components/ui/IconToggleGroup.svelte +56 -0
- package/dist/components/ui/IconToggleGroup.svelte.d.ts +21 -0
- package/dist/components/ui/IconToggleGroup.svelte.d.ts.map +1 -0
- package/dist/components/ui/Pagination.svelte +1 -1
- package/dist/components/ui/Popover.svelte +17 -1
- package/dist/components/ui/Popover.svelte.d.ts +8 -0
- package/dist/components/ui/Popover.svelte.d.ts.map +1 -1
- package/dist/components/ui/__tests__/IconToggle.test.js +59 -0
- package/dist/components/ui/__tests__/IconToggleGroup.test.js +30 -0
- package/dist/components/ui/__tests__/Popover.test.js +44 -0
- package/dist/components/ui/__tests__/dropdown-popover-icon.test.js +125 -0
- package/dist/components/ui/__tests__/phone-touch-targets.test.js +39 -0
- package/dist/components/ui/index.d.ts +2 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +2 -0
- package/dist/i18n/strings.ui.d.ts +41 -0
- package/dist/i18n/strings.ui.d.ts.map +1 -1
- package/dist/i18n/strings.ui.js +46 -0
- package/dist/svelte/__tests__/playground.test.js +1 -0
- package/dist/svelte/playground/CalendarPreview.svelte +140 -0
- package/dist/svelte/playground/CalendarPreview.svelte.d.ts +4 -0
- package/dist/svelte/playground/CalendarPreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground.d.ts.map +1 -1
- package/dist/svelte/playground.js +10 -0
- package/dist/types-generic.d.ts +2 -0
- package/dist/types-generic.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CalendarView — a generic month calendar with a phone agenda.
|
|
3
|
+
*
|
|
4
|
+
* - `month` mode: a month grid (`role="grid"`, roving focus, arrow keys).
|
|
5
|
+
* All-day and multi-day items render as bands across the days they cover;
|
|
6
|
+
* a day that has more than fits shows "+N more", which opens the day in a
|
|
7
|
+
* panel under the grid (never a modal) or follows `dayHref`.
|
|
8
|
+
* - `agenda` mode: a scrollable strip of the month's days above a day-by-day
|
|
9
|
+
* list. `auto` (default) uses the agenda below 48rem, the grid above.
|
|
10
|
+
*
|
|
11
|
+
* Every day is computed in `timeZone` (IANA) and every name comes from
|
|
12
|
+
* `Intl` in `locale`, so the browser's own zone and language never leak in.
|
|
13
|
+
* The visible month and selected day can be controlled (`year`/`month`,
|
|
14
|
+
* `selectedDate`) and are reported through `onNavigate` / `onSelectDate`,
|
|
15
|
+
* so a page can keep them in its URL.
|
|
16
|
+
*/
|
|
17
|
+
import type { Snippet } from 'svelte';
|
|
18
|
+
import { type CalendarItem, type CalendarMode, type CalendarMonth } from './calendar-model.js';
|
|
19
|
+
export interface Props {
|
|
20
|
+
/** Items to show. */
|
|
21
|
+
items?: CalendarItem[];
|
|
22
|
+
/** Visible year (controlled together with `month`). */
|
|
23
|
+
year?: number;
|
|
24
|
+
/** Visible month, 1-12 (controlled together with `year`). */
|
|
25
|
+
month?: number;
|
|
26
|
+
/** Selected day as `YYYY-MM-DD` (controlled). */
|
|
27
|
+
selectedDate?: string | null;
|
|
28
|
+
/** IANA time zone every day is computed in. Defaults to the browser's. */
|
|
29
|
+
timeZone?: string;
|
|
30
|
+
/** BCP 47 locale for month/day names and times. Defaults to the i18n locale. */
|
|
31
|
+
locale?: string;
|
|
32
|
+
/** First weekday column, 0 = Sunday. Defaults to the locale's. */
|
|
33
|
+
weekStartsOn?: number;
|
|
34
|
+
/** `auto` switches to the agenda below 48rem. */
|
|
35
|
+
mode?: CalendarMode;
|
|
36
|
+
/** Rows per day in the month grid, including band rows. */
|
|
37
|
+
maxPerDay?: number;
|
|
38
|
+
/** Heading level of the month title. */
|
|
39
|
+
headingLevel?: 2 | 3 | 4;
|
|
40
|
+
/**
|
|
41
|
+
* "Now", for today highlighting (tests, server rendering). Without it the
|
|
42
|
+
* calendar reads the clock after mount and moves "today" at midnight in
|
|
43
|
+
* `timeZone`; before mount (server rendering) nothing is highlighted as
|
|
44
|
+
* today. Server-rendered pages should pass `timeZone` and `year`/`month`
|
|
45
|
+
* (or `now`) so the server and the browser pick the same month.
|
|
46
|
+
*/
|
|
47
|
+
now?: Date;
|
|
48
|
+
/** Called when the visible month changes (prev/next/today/keyboard). */
|
|
49
|
+
onNavigate?: (month: CalendarMonth) => void;
|
|
50
|
+
/** Called when a day is selected. */
|
|
51
|
+
onSelectDate?: (date: string) => void;
|
|
52
|
+
/** Called when an item is activated (also for items with `href`). */
|
|
53
|
+
onItemSelect?: (item: CalendarItem) => void;
|
|
54
|
+
/** Link for a day; days become links and "+N more" follows it. */
|
|
55
|
+
dayHref?: (date: string) => string | undefined;
|
|
56
|
+
/** Custom row content for an item in the day panel and agenda. */
|
|
57
|
+
itemContent?: Snippet<[CalendarItem, {
|
|
58
|
+
time: string;
|
|
59
|
+
}]>;
|
|
60
|
+
/** Extra class on the root. */
|
|
61
|
+
class?: string;
|
|
62
|
+
}
|
|
63
|
+
declare const CalendarView: import("svelte").Component<Props, {}, "">;
|
|
64
|
+
type CalendarView = ReturnType<typeof CalendarView>;
|
|
65
|
+
export default CalendarView;
|
|
66
|
+
//# sourceMappingURL=CalendarView.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CalendarView.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/calendar/CalendarView.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAItC,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,aAAa,EAgBnB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,KAAK;IACpB,qBAAqB;IACrB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACzB;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,wEAAwE;IACxE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5C,qCAAqC;IACrC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,qEAAqE;IACrE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAC5C,kEAAkE;IAClE,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC/C,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACxD,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyiBD,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* DayView Component
|
|
4
4
|
* Full event list for a specific day with optional weather
|
|
5
|
+
*
|
|
6
|
+
* @deprecated Use `CalendarView` (its day panel, agenda mode, or `dayHref`
|
|
7
|
+
* to a page of your own). This component formats dates as `en-US` in the
|
|
8
|
+
* browser zone and hard-codes game/meeting routes. See the smrt-ui README,
|
|
9
|
+
* "Calendar", for the migration.
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
12
|
import { M } from '../../i18n/strings.ui.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DayView.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/calendar/DayView.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DayView.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/calendar/DayView.svelte.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGvE,MAAM,WAAW,KAAK;IACpB,+CAA+C;IAC/C,IAAI,EAAE,IAAI,CAAC;IACX,qEAAqE;IACrE,MAAM,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IACjC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/D;AA6JD,QAAA,MAAM,OAAO,2CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { act, render, screen, within } from '@testing-library/svelte';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
5
|
+
import CalendarView from '../CalendarView.svelte';
|
|
6
|
+
const tz = 'America/Edmonton';
|
|
7
|
+
const now = new Date('2026-09-29T18:00:00Z');
|
|
8
|
+
const items = [
|
|
9
|
+
{ id: 'fair', title: 'Fall Fair', start: '2026-09-05', end: '2026-09-07' },
|
|
10
|
+
{
|
|
11
|
+
id: 'council',
|
|
12
|
+
title: 'Council meeting',
|
|
13
|
+
start: new Date('2026-09-15T01:00:00Z'),
|
|
14
|
+
label: 'Council',
|
|
15
|
+
href: '/meetings/sept',
|
|
16
|
+
},
|
|
17
|
+
{ id: 'a', title: 'Game A', start: new Date('2026-09-20T18:00:00Z') },
|
|
18
|
+
{ id: 'b', title: 'Game B', start: new Date('2026-09-20T19:00:00Z') },
|
|
19
|
+
{ id: 'c', title: 'Game C', start: new Date('2026-09-20T20:00:00Z') },
|
|
20
|
+
{ id: 'd', title: 'Game D', start: new Date('2026-09-20T21:00:00Z') },
|
|
21
|
+
];
|
|
22
|
+
function renderMonth(props = {}) {
|
|
23
|
+
return render(CalendarView, {
|
|
24
|
+
props: {
|
|
25
|
+
items,
|
|
26
|
+
timeZone: tz,
|
|
27
|
+
locale: 'en-US',
|
|
28
|
+
weekStartsOn: 0,
|
|
29
|
+
mode: 'month',
|
|
30
|
+
now,
|
|
31
|
+
year: 2026,
|
|
32
|
+
month: 9,
|
|
33
|
+
...props,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
describe('CalendarView (month)', () => {
|
|
38
|
+
it('renders a localized grid in the given zone', () => {
|
|
39
|
+
renderMonth();
|
|
40
|
+
expect(screen.getByRole('heading', { name: 'September 2026' })).toBeTruthy();
|
|
41
|
+
expect(screen.getByRole('grid')).toBeTruthy();
|
|
42
|
+
expect(screen.getAllByRole('columnheader')).toHaveLength(7);
|
|
43
|
+
// 19:00 MDT on the 14th, not the 15th (UTC)
|
|
44
|
+
expect(screen.getByRole('button', {
|
|
45
|
+
name: 'Monday, September 14, 2026, 1 item',
|
|
46
|
+
})).toBeTruthy();
|
|
47
|
+
expect(screen
|
|
48
|
+
.getByRole('button', { name: 'Tuesday, September 29, 2026' })
|
|
49
|
+
.getAttribute('aria-current')).toBe('date');
|
|
50
|
+
});
|
|
51
|
+
it('renders month names in the requested locale', () => {
|
|
52
|
+
renderMonth({ locale: 'fr-FR' });
|
|
53
|
+
expect(screen.getByRole('heading').textContent?.trim()).toBe('septembre 2026');
|
|
54
|
+
});
|
|
55
|
+
it('renders all-day spans as bands and overflow as +N more', async () => {
|
|
56
|
+
const onSelectDate = vi.fn();
|
|
57
|
+
const { container } = renderMonth({ onSelectDate });
|
|
58
|
+
// Sat Sep 5 - Mon Sep 7 wraps: one segment per week row.
|
|
59
|
+
const bands = [...container.querySelectorAll('.cv-band')];
|
|
60
|
+
expect(bands.map((b) => b.textContent?.trim())).toEqual([
|
|
61
|
+
'Fall Fair',
|
|
62
|
+
'Fall Fair',
|
|
63
|
+
]);
|
|
64
|
+
expect(bands[0].style.gridColumn).toBe('7 / span 1');
|
|
65
|
+
expect(bands[0].classList.contains('cv-band--after')).toBe(true);
|
|
66
|
+
expect(bands[1].style.gridColumn).toBe('1 / span 2');
|
|
67
|
+
expect(bands[1].classList.contains('cv-band--before')).toBe(true);
|
|
68
|
+
const more = screen.getByText('+2 more');
|
|
69
|
+
await userEvent.click(more);
|
|
70
|
+
expect(onSelectDate).toHaveBeenCalledWith('2026-09-20');
|
|
71
|
+
const panel = screen.getByRole('region', {
|
|
72
|
+
name: 'Sunday, September 20, 2026',
|
|
73
|
+
});
|
|
74
|
+
expect(within(panel).getAllByRole('button', { name: /Game/ })).toHaveLength(4);
|
|
75
|
+
await userEvent.click(within(panel).getByRole('button', {
|
|
76
|
+
name: 'Close Sunday, September 20, 2026',
|
|
77
|
+
}));
|
|
78
|
+
expect(screen.queryByRole('region')).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
it('navigates with prev/next/today and reports the month', async () => {
|
|
81
|
+
const onNavigate = vi.fn();
|
|
82
|
+
render(CalendarView, {
|
|
83
|
+
props: {
|
|
84
|
+
items,
|
|
85
|
+
timeZone: tz,
|
|
86
|
+
locale: 'en-US',
|
|
87
|
+
mode: 'month',
|
|
88
|
+
now,
|
|
89
|
+
onNavigate,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
await userEvent.click(screen.getByRole('button', { name: 'Next month' }));
|
|
93
|
+
expect(onNavigate).toHaveBeenLastCalledWith({ year: 2026, month: 10 });
|
|
94
|
+
expect(screen.getByRole('heading').textContent?.trim()).toBe('October 2026');
|
|
95
|
+
await userEvent.click(screen.getByRole('button', { name: 'Today' }));
|
|
96
|
+
expect(onNavigate).toHaveBeenLastCalledWith({ year: 2026, month: 9 });
|
|
97
|
+
});
|
|
98
|
+
it('moves focus with arrow keys and crosses months', async () => {
|
|
99
|
+
const onNavigate = vi.fn();
|
|
100
|
+
renderMonth({ onNavigate, year: undefined, month: undefined });
|
|
101
|
+
const today = screen.getByRole('button', {
|
|
102
|
+
name: 'Tuesday, September 29, 2026',
|
|
103
|
+
});
|
|
104
|
+
expect(today.tabIndex).toBe(0);
|
|
105
|
+
today.focus();
|
|
106
|
+
await userEvent.keyboard('{ArrowRight}');
|
|
107
|
+
expect(document.activeElement.dataset.cvDay).toBe('2026-09-30');
|
|
108
|
+
await userEvent.keyboard('{ArrowRight}');
|
|
109
|
+
expect(onNavigate).toHaveBeenLastCalledWith({ year: 2026, month: 10 });
|
|
110
|
+
expect(document.activeElement.dataset.cvDay).toBe('2026-10-01');
|
|
111
|
+
await userEvent.keyboard('{ArrowUp}');
|
|
112
|
+
expect(document.activeElement.dataset.cvDay).toBe('2026-09-24');
|
|
113
|
+
await userEvent.keyboard('{Home}');
|
|
114
|
+
expect(document.activeElement.dataset.cvDay).toBe('2026-09-20');
|
|
115
|
+
});
|
|
116
|
+
it('turns days into links with dayHref', () => {
|
|
117
|
+
renderMonth({ dayHref: (d) => `/events/${d}` });
|
|
118
|
+
const link = screen.getByRole('link', {
|
|
119
|
+
name: 'Monday, September 14, 2026, 1 item',
|
|
120
|
+
});
|
|
121
|
+
expect(link.getAttribute('href')).toBe('/events/2026-09-14');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('CalendarView accessibility', () => {
|
|
125
|
+
it('is axe-clean in month mode with an open day', async () => {
|
|
126
|
+
const { container } = renderMonth({ selectedDate: '2026-09-20' });
|
|
127
|
+
await expectNoA11yViolations(container);
|
|
128
|
+
});
|
|
129
|
+
it('is axe-clean in agenda mode', async () => {
|
|
130
|
+
const { container } = renderMonth({ mode: 'agenda' });
|
|
131
|
+
await expectNoA11yViolations(container);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
describe('CalendarView (agenda)', () => {
|
|
135
|
+
it('lists days with items and a day strip', async () => {
|
|
136
|
+
const onItemSelect = vi.fn();
|
|
137
|
+
render(CalendarView, {
|
|
138
|
+
props: {
|
|
139
|
+
items,
|
|
140
|
+
timeZone: tz,
|
|
141
|
+
locale: 'en-US',
|
|
142
|
+
mode: 'agenda',
|
|
143
|
+
now,
|
|
144
|
+
year: 2026,
|
|
145
|
+
month: 9,
|
|
146
|
+
onItemSelect,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
const strip = screen.getByRole('toolbar', {
|
|
150
|
+
name: 'Days in September 2026',
|
|
151
|
+
});
|
|
152
|
+
expect(within(strip).getAllByRole('button')).toHaveLength(30);
|
|
153
|
+
const headings = screen
|
|
154
|
+
.getAllByRole('heading', { level: 3 })
|
|
155
|
+
.map((h) => h.textContent?.trim());
|
|
156
|
+
expect(headings).toEqual([
|
|
157
|
+
'Saturday, September 5, 2026',
|
|
158
|
+
'Sunday, September 6, 2026',
|
|
159
|
+
'Monday, September 7, 2026',
|
|
160
|
+
'Monday, September 14, 2026',
|
|
161
|
+
'Sunday, September 20, 2026',
|
|
162
|
+
]);
|
|
163
|
+
const link = screen.getByRole('link', { name: /Council meeting/ });
|
|
164
|
+
expect(link.getAttribute('href')).toBe('/meetings/sept');
|
|
165
|
+
expect(link.textContent).toContain('7:00 PM');
|
|
166
|
+
expect(link.textContent).toContain('Council');
|
|
167
|
+
expect(screen.getAllByText('Until Sep 7').length).toBeGreaterThan(0);
|
|
168
|
+
await userEvent.click(within(strip).getByRole('button', {
|
|
169
|
+
name: 'Thursday, September 10, 2026',
|
|
170
|
+
}));
|
|
171
|
+
expect(screen.getByRole('heading', { name: 'Thursday, September 10, 2026' })).toBeTruthy();
|
|
172
|
+
expect(screen.getAllByText('Nothing scheduled').length).toBe(1);
|
|
173
|
+
});
|
|
174
|
+
it('switches to the agenda on phones in auto mode', async () => {
|
|
175
|
+
const matchMedia = vi.fn().mockImplementation((query) => ({
|
|
176
|
+
matches: query === '(max-width: 48rem)',
|
|
177
|
+
media: query,
|
|
178
|
+
addEventListener: vi.fn(),
|
|
179
|
+
removeEventListener: vi.fn(),
|
|
180
|
+
}));
|
|
181
|
+
vi.stubGlobal('matchMedia', matchMedia);
|
|
182
|
+
try {
|
|
183
|
+
const { container } = render(CalendarView, {
|
|
184
|
+
props: { items, timeZone: tz, locale: 'en-US', now },
|
|
185
|
+
});
|
|
186
|
+
await Promise.resolve();
|
|
187
|
+
expect(container.querySelector('[data-view]')?.getAttribute('data-view')).toBe('agenda');
|
|
188
|
+
expect(screen.queryByRole('grid')).toBeNull();
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
vi.unstubAllGlobals();
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
describe('CalendarView robustness', () => {
|
|
196
|
+
it('falls back instead of throwing on an invalid time zone', () => {
|
|
197
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
198
|
+
try {
|
|
199
|
+
expect(() => renderMonth({ timeZone: 'Mars/Olympus_Mons' })).not.toThrow();
|
|
200
|
+
expect(screen.getByRole('heading', { name: 'September 2026' })).toBeTruthy();
|
|
201
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining('Mars/Olympus_Mons'));
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
warn.mockRestore();
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
it('moves the today highlight after midnight without a now prop', async () => {
|
|
208
|
+
vi.useFakeTimers({ toFake: ['Date', 'setInterval', 'clearInterval'] });
|
|
209
|
+
try {
|
|
210
|
+
// 23:59 on Sep 29 in Edmonton (05:59Z on the 30th).
|
|
211
|
+
vi.setSystemTime(new Date('2026-09-30T05:59:00Z'));
|
|
212
|
+
renderMonth({ now: undefined });
|
|
213
|
+
const day = (name) => screen.getByRole('button', { name });
|
|
214
|
+
await act(() => { });
|
|
215
|
+
expect(day('Tuesday, September 29, 2026').getAttribute('aria-current')).toBe('date');
|
|
216
|
+
await act(() => {
|
|
217
|
+
vi.advanceTimersByTime(2 * 60_000);
|
|
218
|
+
});
|
|
219
|
+
expect(day('Wednesday, September 30, 2026').getAttribute('aria-current')).toBe('date');
|
|
220
|
+
expect(day('Tuesday, September 29, 2026').getAttribute('aria-current')).toBeNull();
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
vi.useRealTimers();
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { addMonthsToKey, dateKeyInZone, defaultWeekStart, layoutMonth, monthWeeks, shiftMonth, toEntries, toneFor, } from '../calendar-model.js';
|
|
3
|
+
describe('calendar model', () => {
|
|
4
|
+
it('computes days in the given zone, not the host zone', () => {
|
|
5
|
+
const instant = new Date('2026-09-29T03:30:00Z');
|
|
6
|
+
expect(dateKeyInZone(instant, 'America/Edmonton')).toBe('2026-09-28');
|
|
7
|
+
expect(dateKeyInZone(instant, 'Pacific/Auckland')).toBe('2026-09-29');
|
|
8
|
+
});
|
|
9
|
+
it('builds full weeks for any first weekday', () => {
|
|
10
|
+
const sunday = monthWeeks({ year: 2026, month: 9 }, 0);
|
|
11
|
+
expect(sunday[0][0]).toBe('2026-08-30');
|
|
12
|
+
expect(sunday.at(-1)?.[6]).toBe('2026-10-03');
|
|
13
|
+
const monday = monthWeeks({ year: 2026, month: 9 }, 1);
|
|
14
|
+
expect(monday[0][0]).toBe('2026-08-31');
|
|
15
|
+
expect(monday).toHaveLength(5);
|
|
16
|
+
});
|
|
17
|
+
it('shifts months and clamps days', () => {
|
|
18
|
+
expect(shiftMonth({ year: 2026, month: 12 }, 1)).toEqual({
|
|
19
|
+
year: 2027,
|
|
20
|
+
month: 1,
|
|
21
|
+
});
|
|
22
|
+
expect(shiftMonth({ year: 2026, month: 1 }, -1)).toEqual({
|
|
23
|
+
year: 2025,
|
|
24
|
+
month: 12,
|
|
25
|
+
});
|
|
26
|
+
expect(addMonthsToKey('2026-01-31', 1)).toBe('2026-02-28');
|
|
27
|
+
});
|
|
28
|
+
it('reads the locale week start when the runtime knows it', () => {
|
|
29
|
+
const start = defaultWeekStart('en-GB');
|
|
30
|
+
expect([0, 1]).toContain(start);
|
|
31
|
+
expect(defaultWeekStart('not a locale!')).toBe(0);
|
|
32
|
+
});
|
|
33
|
+
it('normalizes all-day dates, exclusive midnights, and timed spans', () => {
|
|
34
|
+
const tz = 'America/Edmonton';
|
|
35
|
+
const entries = toEntries([
|
|
36
|
+
{ id: 'a', title: 'Fair', start: '2026-09-05', end: '2026-09-07' },
|
|
37
|
+
{
|
|
38
|
+
id: 'b',
|
|
39
|
+
title: 'Exclusive',
|
|
40
|
+
allDay: true,
|
|
41
|
+
start: new Date('2026-09-10T06:00:00Z'),
|
|
42
|
+
end: new Date('2026-09-12T06:00:00Z'),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'c',
|
|
46
|
+
title: 'Late meeting',
|
|
47
|
+
start: new Date('2026-09-15T01:00:00Z'),
|
|
48
|
+
end: new Date('2026-09-15T03:00:00Z'),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'd',
|
|
52
|
+
title: 'Overnight',
|
|
53
|
+
start: new Date('2026-09-20T04:00:00Z'),
|
|
54
|
+
end: new Date('2026-09-20T16:00:00Z'),
|
|
55
|
+
},
|
|
56
|
+
{ id: 'bad', title: 'Bad', start: 'not a date' },
|
|
57
|
+
], tz);
|
|
58
|
+
const byId = Object.fromEntries(entries.map((e) => [e.item.id, e]));
|
|
59
|
+
expect(byId.a).toMatchObject({
|
|
60
|
+
startKey: '2026-09-05',
|
|
61
|
+
endKey: '2026-09-07',
|
|
62
|
+
allDay: true,
|
|
63
|
+
band: true,
|
|
64
|
+
});
|
|
65
|
+
expect(byId.b).toMatchObject({
|
|
66
|
+
startKey: '2026-09-10',
|
|
67
|
+
endKey: '2026-09-11',
|
|
68
|
+
});
|
|
69
|
+
// 19:00-21:00 MDT on the 14th
|
|
70
|
+
expect(byId.c).toMatchObject({
|
|
71
|
+
startKey: '2026-09-14',
|
|
72
|
+
endKey: '2026-09-14',
|
|
73
|
+
band: false,
|
|
74
|
+
});
|
|
75
|
+
// 22:00 on the 19th to 10:00 on the 20th spans two days
|
|
76
|
+
expect(byId.d).toMatchObject({
|
|
77
|
+
startKey: '2026-09-19',
|
|
78
|
+
endKey: '2026-09-20',
|
|
79
|
+
band: true,
|
|
80
|
+
});
|
|
81
|
+
expect(byId.bad).toBeUndefined();
|
|
82
|
+
});
|
|
83
|
+
it('lays out bands in lanes and counts overflow as "+N more"', () => {
|
|
84
|
+
const entries = toEntries([
|
|
85
|
+
{ id: 'span', title: 'Span', start: '2026-09-01', end: '2026-09-09' },
|
|
86
|
+
{ id: 'one', title: 'One', start: '2026-09-02' },
|
|
87
|
+
{
|
|
88
|
+
id: 't1',
|
|
89
|
+
title: 'T1',
|
|
90
|
+
start: new Date('2026-09-02T15:00:00Z'),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 't2',
|
|
94
|
+
title: 'T2',
|
|
95
|
+
start: new Date('2026-09-02T16:00:00Z'),
|
|
96
|
+
},
|
|
97
|
+
], 'UTC');
|
|
98
|
+
const weeks = layoutMonth({ year: 2026, month: 9 }, entries, {
|
|
99
|
+
weekStartsOn: 0,
|
|
100
|
+
maxPerDay: 3,
|
|
101
|
+
});
|
|
102
|
+
const first = weeks[0];
|
|
103
|
+
// Span continues into the next week; "One" takes a second lane.
|
|
104
|
+
const span = first.bands.find((b) => b.entry.item.id === 'span');
|
|
105
|
+
expect(span).toMatchObject({ column: 2, span: 5, lane: 0 });
|
|
106
|
+
expect(span?.continuesAfter).toBe(true);
|
|
107
|
+
expect(first.lanes).toBe(2);
|
|
108
|
+
const sep2 = first.days.find((d) => d.key === '2026-09-02');
|
|
109
|
+
// One row left for timed items; two don't fit, so show none + "+2 more".
|
|
110
|
+
expect(sep2?.visible).toHaveLength(0);
|
|
111
|
+
expect(sep2?.hiddenCount).toBe(2);
|
|
112
|
+
expect(sep2?.all.map((e) => e.item.id)).toEqual([
|
|
113
|
+
'span',
|
|
114
|
+
'one',
|
|
115
|
+
't1',
|
|
116
|
+
't2',
|
|
117
|
+
]);
|
|
118
|
+
const second = weeks[1];
|
|
119
|
+
const cont = second.bands.find((b) => b.entry.item.id === 'span');
|
|
120
|
+
expect(cont).toMatchObject({ column: 0, span: 4, continuesBefore: true });
|
|
121
|
+
});
|
|
122
|
+
it('derives a stable tone from the group', () => {
|
|
123
|
+
const a = toneFor({
|
|
124
|
+
id: '1',
|
|
125
|
+
title: 'x',
|
|
126
|
+
start: '2026-01-01',
|
|
127
|
+
group: 'council',
|
|
128
|
+
});
|
|
129
|
+
const b = toneFor({
|
|
130
|
+
id: '2',
|
|
131
|
+
title: 'y',
|
|
132
|
+
start: '2026-01-01',
|
|
133
|
+
group: 'council',
|
|
134
|
+
});
|
|
135
|
+
expect(a).toBe(b);
|
|
136
|
+
expect(toneFor({ id: '3', title: 'z', start: '2026-01-01', tone: 'error' })).toBe('error');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, Svelte-free date model for `CalendarView`.
|
|
3
|
+
*
|
|
4
|
+
* Every calendar day is a `YYYY-MM-DD` key in the view's IANA time zone.
|
|
5
|
+
* Instants are projected into that zone through `Intl` exactly once; all
|
|
6
|
+
* further math (grids, spans, lanes) runs on keys, so the browser's own zone
|
|
7
|
+
* never leaks into what a day contains.
|
|
8
|
+
*/
|
|
9
|
+
/** Color tone of a calendar item, mapped to the theme's color roles. */
|
|
10
|
+
export type CalendarTone = 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'neutral';
|
|
11
|
+
/** A generic item on the calendar (an event, a meeting, a game, a deadline). */
|
|
12
|
+
export interface CalendarItem {
|
|
13
|
+
/** Stable identity, used as the render key. */
|
|
14
|
+
id: string;
|
|
15
|
+
/** Visible title. */
|
|
16
|
+
title: string;
|
|
17
|
+
/**
|
|
18
|
+
* Start: a `Date`, an ISO date-time string, or a `YYYY-MM-DD` date (which
|
|
19
|
+
* makes the item all-day unless `allDay` says otherwise).
|
|
20
|
+
*/
|
|
21
|
+
start: Date | string;
|
|
22
|
+
/**
|
|
23
|
+
* End. Timed items: the exclusive end instant. All-day items: a
|
|
24
|
+
* `YYYY-MM-DD` date is the inclusive last day; an instant at local
|
|
25
|
+
* midnight is the exclusive end (the day after the last day).
|
|
26
|
+
*/
|
|
27
|
+
end?: Date | string | null;
|
|
28
|
+
/** Occupies whole days; rendered as a band in the month grid. */
|
|
29
|
+
allDay?: boolean;
|
|
30
|
+
/** Color role. Defaults to one derived from `group`, else `primary`. */
|
|
31
|
+
tone?: CalendarTone;
|
|
32
|
+
/** Explicit CSS color; overrides `tone`. */
|
|
33
|
+
color?: string;
|
|
34
|
+
/** Short category text shown next to the title in lists (e.g. "Council"). */
|
|
35
|
+
label?: string;
|
|
36
|
+
/** Stable grouping key (e.g. a content type); picks a tone when none is set. */
|
|
37
|
+
group?: string;
|
|
38
|
+
/** Link target. Without it the item is a button that calls `onItemSelect`. */
|
|
39
|
+
href?: string;
|
|
40
|
+
}
|
|
41
|
+
/** A visible month; `month` is 1-12 (calendar numbering, not `Date#getMonth`). */
|
|
42
|
+
export interface CalendarMonth {
|
|
43
|
+
year: number;
|
|
44
|
+
month: number;
|
|
45
|
+
}
|
|
46
|
+
/** Month grid, agenda list, or month grid above 48rem and agenda below. */
|
|
47
|
+
export type CalendarMode = 'auto' | 'month' | 'agenda';
|
|
48
|
+
/** An item projected onto calendar day keys. */
|
|
49
|
+
export interface CalendarEntry {
|
|
50
|
+
item: CalendarItem;
|
|
51
|
+
/** First day (inclusive). */
|
|
52
|
+
startKey: string;
|
|
53
|
+
/** Last day (inclusive). */
|
|
54
|
+
endKey: string;
|
|
55
|
+
allDay: boolean;
|
|
56
|
+
/** Rendered as a band: all-day, or spans more than one day. */
|
|
57
|
+
band: boolean;
|
|
58
|
+
/** Start instant for timed items; start-of-day ordering for all-day. */
|
|
59
|
+
startMs: number;
|
|
60
|
+
}
|
|
61
|
+
/** A band segment inside one week row. */
|
|
62
|
+
export interface CalendarBandSegment {
|
|
63
|
+
entry: CalendarEntry;
|
|
64
|
+
/** 0-6 column in the week. */
|
|
65
|
+
column: number;
|
|
66
|
+
/** Number of columns covered. */
|
|
67
|
+
span: number;
|
|
68
|
+
/** 0-based lane (row) inside the week's band area. */
|
|
69
|
+
lane: number;
|
|
70
|
+
/** The item started before this week. */
|
|
71
|
+
continuesBefore: boolean;
|
|
72
|
+
/** The item ends after this week. */
|
|
73
|
+
continuesAfter: boolean;
|
|
74
|
+
}
|
|
75
|
+
/** A single day cell in a laid-out week. */
|
|
76
|
+
export interface CalendarDayCell {
|
|
77
|
+
key: string;
|
|
78
|
+
day: number;
|
|
79
|
+
inMonth: boolean;
|
|
80
|
+
/** Timed single-day items shown in the cell. */
|
|
81
|
+
visible: CalendarEntry[];
|
|
82
|
+
/** Items (bands and timed) on this day that did not fit. */
|
|
83
|
+
hiddenCount: number;
|
|
84
|
+
/** Every item on this day (bands first, then by start). */
|
|
85
|
+
all: CalendarEntry[];
|
|
86
|
+
}
|
|
87
|
+
/** One week row of the month grid. */
|
|
88
|
+
export interface CalendarWeek {
|
|
89
|
+
days: CalendarDayCell[];
|
|
90
|
+
bands: CalendarBandSegment[];
|
|
91
|
+
/** Number of band lanes rendered in this week. */
|
|
92
|
+
lanes: number;
|
|
93
|
+
}
|
|
94
|
+
/** The browser's resolved IANA zone (`UTC` when unavailable). */
|
|
95
|
+
export declare function defaultTimeZone(): string;
|
|
96
|
+
/** Whether `timeZone` is an IANA zone this runtime's `Intl` accepts. */
|
|
97
|
+
export declare function isValidTimeZone(timeZone: unknown): timeZone is string;
|
|
98
|
+
/**
|
|
99
|
+
* `timeZone` when valid, otherwise the runtime default. `onInvalid` hears
|
|
100
|
+
* about a rejected non-empty zone (a typo, a zone this runtime lacks), so a
|
|
101
|
+
* view can warn instead of throwing from its render.
|
|
102
|
+
*/
|
|
103
|
+
export declare function resolveTimeZone(timeZone: string | undefined, onInvalid?: (timeZone: string) => void): string;
|
|
104
|
+
/**
|
|
105
|
+
* The calendar day of an instant in a time zone.
|
|
106
|
+
*
|
|
107
|
+
* @param instant - Instant to project
|
|
108
|
+
* @param timeZone - IANA time zone
|
|
109
|
+
* @returns `YYYY-MM-DD`
|
|
110
|
+
*/
|
|
111
|
+
export declare function dateKeyInZone(instant: Date, timeZone: string): string;
|
|
112
|
+
/** Today's `YYYY-MM-DD` in a time zone. */
|
|
113
|
+
export declare function todayKey(timeZone: string, now?: Date): string;
|
|
114
|
+
/** Whether a string is a `YYYY-MM-DD` date key. */
|
|
115
|
+
export declare function isDateKey(value: unknown): value is string;
|
|
116
|
+
/** Build a `YYYY-MM-DD` key (month 1-12). */
|
|
117
|
+
export declare function formatKey(year: number, month: number, day: number): string;
|
|
118
|
+
/** Split a key into numbers (month 1-12). */
|
|
119
|
+
export declare function parseKey(key: string): {
|
|
120
|
+
year: number;
|
|
121
|
+
month: number;
|
|
122
|
+
day: number;
|
|
123
|
+
};
|
|
124
|
+
/** Add days to a key. */
|
|
125
|
+
export declare function addDays(key: string, days: number): string;
|
|
126
|
+
/** Day of week of a key (0 = Sunday). */
|
|
127
|
+
export declare function weekdayOfKey(key: string): number;
|
|
128
|
+
/** The month a key falls in. */
|
|
129
|
+
export declare function monthOfKey(key: string): CalendarMonth;
|
|
130
|
+
/** Shift a month by `delta` months. */
|
|
131
|
+
export declare function shiftMonth(value: CalendarMonth, delta: number): CalendarMonth;
|
|
132
|
+
/** Days in a month. */
|
|
133
|
+
export declare function daysInMonth(value: CalendarMonth): number;
|
|
134
|
+
/** Move a key by whole months, clamping the day (Jan 31 + 1 month = Feb 28). */
|
|
135
|
+
export declare function addMonthsToKey(key: string, delta: number): string;
|
|
136
|
+
/** Every key of a month, in order. */
|
|
137
|
+
export declare function monthKeys(value: CalendarMonth): string[];
|
|
138
|
+
/**
|
|
139
|
+
* Full weeks covering a month.
|
|
140
|
+
*
|
|
141
|
+
* @param value - Month to cover
|
|
142
|
+
* @param weekStartsOn - First column (0 = Sunday, 1 = Monday, …)
|
|
143
|
+
* @returns Week rows of seven keys each
|
|
144
|
+
*/
|
|
145
|
+
export declare function monthWeeks(value: CalendarMonth, weekStartsOn: number): string[][];
|
|
146
|
+
/**
|
|
147
|
+
* First day of the week for a locale (0 = Sunday), from `Intl.Locale` week
|
|
148
|
+
* info where the runtime provides it; Sunday otherwise.
|
|
149
|
+
*/
|
|
150
|
+
export declare function defaultWeekStart(locale: string | undefined): number;
|
|
151
|
+
/** Tone for an item: explicit tone, else a stable tone for its group. */
|
|
152
|
+
export declare function toneFor(item: CalendarItem): CalendarTone;
|
|
153
|
+
/**
|
|
154
|
+
* Project items onto day keys. Items with unparseable dates are dropped and
|
|
155
|
+
* reported through `onInvalid`.
|
|
156
|
+
*
|
|
157
|
+
* @param items - Calendar items
|
|
158
|
+
* @param timeZone - IANA zone the calendar shows
|
|
159
|
+
* @param onInvalid - Called for each dropped item
|
|
160
|
+
* @returns Entries sorted by start
|
|
161
|
+
*/
|
|
162
|
+
export declare function toEntries(items: readonly CalendarItem[], timeZone: string, onInvalid?: (item: CalendarItem) => void): CalendarEntry[];
|
|
163
|
+
/** Every entry on a day: bands covering it first, then timed items by start. */
|
|
164
|
+
export declare function entriesOnDay(entries: readonly CalendarEntry[], key: string): CalendarEntry[];
|
|
165
|
+
/**
|
|
166
|
+
* Lay out a month grid: bands get lanes per week, each day shows what fits in
|
|
167
|
+
* `maxPerDay` rows, and the rest is counted for a "+N more" control.
|
|
168
|
+
*
|
|
169
|
+
* @param value - Visible month
|
|
170
|
+
* @param entries - Entries from {@link toEntries}
|
|
171
|
+
* @param options - First weekday and per-day row budget
|
|
172
|
+
* @returns Week rows
|
|
173
|
+
*/
|
|
174
|
+
export declare function layoutMonth(value: CalendarMonth, entries: readonly CalendarEntry[], options: {
|
|
175
|
+
weekStartsOn: number;
|
|
176
|
+
maxPerDay: number;
|
|
177
|
+
}): CalendarWeek[];
|
|
178
|
+
//# sourceMappingURL=calendar-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar-model.d.ts","sourceRoot":"","sources":["../../../src/components/calendar/calendar-model.ts"],"names":[],"mappings":"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"}
|