@marianmeres/stuic 3.170.0 → 3.172.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 (59) hide show
  1. package/AGENTS.md +10 -8
  2. package/API.md +90 -0
  3. package/README.md +1 -1
  4. package/dist/components/AssetsPreview/AssetsPreview.fixture.svelte +1 -1
  5. package/dist/components/Calendar/Calendar.svelte +799 -0
  6. package/dist/components/Calendar/Calendar.svelte.d.ts +135 -0
  7. package/dist/components/Calendar/README.md +294 -0
  8. package/dist/components/Calendar/calendar-i18n-sk.d.ts +20 -0
  9. package/dist/components/Calendar/calendar-i18n-sk.js +46 -0
  10. package/dist/components/Calendar/calendar-i18n.d.ts +61 -0
  11. package/dist/components/Calendar/calendar-i18n.js +73 -0
  12. package/dist/components/Calendar/index.css +307 -0
  13. package/dist/components/Calendar/index.d.ts +5 -0
  14. package/dist/components/Calendar/index.js +5 -0
  15. package/dist/components/Calendar/iso-date.d.ts +107 -0
  16. package/dist/components/Calendar/iso-date.js +247 -0
  17. package/dist/components/CommandMenu/CommandMenu.fixture.svelte +1 -1
  18. package/dist/components/ContextMenu/ContextMenu.svelte +1 -1
  19. package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +1 -1
  20. package/dist/components/ContextMenu/README.md +1 -1
  21. package/dist/components/DropdownMenu/DropdownMenu.svelte +57 -3
  22. package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +4 -2
  23. package/dist/components/DropdownMenu/README.md +1 -0
  24. package/dist/components/Input/FieldDate.svelte +349 -0
  25. package/dist/components/Input/FieldDate.svelte.d.ts +79 -0
  26. package/dist/components/Input/FieldDateRange.svelte +373 -0
  27. package/dist/components/Input/FieldDateRange.svelte.d.ts +90 -0
  28. package/dist/components/Input/README.md +149 -16
  29. package/dist/components/Input/_internal/FieldDateShell.svelte +327 -0
  30. package/dist/components/Input/_internal/FieldDateShell.svelte.d.ts +66 -0
  31. package/dist/components/Input/index.css +119 -0
  32. package/dist/components/Input/index.d.ts +2 -0
  33. package/dist/components/Input/index.js +2 -0
  34. package/dist/components/ModalDialog/ModalDialog.fixture.svelte +1 -1
  35. package/dist/components/SlidingPanels/SlidingPanels.fixture.svelte +1 -1
  36. package/dist/icons/index.d.ts +1 -0
  37. package/dist/icons/index.js +1 -0
  38. package/dist/index.css +1 -0
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.js +1 -0
  41. package/docs/_archive/README.md +10 -0
  42. package/docs/{component-testing → _archive/component-testing}/00-overview-and-roadmap.md +8 -6
  43. package/docs/{component-testing → _archive/component-testing}/01-framework-setup.md +4 -2
  44. package/docs/{component-testing → _archive/component-testing}/03-component-coverage-roadmap.md +3 -1
  45. package/docs/{component-testing → _archive/component-testing}/04-hard-cases-and-e2e.md +5 -3
  46. package/docs/{component-testing → _archive/component-testing}/05-ci.md +2 -0
  47. package/docs/{component-testing → _archive/component-testing}/PROGRESS.md +3 -1
  48. package/docs/_archive/component-testing/README.md +28 -0
  49. package/docs/{upgrading.md → _archive/upgrading.md} +2 -0
  50. package/docs/architecture.md +19 -11
  51. package/docs/domains/actions.md +4 -3
  52. package/docs/domains/components.md +22 -19
  53. package/docs/domains/utils.md +2 -1
  54. package/docs/maybe-todo.md +6 -6
  55. package/docs/tasks.md +19 -9
  56. package/docs/{component-testing/02-test-conventions.md → testing-components.md} +30 -31
  57. package/docs/testing.md +3 -3
  58. package/package.json +2 -1
  59. package/docs/component-testing/README.md +0 -38
