@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,327 @@
1
+ <!--
2
+ Internal: the field scaffolding shared by FieldDate and FieldDateRange — the
3
+ InputWrap shell, the trigger button (icon + formatted value / placeholder + a
4
+ trailing × that clears), the hidden inputs that carry the ISO value(s) into
5
+ FormData (the first one runs the `validate` action), and the picker's two
6
+ homes: inline inside the field (`embedded`) or a native <dialog> opened by the
7
+ trigger. The picker itself is the `children` snippet; the owning field decides
8
+ what it is and what it means.
9
+
10
+ WHY hidden inputs: the visible trigger is a <button>, so nothing would be
11
+ submitted otherwise — and a hidden input is barred from native constraint
12
+ validation, which is why `required` / min / max are enforced by the owning
13
+ field's customValidator (same as FieldPhoneNumber / FieldCountry / …).
14
+ -->
15
+ <script lang="ts" module>
16
+ import type { Snippet } from "svelte";
17
+ import type { ValidateOptions } from "../../../actions/validate.svelte.js";
18
+ import type { THC } from "../../Thc/Thc.svelte";
19
+ import type { InputWrapClassProps } from "../types.js";
20
+
21
+ type SnippetWithId = Snippet<[{ id: string }]>;
22
+
23
+ /** One hidden `<input>` the field submits. The first one carries the `validate` action. */
24
+ export interface FieldDateHiddenInput {
25
+ name?: string;
26
+ value: string;
27
+ }
28
+
29
+ export interface Props extends InputWrapClassProps {
30
+ id: string;
31
+ label?: SnippetWithId | THC;
32
+ description?: SnippetWithId | THC;
33
+ labelAfter?: SnippetWithId | THC;
34
+ below?: SnippetWithId | THC;
35
+ class?: string;
36
+ style?: string;
37
+ renderSize?: "sm" | "md" | "lg" | string;
38
+ required?: boolean;
39
+ disabled?: boolean;
40
+ tabindex?: number;
41
+ labelLeft?: boolean;
42
+ labelLeftWidth?: "normal" | "wide";
43
+ labelLeftBreakpoint?: number;
44
+ /** Formatted value shown in the trigger (`""` = empty → placeholder) */
45
+ display: string;
46
+ placeholder: string;
47
+ /** Show the trailing × while there is a value */
48
+ clearable: boolean;
49
+ clearLabel: string;
50
+ /** Render the picker inline inside the field instead of trigger + dialog */
51
+ embedded: boolean;
52
+ inputs: FieldDateHiddenInput[];
53
+ validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
54
+ dialogTitle: string;
55
+ closeLabel: string;
56
+ noScrollLock?: boolean;
57
+ /** Classes for the trigger button */
58
+ classInput?: string;
59
+ /** Classes for the dialog box */
60
+ classDialog?: string;
61
+ /** The first hidden input (bindable) */
62
+ input?: HTMLInputElement;
63
+ onOpen?: () => void;
64
+ onClose?: () => void;
65
+ onClear: () => void;
66
+ /** The picker */
67
+ children: Snippet;
68
+ }
69
+ </script>
70
+
71
+ <script lang="ts">
72
+ import {
73
+ validate as validateAction,
74
+ type ValidationResult,
75
+ } from "../../../actions/validate.svelte.js";
76
+ import { iconCalendar, iconX } from "../../../icons/index.js";
77
+ import { twMerge } from "../../../utils/tw-merge.js";
78
+ import Button from "../../Button/Button.svelte";
79
+ import ModalDialog from "../../ModalDialog/ModalDialog.svelte";
80
+ import InputWrap from "./InputWrap.svelte";
81
+
82
+ let {
83
+ id,
84
+ label = "",
85
+ description,
86
+ labelAfter,
87
+ below,
88
+ class: classProp,
89
+ style,
90
+ renderSize = "md",
91
+ required = false,
92
+ disabled = false,
93
+ tabindex = 0,
94
+ labelLeft = false,
95
+ labelLeftWidth = "normal",
96
+ labelLeftBreakpoint = 480,
97
+ classLabel,
98
+ classLabelBox,
99
+ classInputBox,
100
+ classInputBoxWrap,
101
+ classInputBoxWrapInvalid,
102
+ classDescBox,
103
+ classDescBoxToggle,
104
+ classBelowBox,
105
+ classValidationBox,
106
+ display,
107
+ placeholder,
108
+ clearable,
109
+ clearLabel,
110
+ embedded,
111
+ inputs,
112
+ // Renamed local binding to avoid collision with `export function validate()` below.
113
+ validate: validateProp,
114
+ dialogTitle,
115
+ closeLabel,
116
+ noScrollLock = false,
117
+ classInput,
118
+ classDialog,
119
+ input = $bindable(),
120
+ onOpen,
121
+ onClose,
122
+ onClear,
123
+ children,
124
+ }: Props = $props();
125
+
126
+ let validation: ValidationResult | undefined = $state();
127
+ const setValidationResult = (res: ValidationResult) => (validation = res);
128
+ let _doValidate: (() => void) | undefined = $state();
129
+
130
+ // Internal refs; the public `input` bindable is a write-out mirror.
131
+ let hiddenEl: HTMLInputElement | undefined = $state();
132
+ let triggerEl: HTMLButtonElement | undefined = $state();
133
+ let embeddedEl: HTMLDivElement | undefined = $state();
134
+ let dialog: ModalDialog | undefined = $state();
135
+
136
+ $effect(() => {
137
+ input = hiddenEl;
138
+ });
139
+
140
+ let isOpen = $derived(!embedded && !!dialog?.visibility().visible);
141
+
142
+ /** Trigger validation now. Renders the inline message if invalid. */
143
+ export function validate(): ValidationResult | undefined {
144
+ _doValidate?.();
145
+ return validation;
146
+ }
147
+
148
+ /** Clear the inline validation message and reset `setCustomValidity`. */
149
+ export function clearValidation(): void {
150
+ validation = undefined;
151
+ hiddenEl?.setCustomValidity?.("");
152
+ }
153
+
154
+ /** Current validation state, or undefined if validator has never run. */
155
+ export function getValidation(): ValidationResult | undefined {
156
+ return validation;
157
+ }
158
+
159
+ /** Focus the trigger (dialog mode) or the picker's tabbable day (embedded). */
160
+ export function focus(): void {
161
+ if (embedded) embeddedEl?.querySelector<HTMLElement>('[tabindex="0"]')?.focus?.();
162
+ else triggerEl?.focus?.();
163
+ }
164
+
165
+ /** Scroll the field into view. Defaults to smooth + center. */
166
+ export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
167
+ (embedded ? embeddedEl : triggerEl)?.scrollIntoView?.({
168
+ behavior: "smooth",
169
+ block: "center",
170
+ ...opts,
171
+ });
172
+ }
173
+
174
+ /** Re-run validation the way a user edit would (the action listens on `change`). */
175
+ export function dispatchChange(): void {
176
+ hiddenEl?.dispatchEvent(new Event("change", { bubbles: true }));
177
+ }
178
+
179
+ /** Open the picker dialog (no-op when `embedded` or `disabled`). */
180
+ export function open(): void {
181
+ if (embedded || disabled) return;
182
+ dialog?.open(triggerEl ?? null);
183
+ onOpen?.();
184
+ }
185
+
186
+ /** Close the picker dialog. */
187
+ export function close(): void {
188
+ dialog?.close();
189
+ }
190
+
191
+ /** Whether the picker dialog is currently open. */
192
+ export function isDialogOpen(): boolean {
193
+ return isOpen;
194
+ }
195
+
196
+ function clear() {
197
+ onClear();
198
+ triggerEl?.focus?.();
199
+ }
200
+ </script>
201
+
202
+ <InputWrap
203
+ {description}
204
+ class={classProp}
205
+ size={renderSize}
206
+ {id}
207
+ {label}
208
+ {labelAfter}
209
+ {below}
210
+ {required}
211
+ {disabled}
212
+ {labelLeft}
213
+ {labelLeftWidth}
214
+ {labelLeftBreakpoint}
215
+ {classLabel}
216
+ {classLabelBox}
217
+ {classInputBox}
218
+ {classInputBoxWrap}
219
+ {classInputBoxWrapInvalid}
220
+ {classDescBox}
221
+ {classDescBoxToggle}
222
+ {classBelowBox}
223
+ {classValidationBox}
224
+ {validation}
225
+ {style}
226
+ >
227
+ {#if embedded}
228
+ <div bind:this={embeddedEl} class="stuic-field-date-embedded" data-size={renderSize}>
229
+ {@render children()}
230
+ </div>
231
+ {:else}
232
+ <button
233
+ bind:this={triggerEl}
234
+ type="button"
235
+ {id}
236
+ class={twMerge("stuic-field-date-trigger", classInput)}
237
+ data-size={renderSize}
238
+ data-empty={display ? undefined : ""}
239
+ aria-haspopup="dialog"
240
+ aria-expanded={isOpen}
241
+ {disabled}
242
+ {tabindex}
243
+ onclick={open}
244
+ >
245
+ <span class="stuic-field-date-trigger-icon" aria-hidden="true">
246
+ {@html iconCalendar({ size: 18 })}
247
+ </span>
248
+ <span class="stuic-field-date-trigger-label">{display || placeholder}</span>
249
+ </button>
250
+ {/if}
251
+
252
+ {#snippet inputAfter()}
253
+ {#if !embedded && clearable && display && !disabled}
254
+ <div class="flex items-center pr-1">
255
+ <Button
256
+ iconButton
257
+ variant="ghost"
258
+ roundedFull
259
+ type="button"
260
+ size={renderSize === "lg" ? "md" : "sm"}
261
+ class="stuic-field-date-clear"
262
+ aria-label={clearLabel}
263
+ tooltip={clearLabel}
264
+ onclick={clear}
265
+ >
266
+ {@html iconX({ size: 16 })}
267
+ </Button>
268
+ </div>
269
+ {/if}
270
+ {/snippet}
271
+
272
+ {#each inputs as h, i (i)}
273
+ {#if i === 0}
274
+ <input
275
+ bind:this={hiddenEl}
276
+ type="hidden"
277
+ id={embedded ? id : undefined}
278
+ name={h.name}
279
+ value={h.value}
280
+ use:validateAction={() => ({
281
+ enabled: validateProp !== false,
282
+ ...(typeof validateProp === "boolean" ? {} : validateProp),
283
+ setValidationResult,
284
+ setDoValidate: (fn) => (_doValidate = fn),
285
+ })}
286
+ {required}
287
+ {disabled}
288
+ />
289
+ {:else}
290
+ <input type="hidden" name={h.name} value={h.value} {disabled} />
291
+ {/if}
292
+ {/each}
293
+ </InputWrap>
294
+
295
+ {#if !embedded}
296
+ <ModalDialog
297
+ bind:this={dialog}
298
+ ariaLabelledby="{id}-dialog-title"
299
+ noAutoFocus
300
+ {noScrollLock}
301
+ preClose={() => {
302
+ onClose?.();
303
+ }}
304
+ class="bg-transparent shadow-none pointer-events-none size-auto max-w-full"
305
+ >
306
+ <div class={twMerge("stuic-field-date-dialog pointer-events-auto", classDialog)}>
307
+ <div class="stuic-field-date-dialog-header">
308
+ <span id="{id}-dialog-title" class="stuic-field-date-dialog-title"
309
+ >{dialogTitle}</span
310
+ >
311
+ <Button
312
+ x
313
+ size="sm"
314
+ variant="ghost"
315
+ roundedFull
316
+ type="button"
317
+ aria-label={closeLabel}
318
+ tooltip={closeLabel}
319
+ onclick={close}
320
+ />
321
+ </div>
322
+ <div class="stuic-field-date-dialog-body">
323
+ {@render children()}
324
+ </div>
325
+ </div>
326
+ </ModalDialog>
327
+ {/if}
@@ -0,0 +1,66 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { ValidateOptions } from "../../../actions/validate.svelte.js";
3
+ import type { THC } from "../../Thc/Thc.svelte";
4
+ import type { InputWrapClassProps } from "../types.js";
5
+ type SnippetWithId = Snippet<[{
6
+ id: string;
7
+ }]>;
8
+ /** One hidden `<input>` the field submits. The first one carries the `validate` action. */
9
+ export interface FieldDateHiddenInput {
10
+ name?: string;
11
+ value: string;
12
+ }
13
+ export interface Props extends InputWrapClassProps {
14
+ id: string;
15
+ label?: SnippetWithId | THC;
16
+ description?: SnippetWithId | THC;
17
+ labelAfter?: SnippetWithId | THC;
18
+ below?: SnippetWithId | THC;
19
+ class?: string;
20
+ style?: string;
21
+ renderSize?: "sm" | "md" | "lg" | string;
22
+ required?: boolean;
23
+ disabled?: boolean;
24
+ tabindex?: number;
25
+ labelLeft?: boolean;
26
+ labelLeftWidth?: "normal" | "wide";
27
+ labelLeftBreakpoint?: number;
28
+ /** Formatted value shown in the trigger (`""` = empty → placeholder) */
29
+ display: string;
30
+ placeholder: string;
31
+ /** Show the trailing × while there is a value */
32
+ clearable: boolean;
33
+ clearLabel: string;
34
+ /** Render the picker inline inside the field instead of trigger + dialog */
35
+ embedded: boolean;
36
+ inputs: FieldDateHiddenInput[];
37
+ validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
38
+ dialogTitle: string;
39
+ closeLabel: string;
40
+ noScrollLock?: boolean;
41
+ /** Classes for the trigger button */
42
+ classInput?: string;
43
+ /** Classes for the dialog box */
44
+ classDialog?: string;
45
+ /** The first hidden input (bindable) */
46
+ input?: HTMLInputElement;
47
+ onOpen?: () => void;
48
+ onClose?: () => void;
49
+ onClear: () => void;
50
+ /** The picker */
51
+ children: Snippet;
52
+ }
53
+ import { type ValidationResult } from "../../../actions/validate.svelte.js";
54
+ declare const FieldDateShell: import("svelte").Component<Props, {
55
+ validate: () => ValidationResult | undefined;
56
+ clearValidation: () => void;
57
+ getValidation: () => ValidationResult | undefined;
58
+ focus: () => void;
59
+ scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
60
+ dispatchChange: () => void;
61
+ open: () => void;
62
+ close: () => void;
63
+ isDialogOpen: () => boolean;
64
+ }, "input">;
65
+ type FieldDateShell = ReturnType<typeof FieldDateShell>;
66
+ export default FieldDateShell;
@@ -83,6 +83,12 @@
83
83
  --stuic-field-options-optgroup-text: var(--stuic-color-muted-foreground);
84
84
  /* FieldOptions `chips` display mode */
85
85
  --stuic-field-options-chips-gap: 0.25rem;
86
+
87
+ /* FieldDate / FieldDateRange specific */
88
+ --stuic-field-date-icon-text: var(--stuic-color-muted-foreground);
89
+ --stuic-field-date-placeholder-text: var(--stuic-input-placeholder);
90
+ --stuic-field-date-embedded-padding: 0.5rem;
91
+ --stuic-field-date-dialog-padding: 1rem;
86
92
  }
87
93
 
88
94
  @layer components {
@@ -810,3 +816,116 @@
810
816
  }
811
817
  }
812
818
  }
819
+
820
+ /* ============================================================================
821
+ FIELD DATE / FIELD DATE RANGE
822
+ The trigger is a <button> (so no iOS zoom guard needed); it takes the input
823
+ size tokens so the field lines up with its siblings.
824
+ ============================================================================ */
825
+
826
+ @layer components {
827
+ .stuic-field-date-trigger {
828
+ display: flex;
829
+ flex: 1 1 0%;
830
+ min-width: 0;
831
+ width: 100%;
832
+ align-items: center;
833
+ gap: 0.5rem;
834
+ margin: 0;
835
+ border: 0;
836
+ background: transparent;
837
+ text-align: start;
838
+ font-family: var(--stuic-input-font-family);
839
+ color: var(--stuic-input-text);
840
+ letter-spacing: -0.025em;
841
+ border-radius: var(--stuic-input-radius, var(--stuic-radius));
842
+ cursor: pointer;
843
+ }
844
+
845
+ /* InputWrap already shows the ring via `.input-wrap:focus-within` */
846
+ .stuic-field-date-trigger:focus,
847
+ .stuic-field-date-trigger:focus-visible {
848
+ outline: none;
849
+ box-shadow: none;
850
+ }
851
+
852
+ .stuic-field-date-trigger:disabled {
853
+ cursor: not-allowed;
854
+ }
855
+
856
+ .stuic-field-date-trigger[data-size="sm"] {
857
+ padding: var(--stuic-input-padding-y-sm) var(--stuic-input-padding-x-sm);
858
+ font-size: var(--stuic-input-font-size-sm);
859
+ min-height: var(--stuic-input-min-height-sm);
860
+ }
861
+
862
+ .stuic-field-date-trigger[data-size="md"],
863
+ .stuic-field-date-trigger:not([data-size]) {
864
+ padding: var(--stuic-input-padding-y-md) var(--stuic-input-padding-x-md);
865
+ font-size: var(--stuic-input-font-size-md);
866
+ min-height: var(--stuic-input-min-height-md);
867
+ }
868
+
869
+ .stuic-field-date-trigger[data-size="lg"] {
870
+ padding: var(--stuic-input-padding-y-lg) var(--stuic-input-padding-x-lg);
871
+ font-size: var(--stuic-input-font-size-lg);
872
+ min-height: var(--stuic-input-min-height-lg);
873
+ }
874
+
875
+ .stuic-field-date-trigger-icon {
876
+ display: inline-flex;
877
+ flex-shrink: 0;
878
+ color: var(--stuic-field-date-icon-text);
879
+ }
880
+
881
+ .stuic-field-date-trigger-label {
882
+ flex: 1;
883
+ min-width: 0;
884
+ overflow: hidden;
885
+ text-overflow: ellipsis;
886
+ white-space: nowrap;
887
+ }
888
+
889
+ .stuic-field-date-trigger[data-empty] .stuic-field-date-trigger-label {
890
+ color: var(--stuic-field-date-placeholder-text);
891
+ }
892
+
893
+ /* `embedded`: the calendar lives inside the input card */
894
+ .stuic-field-date-embedded {
895
+ flex: 1 1 0%;
896
+ min-width: 0;
897
+ display: flex;
898
+ justify-content: center;
899
+ padding: var(--stuic-field-date-embedded-padding);
900
+ overflow-x: auto;
901
+ }
902
+
903
+ /* the picker dialog card (inside a transparent, click-through ModalDialog box) */
904
+ .stuic-field-date-dialog {
905
+ background: var(--stuic-modal-dialog-bg);
906
+ color: var(--stuic-modal-dialog-text);
907
+ border-radius: var(--stuic-field-date-dialog-radius, var(--stuic-radius-container));
908
+ box-shadow: var(--stuic-field-date-dialog-shadow, var(--stuic-shadow-dialog));
909
+ padding: var(--stuic-field-date-dialog-padding);
910
+ max-width: 100%;
911
+ }
912
+
913
+ .stuic-field-date-dialog-header {
914
+ display: flex;
915
+ align-items: center;
916
+ justify-content: space-between;
917
+ gap: 0.5rem;
918
+ margin-bottom: 0.5rem;
919
+ }
920
+
921
+ .stuic-field-date-dialog-title {
922
+ font-weight: var(--font-weight-semibold);
923
+ }
924
+
925
+ .stuic-field-date-dialog-body {
926
+ display: flex;
927
+ justify-content: center;
928
+ max-width: 100%;
929
+ overflow-x: auto;
930
+ }
931
+ }
@@ -21,3 +21,5 @@ export { default as FieldObject, type Props as FieldObjectProps, } from "./Field
21
21
  export { default as FieldPhoneNumber, type Props as FieldPhoneNumberProps, } from "./FieldPhoneNumber.svelte";
22
22
  export { default as FieldCountry, type Props as FieldCountryProps, } from "./FieldCountry.svelte";
23
23
  export { validatePhoneNumber } from "./phone-validation.js";
24
+ export { default as FieldDate, type Props as FieldDateProps, type FieldDateCalendarProps, } from "./FieldDate.svelte";
25
+ export { default as FieldDateRange, type Props as FieldDateRangeProps, type FieldDateRangeValue, } from "./FieldDateRange.svelte";
@@ -21,3 +21,5 @@ export { default as FieldObject, } from "./FieldObject.svelte";
21
21
  export { default as FieldPhoneNumber, } from "./FieldPhoneNumber.svelte";
22
22
  export { default as FieldCountry, } from "./FieldCountry.svelte";
23
23
  export { validatePhoneNumber } from "./phone-validation.js";
24
+ export { default as FieldDate, } from "./FieldDate.svelte";
25
+ export { default as FieldDateRange, } from "./FieldDateRange.svelte";
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- // Conventions escape hatch (docs/component-testing/02-test-conventions.md):
2
+ // Conventions escape hatch (docs/testing-components.md):
3
3
  // ModalDialog is imperative-only (open()/close() via a ref; no bindable
4
4
  // `visible` prop), so a `.svelte.test.ts` file can't drive it directly. This
5
5
  // fixture holds the `bind:this` ref and exposes an opener button that calls
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import SlidingPanels from "./SlidingPanels.svelte";
3
3
 
4
- // Conventions escape hatch (docs/component-testing/02-test-conventions.md):
4
+ // Conventions escape hatch (docs/testing-components.md):
5
5
  // SlidingPanels exposes the panel content via snippets that receive an
6
6
  // imperative `show(panel)` fn. A `.svelte.test.ts` file can't host markup that
7
7
  // wires snippet args to a button, so we compose the real component here and
@@ -31,6 +31,7 @@ export { iconLucideTrendingDown as iconTrendingDown } from "@marianmeres/icons-f
31
31
  export { iconLucideTrendingUp as iconTrendingUp } from "@marianmeres/icons-fns/lucide/iconLucideTrendingUp.js";
32
32
  export { iconLucideZoomIn as iconZoomIn } from "@marianmeres/icons-fns/lucide/iconLucideZoomIn.js";
33
33
  export { iconLucideZoomOut as iconZoomOut } from "@marianmeres/icons-fns/lucide/iconLucideZoomOut.js";
34
+ export { iconLucideCalendar as iconCalendar } from "@marianmeres/icons-fns/lucide/iconLucideCalendar.js";
34
35
  export { iconLucideCheck as iconCheck } from "@marianmeres/icons-fns/lucide/iconLucideCheck.js";
35
36
  export { iconLucideChevronDown as iconChevronDown } from "@marianmeres/icons-fns/lucide/iconLucideChevronDown.js";
36
37
  export { iconLucideChevronLeft as iconChevronLeft } from "@marianmeres/icons-fns/lucide/iconLucideChevronLeft.js";
@@ -36,6 +36,7 @@ export { iconLucideTrendingUp as iconTrendingUp } from "@marianmeres/icons-fns/l
36
36
  export { iconLucideZoomIn as iconZoomIn } from "@marianmeres/icons-fns/lucide/iconLucideZoomIn.js";
37
37
  export { iconLucideZoomOut as iconZoomOut } from "@marianmeres/icons-fns/lucide/iconLucideZoomOut.js";
38
38
  // UI Control Icons (Lucide)
39
+ export { iconLucideCalendar as iconCalendar } from "@marianmeres/icons-fns/lucide/iconLucideCalendar.js";
39
40
  export { iconLucideCheck as iconCheck } from "@marianmeres/icons-fns/lucide/iconLucideCheck.js";
40
41
  export { iconLucideChevronDown as iconChevronDown } from "@marianmeres/icons-fns/lucide/iconLucideChevronDown.js";
41
42
  export { iconLucideChevronLeft as iconChevronLeft } from "@marianmeres/icons-fns/lucide/iconLucideChevronLeft.js";
package/dist/index.css CHANGED
@@ -63,6 +63,7 @@ In practice:
63
63
  @import "./components/Breadcrumbs/index.css";
64
64
  @import "./components/Button/index.css";
65
65
  @import "./components/ButtonGroupRadio/index.css";
66
+ @import "./components/Calendar/index.css";
66
67
  @import "./components/Collapsible/index.css";
67
68
  @import "./components/Cart/index.css";
68
69
  @import "./components/Card/index.css";
package/dist/index.d.ts CHANGED
@@ -32,6 +32,7 @@ export * from "./components/Book/index.js";
32
32
  export * from "./components/Breadcrumbs/index.js";
33
33
  export * from "./components/Button/index.js";
34
34
  export * from "./components/ButtonGroupRadio/index.js";
35
+ export * from "./components/Calendar/index.js";
35
36
  export * from "./components/Cart/index.js";
36
37
  export * from "./components/Card/index.js";
37
38
  export * from "./components/Carousel/index.js";
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ export * from "./components/Book/index.js";
33
33
  export * from "./components/Breadcrumbs/index.js";
34
34
  export * from "./components/Button/index.js";
35
35
  export * from "./components/ButtonGroupRadio/index.js";
36
+ export * from "./components/Calendar/index.js";
36
37
  export * from "./components/Cart/index.js";
37
38
  export * from "./components/Card/index.js";
38
39
  export * from "./components/Carousel/index.js";
@@ -0,0 +1,10 @@
1
+ # Archived docs
2
+
3
+ Documents kept for provenance only. **Nothing here is current guidance** — do not follow it, and do
4
+ not update it. If something here turns out to still be true and useful, promote it back into `docs/`
5
+ rather than citing it from live code.
6
+
7
+ | Archived | Why |
8
+ | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
9
+ | [`component-testing/`](./component-testing/) | Planning set for introducing real-browser component tests (2026-06). The work is done and shipped; the roadmap, coverage tiers, CI plan and `PROGRESS.md` tracker are history. The one living piece was promoted to [`docs/testing-components.md`](../testing-components.md). |
10
+ | [`upgrading.md`](./upgrading.md) | One-time upgrade note for consumers moving off **v3.66.1**. The library is far past that; the deltas it lists are long since absorbed. |
@@ -7,6 +7,8 @@ vitest-browser-svelte docs. Planning artifact; no code was changed.
7
7
 
8
8
  # @marianmeres/stuic — Component Testing: Overview & Roadmap
9
9
 
10
+ > **ARCHIVED — historical, not current guidance.** See [`docs/_archive/README.md`](../README.md) for why.
11
+
10
12
  > **Verdict:** the proposed stack — Vitest Browser Mode + `vitest-browser-svelte` + Playwright/Chromium
11
13
  > — is the right default for _this_ library, where the value being shipped is precisely the DOM/layout/
12
14
  > focus/positioning behavior that the current node/server-build test setup **cannot exercise at all**.
@@ -24,13 +26,13 @@ vitest-browser-svelte docs. Planning artifact; no code was changed.
24
26
  > node with the very server-build error this effort exists to escape.
25
27
  >
26
28
  > Read order: this file → [`01-framework-setup`](./01-framework-setup.md) for the exact config →
27
- > [`02-test-conventions`](./02-test-conventions.md) for how to write a test → then work
29
+ > [`02-test-conventions`](../../testing-components.md) for how to write a test → then work
28
30
  > [`PROGRESS.md`](./PROGRESS.md) top-down. [`03`](./03-component-coverage-roadmap.md) ranks all 74
29
31
  > components; [`04`](./04-hard-cases-and-e2e.md) handles the hard 30; [`05`](./05-ci.md) is CI.
30
32
 
31
33
  ## A note on `docs/testing.md` (this is a partial reversal — by design)
32
34
 
33
- [`docs/testing.md`](../testing.md) records a deliberate decision **not** to test component rendering
35
+ [`docs/testing.md`](../../testing.md) records a deliberate decision **not** to test component rendering
34
36
  ("50+ components × prop combos = slow suite, tiny yield; rendering is gated by svelte-check + publint +
35
37
  build"). That reasoning holds for _"does it render"_ and we keep it. What changes: browser mode lets
36
38
  us test _"does it **behave**"_ — events, two-way binding, aria/disabled/active state, focus traps,
@@ -45,7 +47,7 @@ explicit sprint task so the docs don't contradict each other.
45
47
  | 1 | Upgrade vitest 3→4, verify 9 suites green | [01](./01-framework-setup.md) | high | S | med | Gating prerequisite; nothing installs without it |
46
48
  | 2 | Add `projects` split (node `server` + browser `client`) + Chromium | [01](./01-framework-setup.md) | high | S | med | The harness; routes by `*.svelte.test.ts` filename |
47
49
  | 3 | Separator smoke test — prove client build + `$effect` actually run | [01](./01-framework-setup.md) | high | S | med | Disproves/confirms the documented server-build blocker |
48
- | 4 | Reconcile `docs/testing.md` (behavior ✅, rendering still ❌) | [02](./02-test-conventions.md) | med | S | low | Keep docs internally consistent before scaling |
50
+ | 4 | Reconcile `docs/testing.md` (behavior ✅, rendering still ❌) | [02](../../testing-components.md) | med | S | low | Keep docs internally consistent before scaling |
49
51
  | 5 | Button — flagship; sets every assertion pattern | [03](./03-component-coverage-roadmap.md) | high | S | low | Most-used primitive; template for the rest |
50
52
  | 6 | Pill, Switch — events + binding patterns | [03](./03-component-coverage-roadmap.md) | high | S | low | Cover dismiss/toggle/bind once, reuse everywhere |
51
53
  | 7 | Spinner, Skeleton, DismissibleMessage, Avatar, Progress | [03](./03-component-coverage-roadmap.md) | high | S | low | Deterministic, high-traffic; quick wins |
@@ -68,7 +70,7 @@ Branch: `feat/component-testing`. One commit per task.
68
70
  chromium`, fix the test scripts, and land the Separator smoke test. Unblocks all component tests
69
71
  and proves the server-build blocker is gone. Detail in [01](./01-framework-setup.md).
70
72
  3. **Reconcile `docs/testing.md` (#4)** — small doc edit so the philosophy matches reality.
71
- 4. **Button (#5)** — establishes the assertion vocabulary ([02](./02-test-conventions.md)) every later
73
+ 4. **Button (#5)** — establishes the assertion vocabulary ([02](../../testing-components.md)) every later
72
74
  test reuses. Highest-leverage single component.
73
75
  5. **Pill → Switch → Spinner → Skeleton → DismissibleMessage → Avatar → Progress (#6, #7)** — the easy
74
76
  tier, one commit each; fast, deterministic, high-traffic coverage.
@@ -105,7 +107,7 @@ flowchart TD
105
107
  ## Completeness check
106
108
 
107
109
  - **Snippet-heavy components** (Button needs `children`): the `createRawSnippet` pattern is settled in
108
- [02](./02-test-conventions.md); first real exercise is Button — watch for friction and codify a shared
110
+ [02](../../testing-components.md); first real exercise is Button — watch for friction and codify a shared
109
111
  helper home then.
110
112
  - **SvelteKit-plugin-in-browser-mode** is the top unknown; the Separator smoke test (task 2) is the
111
113
  designated early canary, with a documented fallback (plain `svelte()` plugin for the client project).
@@ -114,6 +116,6 @@ flowchart TD
114
116
  - Not yet scoped: a dedicated `playwright.config.ts` for the standalone E2E layer — intentionally
115
117
  deferred to its own future initiative ([04](./04-hard-cases-and-e2e.md)).
116
118
 
117
- Source documents: [`01-framework-setup`](./01-framework-setup.md), [`02-test-conventions`](./02-test-conventions.md),
119
+ Source documents: [`01-framework-setup`](./01-framework-setup.md), [`02-test-conventions`](../../testing-components.md),
118
120
  [`03-component-coverage-roadmap`](./03-component-coverage-roadmap.md), [`04-hard-cases-and-e2e`](./04-hard-cases-and-e2e.md),
119
121
  [`05-ci`](./05-ci.md).