@marianmeres/stuic 3.171.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 (53) 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/Input/FieldDate.svelte +349 -0
  19. package/dist/components/Input/FieldDate.svelte.d.ts +79 -0
  20. package/dist/components/Input/FieldDateRange.svelte +373 -0
  21. package/dist/components/Input/FieldDateRange.svelte.d.ts +90 -0
  22. package/dist/components/Input/README.md +149 -16
  23. package/dist/components/Input/_internal/FieldDateShell.svelte +327 -0
  24. package/dist/components/Input/_internal/FieldDateShell.svelte.d.ts +66 -0
  25. package/dist/components/Input/index.css +119 -0
  26. package/dist/components/Input/index.d.ts +2 -0
  27. package/dist/components/Input/index.js +2 -0
  28. package/dist/components/ModalDialog/ModalDialog.fixture.svelte +1 -1
  29. package/dist/components/SlidingPanels/SlidingPanels.fixture.svelte +1 -1
  30. package/dist/icons/index.d.ts +1 -0
  31. package/dist/icons/index.js +1 -0
  32. package/dist/index.css +1 -0
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.js +1 -0
  35. package/docs/_archive/README.md +10 -0
  36. package/docs/{component-testing → _archive/component-testing}/00-overview-and-roadmap.md +8 -6
  37. package/docs/{component-testing → _archive/component-testing}/01-framework-setup.md +4 -2
  38. package/docs/{component-testing → _archive/component-testing}/03-component-coverage-roadmap.md +3 -1
  39. package/docs/{component-testing → _archive/component-testing}/04-hard-cases-and-e2e.md +5 -3
  40. package/docs/{component-testing → _archive/component-testing}/05-ci.md +2 -0
  41. package/docs/{component-testing → _archive/component-testing}/PROGRESS.md +3 -1
  42. package/docs/_archive/component-testing/README.md +28 -0
  43. package/docs/{upgrading.md → _archive/upgrading.md} +2 -0
  44. package/docs/architecture.md +19 -11
  45. package/docs/domains/actions.md +4 -3
  46. package/docs/domains/components.md +22 -19
  47. package/docs/domains/utils.md +2 -1
  48. package/docs/maybe-todo.md +6 -6
  49. package/docs/tasks.md +19 -9
  50. package/docs/{component-testing/02-test-conventions.md → testing-components.md} +30 -31
  51. package/docs/testing.md +3 -3
  52. package/package.json +2 -1
  53. package/docs/component-testing/README.md +0 -38