@@ -0,0 +1,135 @@
1
+ import type { HTMLAttributes } from "svelte/elements";
2
+ import type { Snippet } from "svelte";
3
+ import type { DayCell, Weekday } from "@marianmeres/calendar-utils";
4
+ import type { TranslateFn } from "../../types.js";
5
+ import type { IsoDate, YearMonth } from "./iso-date.js";
6
+ /** What the calendar selects. */
7
+ export type CalendarMode = "single" | "range";
8
+ /** How the month/year caption renders. */
9
+ export type CalendarCaptionLayout = "label" | "dropdown";
10
+ /**
11
+ * Everything the `renderDay` snippet knows about one day. The same facts drive
12
+ * the `data-*` styling hooks on the day button / cell.
13
+ */
14
+ export interface CalendarDayState {
15
+ /** `YYYY-MM-DD` */
16
+ iso: IsoDate;
17
+ /** Day of month (1–31) */
18
+ day: number;
19
+ /** The calendar-utils cell (Luxon `date`, `weekNumber`, …) */
20
+ cell: DayCell;
21
+ /** The selected date (single) or one of the range ends (range) */
22
+ selected: boolean;
23
+ rangeStart: boolean;
24
+ rangeEnd: boolean;
25
+ /** Strictly between the two committed range ends */
26
+ inRange: boolean;
27
+ /** Inside the not-yet-committed range (anchor → hovered / focused day) */
28
+ preview: boolean;
29
+ today: boolean;
30
+ /** Belongs to the previous / next month (grid filler) */
31
+ outside: boolean;
32
+ weekend: boolean;
33
+ disabled: boolean;
34
+ }
35
+ /** Payload of `onRangeChange`. */
36
+ export interface CalendarRangeChange {
37
+ start: IsoDate | null;
38
+ end: IsoDate | null;
39
+ /** `true` once both ends are set */
40
+ complete: boolean;
41
+ }
42
+ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
43
+ /** `"single"` (default) binds `value`; `"range"` binds `start` + `end`. */
44
+ mode?: CalendarMode;
45
+ /** The selected date, `YYYY-MM-DD` (bindable; single mode). */
46
+ value?: IsoDate | null;
47
+ /** Range start, `YYYY-MM-DD` (bindable; range mode). */
48
+ start?: IsoDate | null;
49
+ /** Range end, `YYYY-MM-DD` (bindable; range mode). */
50
+ end?: IsoDate | null;
51
+ /**
52
+ * The first displayed month (bindable). Defaults to the month of the
53
+ * selection, else the current month.
54
+ */
55
+ view?: YearMonth;
56
+ /** How many consecutive months to render side by side. Default `1`. */
57
+ months?: number;
58
+ /** Earliest selectable date (inclusive); earlier days are disabled. */
59
+ min?: IsoDate | null;
60
+ /** Latest selectable date (inclusive); later days are disabled. */
61
+ max?: IsoDate | null;
62
+ /** Disable individual days (blackout dates, weekends, …). */
63
+ isDateDisabled?: (iso: IsoDate, cell: DayCell) => boolean;
64
+ /** First day of the week, ISO numbering: 1 = Monday (default) … 7 = Sunday. */
65
+ weekStartsOn?: Weekday;
66
+ /** Days flagged as weekend (`data-weekend`). Default `[6, 7]`. */
67
+ weekendDays?: Weekday[];
68
+ /** BCP 47 locale of the day / month names. Browser default when omitted. */
69
+ locale?: string;
70
+ /** IANA zone that decides which day is "today". Default local. */
71
+ zone?: string;
72
+ /** `"label"` (default) or `"dropdown"` (month + year selects, e.g. for birth dates). */
73
+ captionLayout?: CalendarCaptionLayout;
74
+ /** Years offered by the dropdown caption. Defaults to `min`/`max` years, else today ±100/+20. */
75
+ yearRange?: [number, number];
76
+ /** Show the ISO week number column. */
77
+ showWeekNumbers?: boolean;
78
+ /** Render the previous / next month's filler days. Default `true`. */
79
+ showOutsideDays?: boolean;
80
+ /** Always render 6 weeks so the height never jumps while navigating. */
81
+ fixedWeeks?: boolean;
82
+ /** Footer "Today" button (navigates to and focuses today; does not select). */
83
+ showToday?: boolean;
84
+ /** Footer "Clear" button. */
85
+ showClear?: boolean;
86
+ /** Whole calendar non-interactive. */
87
+ disabled?: boolean;
88
+ /** Move focus into the grid once mounted (dialog use). */
89
+ focusOnMount?: boolean;
90
+ /** i18n — see `createCalendarT`. */
91
+ t?: TranslateFn;
92
+ /** Single mode: a date was picked (or cleared). */
93
+ onSelect?: (value: IsoDate | null) => void;
94
+ /** Range mode: an end was picked / the range cleared. */
95
+ onRangeChange?: (range: CalendarRangeChange) => void;
96
+ /** The displayed month changed (navigation, keyboard, dropdowns). */
97
+ onViewChange?: (view: YearMonth) => void;
98
+ /** Custom day cell content (receives `CalendarDayState`). */
99
+ renderDay?: Snippet<[CalendarDayState]>;
100
+ /** Extra footer content (rendered on the end side of the footer row). */
101
+ footer?: Snippet;
102
+ /** Skip all default styling */
103
+ unstyled?: boolean;
104
+ /** Additional CSS classes for the root */
105
+ class?: string;
106
+ /** Class for each month block */
107
+ classMonth?: string;
108
+ /** Class for each month's header row (prev / caption / next) */
109
+ classHeader?: string;
110
+ /** Class for the caption (month + year) */
111
+ classCaption?: string;
112
+ /** Class for the `<table>` */
113
+ classGrid?: string;
114
+ /** Class for the weekday header cells */
115
+ classWeekday?: string;
116
+ /** Class for the day cells (`<td>`) */
117
+ classCell?: string;
118
+ /** Class for the day buttons */
119
+ classDay?: string;
120
+ /** Class for the footer row */
121
+ classFooter?: string;
122
+ /** Bindable element reference */
123
+ el?: HTMLDivElement;
124
+ }
125
+ declare const Calendar: import("svelte").Component<Props, {
126
+ focus: () => void;
127
+ prevMonth: () => void;
128
+ nextMonth: () => void;
129
+ goToMonth: (year: number, month: number) => void;
130
+ goToDate: (iso: IsoDate) => void;
131
+ goToToday: () => void;
132
+ getView: () => YearMonth;
133
+ }, "el" | "value" | "view" | "end" | "start">;
134
+ type Calendar = ReturnType<typeof Calendar>;
135
+ export default Calendar;
@@ -0,0 +1,294 @@
1
+ # Calendar
2
+
3
+ An accessible month grid for picking a single date or a date range — the picker
4
+ inside [`FieldDate` / `FieldDateRange`](../Input/README.md#date-and-date-range-fields),
5
+ usable on its own wherever a calendar should sit inline (a booking widget, a filter
6
+ sidebar, a dashboard). Values are calendar dates in ISO `YYYY-MM-DD` form; the grid,
7
+ "today", week numbers and DST-safe day stepping come from
8
+ [`@marianmeres/calendar-utils`](https://github.com/marianmeres/calendar-utils), the
9
+ day / month names from `Intl` via `locale`.
10
+
11
+ - **Single or range** selection (`mode`), with hover / keyboard range preview
12
+ - **Full keyboard grid** (APG date-picker pattern): roving tabindex, arrows, Home/End,
13
+ PageUp/PageDown (± Shift), Enter/Space
14
+ - **Bounds and blackouts**: `min` / `max` / `isDateDisabled`
15
+ - **Navigation**: prev/next, or native month + year selects (`captionLayout="dropdown"`)
16
+ - **Multiple months** side by side, fixed 6-week height, ISO week numbers
17
+ - **Localized** (`locale` for names, `t` for UI texts; Slovak bundled), announced
18
+ selections via a live region, 44px touch targets on coarse pointers
19
+
20
+ ## Usage
21
+
22
+ ```svelte
23
+ <script lang="ts">
24
+ import { Calendar } from "@marianmeres/stuic";
25
+
26
+ let date = $state<string | null>(null); // "YYYY-MM-DD"
27
+ let start = $state<string | null>(null);
28
+ let end = $state<string | null>(null);
29
+ </script>
30
+
31
+ <!-- single date -->
32
+ <Calendar bind:value={date} showToday showClear />
33
+
34
+ <!-- range, two months, Sunday-first week, next 90 days only -->
35
+ <Calendar
36
+ mode="range"
37
+ bind:start
38
+ bind:end
39
+ months={2}
40
+ weekStartsOn={7}
41
+ min={todayIso()}
42
+ max={addDaysIso(todayIso(), 90)}
43
+ />
44
+
45
+ <!-- date of birth: dropdown caption -->
46
+ <Calendar
47
+ bind:value={dob}
48
+ captionLayout="dropdown"
49
+ yearRange={[1920, 2026]}
50
+ max={todayIso()}
51
+ />
52
+
53
+ <!-- blackout weekends -->
54
+ <Calendar bind:value={date} isDateDisabled={(_iso, cell) => cell.isWeekend} />
55
+ ```
56
+
57
+ ## Props
58
+
59
+ | Prop | Type | Default | Description |
60
+ | ----------------- | ---------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------- |
61
+ | `mode` | `"single" \| "range"` | `"single"` | `"single"` binds `value`; `"range"` binds `start` + `end` |
62
+ | `value` | `string \| null` | `null` | The selected date, `YYYY-MM-DD` (bindable; single mode) |
63
+ | `start`, `end` | `string \| null` | `null` | The range ends, `YYYY-MM-DD` (bindable; range mode) |
64
+ | `view` | `{ year, month }` | selection | The first displayed month (bindable). Defaults to the month of the selection, else the current month |
65
+ | `months` | `number` | `1` | Consecutive months rendered side by side (they wrap on narrow containers) |
66
+ | `min`, `max` | `string \| null` | - | Earliest / latest selectable date (inclusive); days and navigation beyond are disabled |
67
+ | `isDateDisabled` | `(iso, cell: DayCell) => boolean` | - | Disable individual days (blackouts, weekends, …). They stay keyboard-reachable, never selectable |
68
+ | `weekStartsOn` | `1 \| 2 \| … \| 7` | `1` | First day of the week, ISO numbering (1 = Monday … 7 = Sunday) |
69
+ | `weekendDays` | `Weekday[]` | `[6, 7]` | Days flagged `data-weekend` |
70
+ | `locale` | `string` | browser | BCP 47 locale of the day / month names and the spoken day labels |
71
+ | `zone` | `string` | local | IANA zone that decides which day is "today" |
72
+ | `captionLayout` | `"label" \| "dropdown"` | `"label"` | Plain "September 2026" caption, or native month + year `<select>`s (fast for far-away years) |
73
+ | `yearRange` | `[number, number]` | see note | Years offered by the dropdown caption. Defaults to the `min` / `max` years, else today −100 … +20 |
74
+ | `showWeekNumbers` | `boolean` | `false` | ISO week number column (always Monday-based, per ISO 8601, regardless of `weekStartsOn`) |
75
+ | `showOutsideDays` | `boolean` | `true` | Render the previous / next month's filler days (selectable; picking one navigates) |
76
+ | `fixedWeeks` | `boolean` | `false` | Always render 6 weeks so the height never jumps while navigating |
77
+ | `showToday` | `boolean` | `false` | Footer "Today" button — navigates to and focuses today (does **not** select it) |
78
+ | `showClear` | `boolean` | `false` | Footer "Clear" button |
79
+ | `disabled` | `boolean` | `false` | Whole calendar non-interactive |
80
+ | `focusOnMount` | `boolean` | `false` | Move focus into the grid once mounted (what the fields do when their dialog opens) |
81
+ | `t` | `TranslateFn` | English | UI texts — see [i18n](#i18n) |
82
+ | `onSelect` | `(value: string \| null) => void` | - | Single mode: a date was picked (or cleared) |
83
+ | `onRangeChange` | `(r: CalendarRangeChange) => void` | - | Range mode: `{ start, end, complete }` after every pick / clear (`complete` once both ends are set) |
84
+ | `onViewChange` | `(view: { year, month }) => void` | - | The displayed month changed (buttons, keyboard, dropdowns, an off-screen selection) |
85
+ | `unstyled` | `boolean` | `false` | Skip all `stuic-calendar*` classes (the nav / footer `Button`s keep theirs) |
86
+ | `class` | `string` | - | Additional classes for the root |
87
+ | `classMonth` | `string` | - | Each month block |
88
+ | `classHeader` | `string` | - | Each month's header row (prev / caption / next) |
89
+ | `classCaption` | `string` | - | The caption |
90
+ | `classGrid` | `string` | - | The `<table>` |
91
+ | `classWeekday` | `string` | - | The weekday header cells |
92
+ | `classCell` | `string` | - | The day cells (`<td>`) |
93
+ | `classDay` | `string` | - | The day buttons |
94
+ | `classFooter` | `string` | - | The footer row |
95
+ | `el` | `HTMLDivElement` | - | Root element reference (bindable) |
96
+
97
+ Any other attribute (`aria-label`, `data-*`, …) is passed to the root `<div role="group">`.
98
+
99
+ ### Values
100
+
101
+ Every date is a plain `YYYY-MM-DD` string — zone-independent, sortable, what
102
+ `<input type="date">` and most APIs already speak. Loose input is tolerated on the way
103
+ in: a datetime string is reduced to its date portion **as written** (never shifted
104
+ through a zone), a `Date` to its local calendar date, a Luxon `DateTime` to its own-zone
105
+ date; unparseable input counts as empty. What the component writes back is always strict
106
+ ISO. The helpers are exported: `isIsoDate`, `parseIsoDate`, `normalizeIsoDate`,
107
+ `toIsoDate`, `todayIso`, `addDaysIso`, `addMonthsIso`, `compareIso`, `daysBetweenIso`,
108
+ `rangeLengthIso`, `formatIsoDate`, `formatIsoDateRange`.
109
+
110
+ ### Range selection
111
+
112
+ The first pick is the **anchor**, the second completes the range — in either order (an
113
+ earlier second pick becomes the start). A third pick starts over. While an anchor waits,
114
+ the hovered (mouse) or focused (keyboard) day previews the range. A `start` passed in
115
+ without an `end` is treated as a waiting anchor, so one more pick completes it.
116
+
117
+ ## Snippets
118
+
119
+ | Snippet | Parameters | Description |
120
+ | ----------- | ------------------ | ----------------------------------------------------------------------------------------- |
121
+ | `renderDay` | `CalendarDayState` | Custom day content (event dots, prices, …). The button, its states and a11y stay in place |
122
+ | `footer` | - | Extra footer content, on the end side of the Today / Clear buttons (e.g. a "Done" button) |
123
+
124
+ `CalendarDayState`: `{ iso, day, cell, selected, rangeStart, rangeEnd, inRange, preview, today, outside, weekend, disabled }` — `cell` is the calendar-utils `DayCell` (Luxon `date`, `weekNumber`, …).
125
+
126
+ ```svelte
127
+ <Calendar bind:value={date}>
128
+ {#snippet renderDay(s)}
129
+ <span class="relative">
130
+ {s.day}
131
+ {#if events.has(s.iso)}<span
132
+ class="absolute -bottom-2 size-1 rounded-full bg-current"
133
+ ></span>{/if}
134
+ </span>
135
+ {/snippet}
136
+ </Calendar>
137
+ ```
138
+
139
+ ## Methods
140
+
141
+ Accessed via a `bind:this` reference.
142
+
143
+ | Method | Description |
144
+ | --------------------------- | ---------------------------------------------------- |
145
+ | `focus()` | Focus the day that is currently in the tab order |
146
+ | `prevMonth()`/`nextMonth()` | Step the view one month (no-op beyond `min` / `max`) |
147
+ | `goToMonth(year, month)` | Show a specific month |
148
+ | `goToDate(iso)` | Bring the month containing `iso` on screen |
149
+ | `goToToday()` | Navigate to and focus today (does not select it) |
150
+ | `getView()` | The first displayed month, `{ year, month }` |
151
+
152
+ ## Keyboard
153
+
154
+ The grid is a single tab stop (roving tabindex on the day buttons):
155
+
156
+ | Key | Action |
157
+ | --------------------------- | --------------------------------------------------------- |
158
+ | `←` / `→` | Previous / next day |
159
+ | `↑` / `↓` | Same weekday, previous / next week |
160
+ | `Home` / `End` | First / last day of the week (per `weekStartsOn`) |
161
+ | `PageUp` / `PageDown` | Same day, previous / next month (clamped to month length) |
162
+ | `Shift+PageUp` / `PageDown` | Same day, previous / next year |
163
+ | `Enter` / `Space` | Select the focused day |
164
+
165
+ Stepping past the visible months navigates the view; stepping is clamped to `min` /
166
+ `max`. Disabled days are focusable (so the grid stays navigable) but cannot be selected.
167
+
168
+ ## Accessibility
169
+
170
+ - `<table role="grid" aria-labelledby={caption}>`; each day is a `<button>` inside a
171
+ `<td role="gridcell" aria-selected>` with a full spoken `aria-label`
172
+ ("Tuesday, February 10, 2026, today"), `aria-current="date"` on today and
173
+ `aria-disabled` on unavailable days.
174
+ - Selections and the "select an end date" step are announced through a polite live
175
+ region; nav buttons and the dropdown selects are labelled.
176
+ - Day buttons are 40px, 44px on coarse pointers (`--stuic-calendar-day-size`).
177
+
178
+ ## Data attributes
179
+
180
+ Styling hooks, set only when applicable (empty-string attributes):
181
+
182
+ - root: `data-mode`, `data-months`, `data-disabled`
183
+ - day button: `data-iso`, `data-selected`, `data-range-start`, `data-range-end`,
184
+ `data-in-range` (strictly inside a complete range), `data-preview`, `data-today`,
185
+ `data-outside`, `data-weekend`, `aria-disabled`
186
+ - day cell (`<td>`): `data-in-range` (the whole committed band, ends included),
187
+ `data-preview`, `data-band-start`, `data-band-end` (rounded outer corners), `data-outside`
188
+
189
+ ## i18n
190
+
191
+ Day and month names come from `Intl` (`locale`). Everything else — nav labels, Today /
192
+ Clear / Done, the live announcements, the fields' placeholder / dialog title / validation
193
+ messages — goes through `t`. English is built in; Slovak ships bundled and opt-in
194
+ (importing it is what pulls it into your bundle). Placeholders are mustache-style
195
+ (`{{value}}`).
196
+
197
+ ```svelte
198
+ <script>
199
+ import { Calendar, createCalendarT, CALENDAR_MESSAGES_SK } from "@marianmeres/stuic";
200
+ const t = createCalendarT(CALENDAR_MESSAGES_SK);
201
+ // or partial: reword one text, keep English elsewhere
202
+ const t2 = createCalendarT({ today: "Now" });
203
+ </script>
204
+
205
+ <Calendar bind:value locale="sk" {t} showToday />
206
+ ```
207
+
208
+ | Key | English | Used by |
209
+ | ------------------------------------------ | ----------------------------------------------------------- | -------------------------------------- |
210
+ | `prev_month` / `next_month` | Previous month / Next month | Nav buttons |
211
+ | `month` / `year` | Month / Year | Dropdown caption labels (sr-only) |
212
+ | `week` | Wk | Week-number column header |
213
+ | `week_number` | Week {{value}} | Week-number cell label |
214
+ | `day_today` | today | Appended to today's spoken label |
215
+ | `today` / `clear` / `done` / `close` | Today / Clear / Done / Close | Footer + dialog buttons |
216
+ | `select_start` / `select_end` | Select a start date / Select an end date | Live region (range) |
217
+ | `selected_date` / `selected_range` | Selected {{value}} / Selected {{start}} to {{end}} | Live region |
218
+ | `placeholder_date` / `placeholder_range` | Select a date / Select a date range | `FieldDate` / `FieldDateRange` trigger |
219
+ | `dialog_title_date` / `dialog_title_range` | Select date / Select date range | Field dialog title |
220
+ | `clear_value` | Clear selection | Field × button |
221
+ | `field_req_att` | This field requires attention. Please review and try again. | Field validation (required) |
222
+ | `date_invalid` | Please enter a valid date. | Field validation |
223
+ | `date_disabled` | This date is not available. | Field validation (`isDateDisabled`) |
224
+ | `date_before_min` / `date_after_max` | Date must be on or after/before {{value}}. | Field validation (`min` / `max`) |
225
+ | `range_incomplete` | Please select both a start and an end date. | `FieldDateRange` validation |
226
+
227
+ ## CSS Variables
228
+
229
+ | Variable | Default | Description |
230
+ | ---------------------------------------- | ----------------------------------------- | ------------------------------------------ |
231
+ | `--stuic-calendar-bg` | `transparent` | Root background |
232
+ | `--stuic-calendar-text` | `var(--stuic-color-foreground)` | Root text color |
233
+ | `--stuic-calendar-font-family` | `var(--font-sans)` | Font family |
234
+ | `--stuic-calendar-gap` | `1.5rem` | Gap between months (`months > 1`) |
235
+ | `--stuic-calendar-header-gap` | `0.25rem` | Gap in the header row |
236
+ | `--stuic-calendar-header-margin-bottom` | `0.5rem` | Space between header and grid |
237
+ | `--stuic-calendar-footer-gap` | `0.5rem` | Gap between footer buttons |
238
+ | `--stuic-calendar-footer-margin-top` | `0.5rem` | Space above the footer |
239
+ | `--stuic-calendar-day-size` | `2.5rem` (`2.75rem` on `pointer: coarse`) | Width and height of a day cell |
240
+ | `--stuic-calendar-day-radius` | `var(--stuic-radius)` | Day button / band corner radius |
241
+ | `--stuic-calendar-day-font-size` | `var(--text-sm)` | Day number size |
242
+ | `--stuic-calendar-day-font-weight` | `var(--font-weight-normal)` | Day number weight |
243
+ | `--stuic-calendar-day-text` | `var(--stuic-color-foreground)` | Day text |
244
+ | `--stuic-calendar-day-bg-hover` | `var(--stuic-color-muted)` | Day hover background |
245
+ | `--stuic-calendar-day-text-outside` | `var(--stuic-color-muted-foreground)` | Filler (adjacent month) day text |
246
+ | `--stuic-calendar-day-opacity-outside` | `0.6` | Filler day opacity |
247
+ | `--stuic-calendar-day-text-weekend` | (inherits day text) | Weekend day text — set it to tint weekends |
248
+ | `--stuic-calendar-day-opacity-disabled` | `0.35` | Disabled day opacity |
249
+ | `--stuic-calendar-day-bg-selected` | `var(--stuic-color-primary)` | Selected day / range end background |
250
+ | `--stuic-calendar-day-text-selected` | `var(--stuic-color-primary-foreground)` | Selected day text |
251
+ | `--stuic-calendar-day-bg-selected-hover` | `var(--stuic-color-primary-hover)` | Selected day hover background |
252
+ | `--stuic-calendar-day-bg-range` | primary @ 15% | The band behind a complete range |
253
+ | `--stuic-calendar-day-text-range` | `var(--stuic-color-foreground)` | Text of days inside the range |
254
+ | `--stuic-calendar-day-bg-preview` | primary @ 8% | The band behind the hover / focus preview |
255
+ | `--stuic-calendar-today-text` | `var(--stuic-color-primary)` | Today's number color (when not selected) |
256
+ | `--stuic-calendar-today-font-weight` | `var(--font-weight-semibold)` | Today's number weight |
257
+ | `--stuic-calendar-weekday-text` | `var(--stuic-color-muted-foreground)` | Weekday header text |
258
+ | `--stuic-calendar-weekday-font-size` | `var(--text-xs)` | Weekday header size |
259
+ | `--stuic-calendar-weekday-font-weight` | `var(--font-weight-medium)` | Weekday header weight |
260
+ | `--stuic-calendar-week-number-text` | `var(--stuic-color-muted-foreground)` | Week-number column text |
261
+ | `--stuic-calendar-week-number-font-size` | `var(--text-xs)` | Week-number size |
262
+ | `--stuic-calendar-caption-text` | `var(--stuic-color-foreground)` | Caption text |
263
+ | `--stuic-calendar-caption-font-size` | `var(--text-base)` | Caption size |
264
+ | `--stuic-calendar-caption-font-weight` | `var(--font-weight-semibold)` | Caption weight |
265
+ | `--stuic-calendar-select-bg` | `var(--stuic-color-input)` | Dropdown caption select background |
266
+ | `--stuic-calendar-select-text` | `var(--stuic-color-foreground)` | Dropdown caption select text |
267
+ | `--stuic-calendar-select-border` | `var(--stuic-color-border)` | Dropdown caption select border |
268
+ | `--stuic-calendar-select-radius` | `var(--stuic-radius)` | Dropdown caption select radius |
269
+ | `--stuic-calendar-border-width` | `var(--stuic-border-width)` | Dropdown caption select border width |
270
+ | `--stuic-calendar-ring-width` | `2px` | Focus ring width |
271
+ | `--stuic-calendar-ring-color` | `var(--stuic-color-ring)` | Focus ring color |
272
+ | `--stuic-calendar-transition` | `var(--stuic-transition)` | Day background / color transition |
273
+
274
+ ```css
275
+ /* circles instead of rounded squares, bigger cells */
276
+ :root {
277
+ --stuic-calendar-day-radius: 9999px;
278
+ --stuic-calendar-day-size: 3rem;
279
+ }
280
+ ```
281
+
282
+ ## Notes
283
+
284
+ - The calendar is `display: inline-flex` and sizes to its content (7 × day size per
285
+ month); put it in a flex/grid parent to center it.
286
+ - `view` is only defaulted once (from the selection at mount). Afterwards the view
287
+ follows the user; an externally written selection that is off screen is brought on
288
+ screen, browsing away from it is never undone.
289
+ - `today` is computed when the grid is (re)built; a calendar left open across midnight
290
+ updates on the next navigation.
291
+ - `weekNumber` is the ISO week (Monday-based) even with a Sunday-first `weekStartsOn` —
292
+ a calendar-utils convention, documented there.
293
+ - The nav / footer buttons are stuic `Button`s (`variant="ghost" size="sm"`) and keep
294
+ their own styling under `unstyled`.
@@ -0,0 +1,20 @@
1
+ import type { CalendarMessages } from "./calendar-i18n.js";
2
+ /**
3
+ * Slovak message catalog for `Calendar`, `FieldDate` and `FieldDateRange`. Opt-in —
4
+ * English stays the built-in default, and this module is only pulled into a bundle
5
+ * when it is actually imported (the components themselves never reference it).
6
+ *
7
+ * Pair it with `locale="sk"` so the day and month names (which come from `Intl`)
8
+ * match.
9
+ *
10
+ * @example
11
+ * ```svelte
12
+ * <script>
13
+ * import { FieldDate, createCalendarT, CALENDAR_MESSAGES_SK } from "@marianmeres/stuic";
14
+ * const t = createCalendarT(CALENDAR_MESSAGES_SK);
15
+ * </script>
16
+ *
17
+ * <FieldDate name="from" bind:value locale="sk" {t} />
18
+ * ```
19
+ */
20
+ export declare const CALENDAR_MESSAGES_SK: CalendarMessages;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Slovak message catalog for `Calendar`, `FieldDate` and `FieldDateRange`. Opt-in —
3
+ * English stays the built-in default, and this module is only pulled into a bundle
4
+ * when it is actually imported (the components themselves never reference it).
5
+ *
6
+ * Pair it with `locale="sk"` so the day and month names (which come from `Intl`)
7
+ * match.
8
+ *
9
+ * @example
10
+ * ```svelte
11
+ * <script>
12
+ * import { FieldDate, createCalendarT, CALENDAR_MESSAGES_SK } from "@marianmeres/stuic";
13
+ * const t = createCalendarT(CALENDAR_MESSAGES_SK);
14
+ * </script>
15
+ *
16
+ * <FieldDate name="from" bind:value locale="sk" {t} />
17
+ * ```
18
+ */
19
+ export const CALENDAR_MESSAGES_SK = {
20
+ prev_month: "Predchádzajúci mesiac",
21
+ next_month: "Nasledujúci mesiac",
22
+ month: "Mesiac",
23
+ year: "Rok",
24
+ week: "Týž.",
25
+ week_number: "Týždeň {{value}}",
26
+ day_today: "dnes",
27
+ today: "Dnes",
28
+ clear: "Zrušiť výber",
29
+ done: "Hotovo",
30
+ close: "Zavrieť",
31
+ select_start: "Vyberte začiatok obdobia",
32
+ select_end: "Vyberte koniec obdobia",
33
+ selected_date: "Vybraný dátum {{value}}",
34
+ selected_range: "Vybrané obdobie od {{start}} do {{end}}",
35
+ placeholder_date: "Vyberte dátum",
36
+ placeholder_range: "Vyberte obdobie",
37
+ dialog_title_date: "Výber dátumu",
38
+ dialog_title_range: "Výber obdobia",
39
+ clear_value: "Zrušiť výber",
40
+ field_req_att: "Toto pole vyžaduje pozornosť. Skontrolujte ho a skúste to znova.",
41
+ date_invalid: "Zadajte, prosím, platný dátum.",
42
+ date_disabled: "Tento dátum nie je k dispozícii.",
43
+ date_before_min: "Dátum musí byť najskôr {{value}}.",
44
+ date_after_max: "Dátum musí byť najneskôr {{value}}.",
45
+ range_incomplete: "Vyberte, prosím, začiatok aj koniec obdobia.",
46
+ };
@@ -0,0 +1,61 @@
1
+ import type { TranslateFn } from "../../types.js";
2
+ /**
3
+ * The built-in (English) message catalog of `Calendar`, `FieldDate` and
4
+ * `FieldDateRange`. Also the fallback of every other bundled locale, so a locale
5
+ * missing a key still renders text.
6
+ *
7
+ * Day and month names are NOT in here — they come from `Intl` via the `locale` prop.
8
+ * Placeholders are mustache-style (`{{value}}`).
9
+ */
10
+ export declare const CALENDAR_MESSAGES_EN: {
11
+ prev_month: string;
12
+ next_month: string;
13
+ month: string;
14
+ year: string;
15
+ week: string;
16
+ week_number: string;
17
+ day_today: string;
18
+ today: string;
19
+ clear: string;
20
+ done: string;
21
+ close: string;
22
+ select_start: string;
23
+ select_end: string;
24
+ selected_date: string;
25
+ selected_range: string;
26
+ placeholder_date: string;
27
+ placeholder_range: string;
28
+ dialog_title_date: string;
29
+ dialog_title_range: string;
30
+ clear_value: string;
31
+ field_req_att: string;
32
+ date_invalid: string;
33
+ date_disabled: string;
34
+ date_before_min: string;
35
+ date_after_max: string;
36
+ range_incomplete: string;
37
+ };
38
+ /** Every message key the calendar components may look up. */
39
+ export type CalendarMessageKey = keyof typeof CALENDAR_MESSAGES_EN;
40
+ /** A (possibly partial) catalog for one locale. */
41
+ export type CalendarMessages = Record<CalendarMessageKey, string>;
42
+ /**
43
+ * Builds the `t` prop of `Calendar` / `FieldDate` / `FieldDateRange` from a message
44
+ * catalog. Unknown or untranslated keys fall back to `fallbackMessages` (English by
45
+ * default), so a catalog may safely be partial and never renders a raw key.
46
+ *
47
+ * @example
48
+ * ```svelte
49
+ * <script>
50
+ * import { FieldDate, createCalendarT, CALENDAR_MESSAGES_SK } from "@marianmeres/stuic";
51
+ *
52
+ * // full locale, plus a field specific tweak
53
+ * const t = createCalendarT({ ...CALENDAR_MESSAGES_SK, placeholder_date: "Dátum narodenia" });
54
+ * </script>
55
+ *
56
+ * <FieldDate name="born" bind:value locale="sk" {t} />
57
+ * ```
58
+ */
59
+ export declare function createCalendarT(messages: Partial<CalendarMessages> | Record<string, string>, fallbackMessages?: Partial<CalendarMessages> | Record<string, string>): TranslateFn;
60
+ /** The components' built-in English `t`. */
61
+ export declare const t_default: TranslateFn;
@@ -0,0 +1,73 @@
1
+ import { isPlainObject } from "../../utils/is-plain-object.js";
2
+ import { replaceMap } from "../../utils/replace-map.js";
3
+ /**
4
+ * The built-in (English) message catalog of `Calendar`, `FieldDate` and
5
+ * `FieldDateRange`. Also the fallback of every other bundled locale, so a locale
6
+ * missing a key still renders text.
7
+ *
8
+ * Day and month names are NOT in here — they come from `Intl` via the `locale` prop.
9
+ * Placeholders are mustache-style (`{{value}}`).
10
+ */
11
+ export const CALENDAR_MESSAGES_EN = {
12
+ // navigation
13
+ prev_month: "Previous month",
14
+ next_month: "Next month",
15
+ month: "Month",
16
+ year: "Year",
17
+ // grid
18
+ week: "Wk",
19
+ week_number: "Week {{value}}",
20
+ day_today: "today",
21
+ // footer
22
+ today: "Today",
23
+ clear: "Clear",
24
+ done: "Done",
25
+ close: "Close",
26
+ // live announcements
27
+ select_start: "Select a start date",
28
+ select_end: "Select an end date",
29
+ selected_date: "Selected {{value}}",
30
+ selected_range: "Selected {{start}} to {{end}}",
31
+ // fields (trigger + dialog)
32
+ placeholder_date: "Select a date",
33
+ placeholder_range: "Select a date range",
34
+ dialog_title_date: "Select date",
35
+ dialog_title_range: "Select date range",
36
+ clear_value: "Clear selection",
37
+ // validation
38
+ field_req_att: "This field requires attention. Please review and try again.",
39
+ date_invalid: "Please enter a valid date.",
40
+ date_disabled: "This date is not available.",
41
+ date_before_min: "Date must be on or after {{value}}.",
42
+ date_after_max: "Date must be on or before {{value}}.",
43
+ range_incomplete: "Please select both a start and an end date.",
44
+ };
45
+ /**
46
+ * Builds the `t` prop of `Calendar` / `FieldDate` / `FieldDateRange` from a message
47
+ * catalog. Unknown or untranslated keys fall back to `fallbackMessages` (English by
48
+ * default), so a catalog may safely be partial and never renders a raw key.
49
+ *
50
+ * @example
51
+ * ```svelte
52
+ * <script>
53
+ * import { FieldDate, createCalendarT, CALENDAR_MESSAGES_SK } from "@marianmeres/stuic";
54
+ *
55
+ * // full locale, plus a field specific tweak
56
+ * const t = createCalendarT({ ...CALENDAR_MESSAGES_SK, placeholder_date: "Dátum narodenia" });
57
+ * </script>
58
+ *
59
+ * <FieldDate name="born" bind:value locale="sk" {t} />
60
+ * ```
61
+ */
62
+ export function createCalendarT(messages, fallbackMessages = CALENDAR_MESSAGES_EN) {
63
+ return (k, values = null, fallback = "") => {
64
+ const out = messages[k] ??
65
+ fallbackMessages[k] ??
66
+ (typeof fallback === "string" ? fallback : k);
67
+ return isPlainObject(values)
68
+ ? replaceMap(out, values)
69
+ : out;
70
+ };
71
+ }
72
+ /** The components' built-in English `t`. */
73
+ export const t_default = createCalendarT(CALENDAR_MESSAGES_EN);