@@ -0,0 +1,349 @@
1
+ <!--
2
+ FieldDate — pick a single calendar date.
3
+
4
+ The bound `value` is an ISO `YYYY-MM-DD` string (or null). The user picks it in
5
+ a `Calendar`, either in a dialog opened from a trigger button that shows the
6
+ formatted date (default), or inline inside the field (`embedded`). The `name`
7
+ goes on a hidden input carrying the ISO value, so a <form> submits
8
+ `2026-09-02` regardless of how the date is displayed.
9
+
10
+ Loose input (`"2026-09-02T10:00:00Z"`, a `Date`) is normalized to its calendar
11
+ date for display and submission; garbage is submitted as-is so the validator
12
+ can flag it. `required` / `min` / `max` / `isDateDisabled` are enforced in the
13
+ validator because a hidden input never validates natively.
14
+ -->
15
+ <script lang="ts" module>
16
+ import type { Snippet } from "svelte";
17
+ import type { ValidateOptions } from "../../actions/validate.svelte.js";
18
+ import type { TranslateFn } from "../../types.js";
19
+ import type { THC } from "../Thc/Thc.svelte";
20
+ import type { Props as CalendarProps } from "../Calendar/Calendar.svelte";
21
+ import type { IsoDate } from "../Calendar/iso-date.js";
22
+ import type { InputWrapClassProps } from "./types.js";
23
+
24
+ type SnippetWithId = Snippet<[{ id: string }]>;
25
+
26
+ /** The `Calendar` props a date field lifts to its own top level. */
27
+ export type FieldDateCalendarProps = Pick<
28
+ CalendarProps,
29
+ | "min"
30
+ | "max"
31
+ | "isDateDisabled"
32
+ | "weekStartsOn"
33
+ | "weekendDays"
34
+ | "zone"
35
+ | "captionLayout"
36
+ | "yearRange"
37
+ | "showWeekNumbers"
38
+ | "showOutsideDays"
39
+ | "fixedWeeks"
40
+ | "showToday"
41
+ | "renderDay"
42
+ >;
43
+
44
+ export interface Props extends InputWrapClassProps, FieldDateCalendarProps {
45
+ /** The selected date as `YYYY-MM-DD` (bindable). `null` / `""` = empty. */
46
+ value?: IsoDate | null;
47
+ /** Form field name — applied to the hidden input carrying the ISO date. */
48
+ name?: string;
49
+ /** Render the calendar inline inside the field instead of a trigger + dialog. */
50
+ embedded?: boolean;
51
+ /** Close the dialog right after a date is picked. Default `true`. */
52
+ closeOnSelect?: boolean;
53
+ /** Trigger text while empty. Defaults to `t("placeholder_date")`. */
54
+ placeholder?: string;
55
+ /** Offer clearing (trailing × on the trigger / "Clear" in the embedded footer). Default `true`. */
56
+ clearable?: boolean;
57
+ /** BCP 47 locale of the display text and the calendar names. Browser default when omitted. */
58
+ locale?: string;
59
+ /** `Intl.DateTimeFormat` options of the display text. Default `{ dateStyle: "medium" }`. */
60
+ formatOptions?: Intl.DateTimeFormatOptions;
61
+ /** Custom display formatter (wins over `formatOptions`). */
62
+ format?: (iso: IsoDate) => string;
63
+ /** Months the calendar shows side by side. Default `1`. */
64
+ months?: number;
65
+ /** Anything else for the inner `Calendar` (class hooks, `view`, …). Applied last. */
66
+ calendarProps?: Partial<CalendarProps>;
67
+ /** The hidden input (bindable). */
68
+ input?: HTMLInputElement;
69
+ label?: SnippetWithId | THC;
70
+ description?: SnippetWithId | THC;
71
+ labelAfter?: SnippetWithId | THC;
72
+ below?: SnippetWithId | THC;
73
+ class?: string;
74
+ id?: string;
75
+ tabindex?: number;
76
+ renderSize?: "sm" | "md" | "lg" | string;
77
+ required?: boolean;
78
+ disabled?: boolean;
79
+ /**
80
+ * Validation. `false` disables the built-in guard (required / valid ISO / min /
81
+ * max / disabled dates); an options object is merged with it (your
82
+ * `customValidator` runs after the built-in checks pass). Default: the guard.
83
+ */
84
+ validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
85
+ labelLeft?: boolean;
86
+ labelLeftWidth?: "normal" | "wide";
87
+ labelLeftBreakpoint?: number;
88
+ /** Classes for the trigger button */
89
+ classInput?: string;
90
+ /** Classes for the dialog box */
91
+ classDialog?: string;
92
+ style?: string;
93
+ noScrollLock?: boolean;
94
+ /** i18n — see `createCalendarT`. */
95
+ t?: TranslateFn;
96
+ /** Called after the value changed through the picker or the × (not on external writes). */
97
+ onChange?: (value: IsoDate | null) => void;
98
+ }
99
+ </script>
100
+
101
+ <script lang="ts">
102
+ import { tick } from "svelte";
103
+ import type { ValidationResult } from "../../actions/validate.svelte.js";
104
+ import { getId } from "../../utils/get-id.js";
105
+ import Button from "../Button/Button.svelte";
106
+ import Calendar from "../Calendar/Calendar.svelte";
107
+ import { t_default } from "../Calendar/calendar-i18n.js";
108
+ import {
109
+ compareIso,
110
+ dayCellOf,
111
+ DEFAULT_DATE_FORMAT,
112
+ formatIsoDate,
113
+ normalizeIsoDate,
114
+ } from "../Calendar/iso-date.js";
115
+ import FieldDateShell from "./_internal/FieldDateShell.svelte";
116
+
117
+ let {
118
+ value = $bindable(null),
119
+ name,
120
+ embedded = false,
121
+ closeOnSelect = true,
122
+ placeholder,
123
+ clearable = true,
124
+ locale,
125
+ formatOptions,
126
+ format,
127
+ months = 1,
128
+ calendarProps,
129
+ input = $bindable(),
130
+ label = "",
131
+ description,
132
+ labelAfter,
133
+ below,
134
+ class: classProp,
135
+ id = getId(),
136
+ tabindex = 0,
137
+ renderSize = "md",
138
+ required = false,
139
+ disabled = false,
140
+ // Renamed local binding to avoid collision with `export function validate()` below.
141
+ validate: validateProp,
142
+ labelLeft = false,
143
+ labelLeftWidth = "normal",
144
+ labelLeftBreakpoint = 480,
145
+ classInput,
146
+ classDialog,
147
+ style,
148
+ noScrollLock = false,
149
+ t = t_default,
150
+ onChange,
151
+ // calendar
152
+ min,
153
+ max,
154
+ isDateDisabled,
155
+ weekStartsOn,
156
+ weekendDays,
157
+ zone,
158
+ captionLayout,
159
+ yearRange,
160
+ showWeekNumbers,
161
+ showOutsideDays,
162
+ fixedWeeks,
163
+ showToday,
164
+ renderDay,
165
+ // wrapper class props
166
+ classLabel,
167
+ classLabelBox,
168
+ classInputBox,
169
+ classInputBoxWrap,
170
+ classInputBoxWrapInvalid,
171
+ classDescBox,
172
+ classDescBoxToggle,
173
+ classBelowBox,
174
+ classValidationBox,
175
+ }: Props = $props();
176
+
177
+ let shell: FieldDateShell | undefined = $state();
178
+
179
+ let normalized = $derived(normalizeIsoDate(value));
180
+ let _min = $derived(normalizeIsoDate(min));
181
+ let _max = $derived(normalizeIsoDate(max));
182
+
183
+ // What the form submits: the normalized date — or the raw (unparseable) value,
184
+ // so the validator can flag it instead of silently submitting "".
185
+ let submitValue = $derived(normalized ?? (value == null ? "" : `${value}`));
186
+
187
+ const fmt = (iso: IsoDate): string =>
188
+ format
189
+ ? format(iso)
190
+ : formatIsoDate(iso, locale, formatOptions ?? DEFAULT_DATE_FORMAT);
191
+
192
+ let display = $derived(normalized ? fmt(normalized) : "");
193
+
194
+ function commit(next: IsoDate | null) {
195
+ value = next;
196
+ onChange?.(next);
197
+ // let the hidden input's value flush before the validate action reads it
198
+ tick().then(() => shell?.dispatchChange());
199
+ }
200
+
201
+ function onSelect(next: IsoDate | null) {
202
+ commit(next);
203
+ if (!embedded && closeOnSelect && next) shell?.close();
204
+ }
205
+
206
+ // Built-in guard (a hidden input never validates natively): required, a valid
207
+ // ISO date, min / max, disabled dates. Consumer opts are honored: `false`
208
+ // disables; an object is merged (its customValidator runs after the guard).
209
+ const validateOpts = $derived.by<
210
+ boolean | Omit<ValidateOptions, "setValidationResult">
211
+ >(() => {
212
+ if (validateProp === false) return false;
213
+ const custom = validateProp && typeof validateProp === "object" ? validateProp : {};
214
+ return {
215
+ ...custom,
216
+ customValidator: (val, ctx, el) => {
217
+ const str = `${val ?? ""}`.trim();
218
+ if (!str) {
219
+ if (required) return t("field_req_att");
220
+ return custom.customValidator?.(val, ctx, el) || "";
221
+ }
222
+ const iso = normalizeIsoDate(str);
223
+ if (!iso) return t("date_invalid");
224
+ if (_min && compareIso(iso, _min) < 0) {
225
+ return t("date_before_min", { value: fmt(_min) });
226
+ }
227
+ if (_max && compareIso(iso, _max) > 0) {
228
+ return t("date_after_max", { value: fmt(_max) });
229
+ }
230
+ if (isDateDisabled?.(iso, dayCellOf(iso, { zone, weekendDays }))) {
231
+ return t("date_disabled");
232
+ }
233
+ return custom.customValidator?.(val, ctx, el) || "";
234
+ },
235
+ };
236
+ });
237
+
238
+ // ---- Imperative API (satisfies ValidatableField), forwarded to the shell ----
239
+
240
+ /** Trigger validation now. Renders the inline message if invalid. */
241
+ export function validate(): ValidationResult | undefined {
242
+ return shell?.validate();
243
+ }
244
+
245
+ /** Clear the inline validation message and reset `setCustomValidity`. */
246
+ export function clearValidation(): void {
247
+ shell?.clearValidation();
248
+ }
249
+
250
+ /** Current validation state, or undefined if the validator has never run. */
251
+ export function getValidation(): ValidationResult | undefined {
252
+ return shell?.getValidation();
253
+ }
254
+
255
+ /** Focus the trigger (or the embedded calendar's tabbable day). */
256
+ export function focus(): void {
257
+ shell?.focus();
258
+ }
259
+
260
+ /** Scroll the field into view. Defaults to smooth + center. */
261
+ export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
262
+ shell?.scrollIntoView(opts);
263
+ }
264
+
265
+ /** Open the picker dialog (no-op when `embedded`). */
266
+ export function open(): void {
267
+ shell?.open();
268
+ }
269
+
270
+ /** Close the picker dialog. */
271
+ export function close(): void {
272
+ shell?.close();
273
+ }
274
+ </script>
275
+
276
+ {#snippet doneFooter()}
277
+ <Button type="button" size="sm" intent="primary" onclick={() => shell?.close()}>
278
+ {t("done")}
279
+ </Button>
280
+ {/snippet}
281
+
282
+ <FieldDateShell
283
+ bind:this={shell}
284
+ bind:input
285
+ {id}
286
+ {label}
287
+ {description}
288
+ {labelAfter}
289
+ {below}
290
+ class={classProp}
291
+ {style}
292
+ {renderSize}
293
+ {required}
294
+ {disabled}
295
+ {tabindex}
296
+ {labelLeft}
297
+ {labelLeftWidth}
298
+ {labelLeftBreakpoint}
299
+ {classLabel}
300
+ {classLabelBox}
301
+ {classInputBox}
302
+ {classInputBoxWrap}
303
+ {classInputBoxWrapInvalid}
304
+ {classDescBox}
305
+ {classDescBoxToggle}
306
+ {classBelowBox}
307
+ {classValidationBox}
308
+ {display}
309
+ placeholder={placeholder ?? t("placeholder_date")}
310
+ {clearable}
311
+ clearLabel={t("clear_value")}
312
+ {embedded}
313
+ inputs={[{ name, value: submitValue }]}
314
+ validate={validateOpts}
315
+ dialogTitle={t("dialog_title_date")}
316
+ closeLabel={t("close")}
317
+ {noScrollLock}
318
+ {classInput}
319
+ {classDialog}
320
+ onClear={() => commit(null)}
321
+ >
322
+ <Calendar
323
+ mode="single"
324
+ value={normalized}
325
+ {months}
326
+ {min}
327
+ {max}
328
+ {isDateDisabled}
329
+ {weekStartsOn}
330
+ {weekendDays}
331
+ {zone}
332
+ {locale}
333
+ {captionLayout}
334
+ {yearRange}
335
+ {showWeekNumbers}
336
+ {showOutsideDays}
337
+ {fixedWeeks}
338
+ {showToday}
339
+ showClear={embedded && clearable}
340
+ {renderDay}
341
+ {disabled}
342
+ {t}
343
+ focusOnMount={!embedded}
344
+ aria-labelledby={embedded && label ? `${id}-label` : undefined}
345
+ footer={!embedded && !closeOnSelect ? doneFooter : undefined}
346
+ {onSelect}
347
+ {...calendarProps}
348
+ />
349
+ </FieldDateShell>
@@ -0,0 +1,79 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { ValidateOptions } from "../../actions/validate.svelte.js";
3
+ import type { TranslateFn } from "../../types.js";
4
+ import type { THC } from "../Thc/Thc.svelte";
5
+ import type { Props as CalendarProps } from "../Calendar/Calendar.svelte";
6
+ import type { IsoDate } from "../Calendar/iso-date.js";
7
+ import type { InputWrapClassProps } from "./types.js";
8
+ type SnippetWithId = Snippet<[{
9
+ id: string;
10
+ }]>;
11
+ /** The `Calendar` props a date field lifts to its own top level. */
12
+ export type FieldDateCalendarProps = Pick<CalendarProps, "min" | "max" | "isDateDisabled" | "weekStartsOn" | "weekendDays" | "zone" | "captionLayout" | "yearRange" | "showWeekNumbers" | "showOutsideDays" | "fixedWeeks" | "showToday" | "renderDay">;
13
+ export interface Props extends InputWrapClassProps, FieldDateCalendarProps {
14
+ /** The selected date as `YYYY-MM-DD` (bindable). `null` / `""` = empty. */
15
+ value?: IsoDate | null;
16
+ /** Form field name — applied to the hidden input carrying the ISO date. */
17
+ name?: string;
18
+ /** Render the calendar inline inside the field instead of a trigger + dialog. */
19
+ embedded?: boolean;
20
+ /** Close the dialog right after a date is picked. Default `true`. */
21
+ closeOnSelect?: boolean;
22
+ /** Trigger text while empty. Defaults to `t("placeholder_date")`. */
23
+ placeholder?: string;
24
+ /** Offer clearing (trailing × on the trigger / "Clear" in the embedded footer). Default `true`. */
25
+ clearable?: boolean;
26
+ /** BCP 47 locale of the display text and the calendar names. Browser default when omitted. */
27
+ locale?: string;
28
+ /** `Intl.DateTimeFormat` options of the display text. Default `{ dateStyle: "medium" }`. */
29
+ formatOptions?: Intl.DateTimeFormatOptions;
30
+ /** Custom display formatter (wins over `formatOptions`). */
31
+ format?: (iso: IsoDate) => string;
32
+ /** Months the calendar shows side by side. Default `1`. */
33
+ months?: number;
34
+ /** Anything else for the inner `Calendar` (class hooks, `view`, …). Applied last. */
35
+ calendarProps?: Partial<CalendarProps>;
36
+ /** The hidden input (bindable). */
37
+ input?: HTMLInputElement;
38
+ label?: SnippetWithId | THC;
39
+ description?: SnippetWithId | THC;
40
+ labelAfter?: SnippetWithId | THC;
41
+ below?: SnippetWithId | THC;
42
+ class?: string;
43
+ id?: string;
44
+ tabindex?: number;
45
+ renderSize?: "sm" | "md" | "lg" | string;
46
+ required?: boolean;
47
+ disabled?: boolean;
48
+ /**
49
+ * Validation. `false` disables the built-in guard (required / valid ISO / min /
50
+ * max / disabled dates); an options object is merged with it (your
51
+ * `customValidator` runs after the built-in checks pass). Default: the guard.
52
+ */
53
+ validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
54
+ labelLeft?: boolean;
55
+ labelLeftWidth?: "normal" | "wide";
56
+ labelLeftBreakpoint?: number;
57
+ /** Classes for the trigger button */
58
+ classInput?: string;
59
+ /** Classes for the dialog box */
60
+ classDialog?: string;
61
+ style?: string;
62
+ noScrollLock?: boolean;
63
+ /** i18n — see `createCalendarT`. */
64
+ t?: TranslateFn;
65
+ /** Called after the value changed through the picker or the × (not on external writes). */
66
+ onChange?: (value: IsoDate | null) => void;
67
+ }
68
+ import type { ValidationResult } from "../../actions/validate.svelte.js";
69
+ declare const FieldDate: import("svelte").Component<Props, {
70
+ validate: () => ValidationResult | undefined;
71
+ clearValidation: () => void;
72
+ getValidation: () => ValidationResult | undefined;
73
+ focus: () => void;
74
+ scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
75
+ open: () => void;
76
+ close: () => void;
77
+ }, "value" | "input">;
78
+ type FieldDate = ReturnType<typeof FieldDate>;
79
+ export default FieldDate;