@marianmeres/stuic 3.115.0 → 3.116.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 (86) hide show
  1. package/API.md +297 -304
  2. package/dist/actions/dim-behind/index.css +4 -1
  3. package/dist/actions/focus-trap.js +3 -1
  4. package/dist/components/Accordion/README.md +17 -17
  5. package/dist/components/Accordion/index.css +4 -2
  6. package/dist/components/AssetsPreview/README.md +7 -7
  7. package/dist/components/AssetsPreview/_internal/assets-preview-types.d.ts +1 -2
  8. package/dist/components/AssetsPreview/_internal/assets-preview-utils.d.ts +1 -1
  9. package/dist/components/AssetsPreview/_internal/assets-preview-utils.js +9 -3
  10. package/dist/components/Avatar/Avatar.svelte +1 -3
  11. package/dist/components/Avatar/README.md +33 -27
  12. package/dist/components/Book/Book.svelte +6 -1
  13. package/dist/components/Book/README.md +22 -20
  14. package/dist/components/Book/index.css +4 -2
  15. package/dist/components/Button/README.md +17 -17
  16. package/dist/components/Card/Card.svelte +25 -8
  17. package/dist/components/Card/README.md +52 -56
  18. package/dist/components/Card/index.css +2 -1
  19. package/dist/components/Carousel/Carousel.svelte +1 -3
  20. package/dist/components/Carousel/README.md +28 -28
  21. package/dist/components/Cart/Cart.svelte +2 -1
  22. package/dist/components/Cart/README.md +25 -25
  23. package/dist/components/Checkout/CheckoutGuestOrLoginForm.svelte +8 -3
  24. package/dist/components/Checkout/CheckoutShippingStep.svelte +1 -2
  25. package/dist/components/Checkout/README.md +143 -130
  26. package/dist/components/CronInput/CronInput.svelte +64 -60
  27. package/dist/components/CronInput/README.md +46 -46
  28. package/dist/components/DataTable/DataTable.svelte +5 -1
  29. package/dist/components/DataTable/README.md +78 -63
  30. package/dist/components/DropdownMenu/DropdownMenu.svelte +6 -2
  31. package/dist/components/DropdownMenu/README.md +33 -27
  32. package/dist/components/EmailVerifyForm/EmailVerifyForm.svelte +2 -9
  33. package/dist/components/EmailVerifyForm/README.md +30 -30
  34. package/dist/components/Header/Header.svelte +161 -165
  35. package/dist/components/Header/README.md +7 -7
  36. package/dist/components/IconSwap/README.md +20 -15
  37. package/dist/components/IconSwap/index.css +2 -1
  38. package/dist/components/ImageCycler/ImageCycler.svelte +19 -5
  39. package/dist/components/ImageCycler/ImageCycler.svelte.d.ts +14 -10
  40. package/dist/components/ImageCycler/README.md +15 -15
  41. package/dist/components/ImageCycler/index.css +26 -20
  42. package/dist/components/Input/FieldFile.svelte +1 -3
  43. package/dist/components/Input/FieldInput.svelte +1 -3
  44. package/dist/components/Input/FieldKeyValues.svelte +2 -6
  45. package/dist/components/Input/FieldObject.svelte +2 -1
  46. package/dist/components/Input/README.md +11 -11
  47. package/dist/components/KbdShortcut/index.css +2 -1
  48. package/dist/components/LoginForm/LoginForm.svelte +1 -7
  49. package/dist/components/LoginForm/README.md +46 -46
  50. package/dist/components/ModalDialog/index.css +2 -1
  51. package/dist/components/Notifications/index.css +24 -6
  52. package/dist/components/OtpInput/OtpInput.svelte +0 -0
  53. package/dist/components/OtpInput/README.md +15 -19
  54. package/dist/components/OtpInput/index.css +1 -4
  55. package/dist/components/OtpInput/index.d.ts +1 -1
  56. package/dist/components/OtpInput/index.js +1 -1
  57. package/dist/components/Pill/README.md +41 -40
  58. package/dist/components/Pill/index.css +3 -6
  59. package/dist/components/PricingTable/README.md +86 -86
  60. package/dist/components/PricingTable/index.css +20 -35
  61. package/dist/components/RegisterForm/README.md +60 -60
  62. package/dist/components/RegisterForm/RegisterForm.svelte +1 -7
  63. package/dist/components/Separator/README.md +7 -7
  64. package/dist/components/TabbedMenu/index.css +6 -3
  65. package/dist/components/Tree/README.md +67 -67
  66. package/dist/components/UserAvatarMenu/UserAvatarMenu.svelte +1 -5
  67. package/dist/components/WithSidePanel/index.css +4 -4
  68. package/dist/index.css +12 -8
  69. package/dist/utils/design-tokens.d.ts +1 -1
  70. package/dist/utils/design-tokens.js +1 -1
  71. package/docs/architecture.md +7 -7
  72. package/docs/component-testing/00-overview-and-roadmap.md +19 -19
  73. package/docs/component-testing/01-framework-setup.md +6 -6
  74. package/docs/component-testing/02-test-conventions.md +6 -5
  75. package/docs/component-testing/03-component-coverage-roadmap.md +27 -27
  76. package/docs/component-testing/04-hard-cases-and-e2e.md +8 -8
  77. package/docs/component-testing/05-ci.md +3 -3
  78. package/docs/component-testing/PROGRESS.md +30 -26
  79. package/docs/component-testing/README.md +8 -8
  80. package/docs/conventions.md +25 -25
  81. package/docs/domains/components.md +386 -385
  82. package/docs/domains/theming.md +24 -24
  83. package/docs/domains/utils.md +22 -25
  84. package/docs/testing.md +2 -2
  85. package/docs/upgrading.md +32 -28
  86. package/package.json +2 -1
@@ -6,80 +6,80 @@ Standalone registration form. Same conventions as [`LoginForm`](../LoginForm/REA
6
6
 
7
7
  ## Exports
8
8
 
9
- | Export | Kind | Description |
10
- | ------------------------------- | --------- | ------------------------------------------------- |
11
- | `RegisterForm` | component | Form component |
12
- | `RegisterFormModal` | component | Modal-wrapped form with optional trigger snippet |
13
- | `RegisterFormProps` | type | Props for `RegisterForm` |
14
- | `RegisterFormModalProps` | type | Props for `RegisterFormModal` |
15
- | `RegisterFormData` | type | `{ email, password, passwordConfirm, extra }` |
16
- | `RegisterFormValidationError` | type | `{ field, message }` |
17
- | `RegisterFieldConfig` | type | Declarative extra-field descriptor |
18
- | `createEmptyRegisterFormData` | function | Factory for an empty `RegisterFormData` |
19
- | `validateRegisterForm` | function | `(data, t, extraFields, opts) => Error[]` |
9
+ | Export | Kind | Description |
10
+ | ----------------------------- | --------- | ------------------------------------------------ |
11
+ | `RegisterForm` | component | Form component |
12
+ | `RegisterFormModal` | component | Modal-wrapped form with optional trigger snippet |
13
+ | `RegisterFormProps` | type | Props for `RegisterForm` |
14
+ | `RegisterFormModalProps` | type | Props for `RegisterFormModal` |
15
+ | `RegisterFormData` | type | `{ email, password, passwordConfirm, extra }` |
16
+ | `RegisterFormValidationError` | type | `{ field, message }` |
17
+ | `RegisterFieldConfig` | type | Declarative extra-field descriptor |
18
+ | `createEmptyRegisterFormData` | function | Factory for an empty `RegisterFormData` |
19
+ | `validateRegisterForm` | function | `(data, t, extraFields, opts) => Error[]` |
20
20
 
21
21
  ## `RegisterFieldConfig`
22
22
 
23
23
  ```ts
24
24
  interface RegisterFieldConfig {
25
- name: string; // unique key under formData.extra
26
- label: string; // already-translated
25
+ name: string; // unique key under formData.extra
26
+ label: string; // already-translated
27
27
  type?: "text" | "email" | "tel" | "url" | "password" | "number";
28
28
  placeholder?: string;
29
29
  required?: boolean;
30
30
  autocomplete?: HTMLInputAttributes["autocomplete"];
31
31
  initialValue?: unknown;
32
32
  validate?: (value: unknown, data: RegisterFormData) => string | undefined;
33
- position?: "top" | "bottom"; // default "bottom"
34
- props?: Record<string, unknown>; // passthrough to FieldInput
33
+ position?: "top" | "bottom"; // default "bottom"
34
+ props?: Record<string, unknown>; // passthrough to FieldInput
35
35
  }
36
36
  ```
37
37
 
38
38
  ## RegisterForm — Props
39
39
 
40
- | Prop | Type | Default | Description |
41
- | --------------------- | ------------------------------------------------- | -------- | ------------------------------------------------------------------------------------ |
42
- | `formData` | `RegisterFormData` | empty | Bindable form data. |
43
- | `onSubmit` | `(data: RegisterFormData) => void` | required | Called after client-side validation passes. |
44
- | `isSubmitting` | `boolean` | `false` | Disables the CTA during submission. |
45
- | `errors` | `RegisterFormValidationError[]` | `[]` | Field-specific server errors (merged with internal validation). |
46
- | `error` | `string` | - | General error rendered as a `DismissibleMessage` above the form. |
47
- | `showPasswordConfirm` | `boolean` | `true` | Render the password-confirm field. |
48
- | `passwordMinLength` | `number` | `8` | Minimum password length (fed into both the FieldInput attribute and the validator). |
49
- | `extraFields` | `RegisterFieldConfig[]` | `[]` | Declarative extra fields. Rendered as `FieldInput`s positioned top or bottom. |
50
- | `extraFieldsSlot` | `Snippet<[{ formData, fieldError }]>` | - | Escape hatch for non-FieldInput extras. Rendered after declarative bottom fields. |
51
- | `submitLabel` | `string` | i18n | Override the CTA label. |
52
- | `submittingLabel` | `string` | i18n | Override the CTA label while submitting. |
53
- | `submitButton` | `Snippet<[{ isSubmitting, disabled }]>` | - | Override the entire CTA section. |
54
- | `socialLogins` | `Snippet` | - | Social/OAuth buttons. A divider is shown above when set. |
55
- | `socialDividerLabel` | `string \| false` | i18n | Override (or hide with `false`) the divider above social buttons. |
56
- | `footer` | `Snippet` | - | Content below the form (e.g., "Already have an account? Log in"). |
57
- | `notifications` | `NotificationsStack` | - | When set, general errors are also pushed via `notifications.error()`. |
58
- | `t` | `TranslateFn` | English | i18n function. |
59
- | `unstyled` / `class` | - | - | Standard styling escape hatches. |
60
- | `el` | `HTMLFormElement` | - | Bindable form element. |
40
+ | Prop | Type | Default | Description |
41
+ | --------------------- | --------------------------------------- | -------- | ----------------------------------------------------------------------------------- |
42
+ | `formData` | `RegisterFormData` | empty | Bindable form data. |
43
+ | `onSubmit` | `(data: RegisterFormData) => void` | required | Called after client-side validation passes. |
44
+ | `isSubmitting` | `boolean` | `false` | Disables the CTA during submission. |
45
+ | `errors` | `RegisterFormValidationError[]` | `[]` | Field-specific server errors (merged with internal validation). |
46
+ | `error` | `string` | - | General error rendered as a `DismissibleMessage` above the form. |
47
+ | `showPasswordConfirm` | `boolean` | `true` | Render the password-confirm field. |
48
+ | `passwordMinLength` | `number` | `8` | Minimum password length (fed into both the FieldInput attribute and the validator). |
49
+ | `extraFields` | `RegisterFieldConfig[]` | `[]` | Declarative extra fields. Rendered as `FieldInput`s positioned top or bottom. |
50
+ | `extraFieldsSlot` | `Snippet<[{ formData, fieldError }]>` | - | Escape hatch for non-FieldInput extras. Rendered after declarative bottom fields. |
51
+ | `submitLabel` | `string` | i18n | Override the CTA label. |
52
+ | `submittingLabel` | `string` | i18n | Override the CTA label while submitting. |
53
+ | `submitButton` | `Snippet<[{ isSubmitting, disabled }]>` | - | Override the entire CTA section. |
54
+ | `socialLogins` | `Snippet` | - | Social/OAuth buttons. A divider is shown above when set. |
55
+ | `socialDividerLabel` | `string \| false` | i18n | Override (or hide with `false`) the divider above social buttons. |
56
+ | `footer` | `Snippet` | - | Content below the form (e.g., "Already have an account? Log in"). |
57
+ | `notifications` | `NotificationsStack` | - | When set, general errors are also pushed via `notifications.error()`. |
58
+ | `t` | `TranslateFn` | English | i18n function. |
59
+ | `unstyled` / `class` | - | - | Standard styling escape hatches. |
60
+ | `el` | `HTMLFormElement` | - | Bindable form element. |
61
61
 
62
62
  ### Imperative methods (via `bind:this`)
63
63
 
64
- | Method | Returns | Purpose |
65
- | ------------------------------ | --------- | ------------------------------------------------------------------------ |
66
- | `validate()` | `boolean` | Forces every inner field's validator to run. `true` if all valid. |
67
- | `scrollToFirstError(opts?)` | `boolean` | Scrolls + focuses the first invalid field. Call after `validate()`. |
64
+ | Method | Returns | Purpose |
65
+ | --------------------------- | --------- | ------------------------------------------------------------------- |
66
+ | `validate()` | `boolean` | Forces every inner field's validator to run. `true` if all valid. |
67
+ | `scrollToFirstError(opts?)` | `boolean` | Scrolls + focuses the first invalid field. Call after `validate()`. |
68
68
 
69
69
  ## RegisterFormModal — extra props
70
70
 
71
71
  Inherits all `RegisterForm` props, plus:
72
72
 
73
- | Prop | Type | Default | Description |
74
- | ------------- | ------------------------------- | ------------------------------------ | ---------------------------------------------------------- |
75
- | `title` | `string` | `"Create account"` (i18n) | Modal title. |
76
- | `visible` | `boolean` | `false` | Bindable modal visibility. |
77
- | `trigger` | `Snippet<[{ open }]>` | - | Optional trigger element rendered outside the modal. |
78
- | `classModal` | `string` | - | Class for the Modal box. |
79
- | `classInner` | `string` | - | Class for the Modal inner width container. |
80
- | `classForm` | `string` | - | Class forwarded to the inner `RegisterForm`. |
81
- | `noXClose` | `boolean` | `false` | Hide the close (X) button. |
82
- | `onClose` | `() => false \| void` | - | Pre-close hook. Return `false` to prevent close. |
73
+ | Prop | Type | Default | Description |
74
+ | ------------ | --------------------- | ------------------------- | ---------------------------------------------------- |
75
+ | `title` | `string` | `"Create account"` (i18n) | Modal title. |
76
+ | `visible` | `boolean` | `false` | Bindable modal visibility. |
77
+ | `trigger` | `Snippet<[{ open }]>` | - | Optional trigger element rendered outside the modal. |
78
+ | `classModal` | `string` | - | Class for the Modal box. |
79
+ | `classInner` | `string` | - | Class for the Modal inner width container. |
80
+ | `classForm` | `string` | - | Class forwarded to the inner `RegisterForm`. |
81
+ | `noXClose` | `boolean` | `false` | Hide the close (X) button. |
82
+ | `onClose` | `() => false \| void` | - | Pre-close hook. Return `false` to prevent close. |
83
83
 
84
84
  **Methods:** `open(openerOrEvent?)`, `close()` — exposed via `bind:this`.
85
85
 
@@ -161,16 +161,16 @@ Inherits all `RegisterForm` props, plus:
161
161
 
162
162
  Prefix: `--stuic-register-form-*`
163
163
 
164
- | Variable | Purpose |
165
- | ----------------------------------------------------- | ----------------------------- |
166
- | `--stuic-register-form-gap` | Vertical gap between sections |
167
- | `--stuic-register-form-gap-row` | Gap inside multi-column rows |
168
- | `--stuic-register-form-social-margin-top` | Margin above social block |
169
- | `--stuic-register-form-social-gap` | Gap between social buttons |
170
- | `--stuic-register-form-social-divider-color` | Divider text color |
171
- | `--stuic-register-form-social-divider-line-color` | Divider line color |
172
- | `--stuic-register-form-social-divider-font-size` | Divider text size |
173
- | `--stuic-register-form-social-divider-margin-bottom` | Divider bottom margin |
164
+ | Variable | Purpose |
165
+ | ---------------------------------------------------- | ----------------------------- |
166
+ | `--stuic-register-form-gap` | Vertical gap between sections |
167
+ | `--stuic-register-form-gap-row` | Gap inside multi-column rows |
168
+ | `--stuic-register-form-social-margin-top` | Margin above social block |
169
+ | `--stuic-register-form-social-gap` | Gap between social buttons |
170
+ | `--stuic-register-form-social-divider-color` | Divider text color |
171
+ | `--stuic-register-form-social-divider-line-color` | Divider line color |
172
+ | `--stuic-register-form-social-divider-font-size` | Divider text size |
173
+ | `--stuic-register-form-social-divider-margin-bottom` | Divider bottom margin |
174
174
 
175
175
  ## i18n keys
176
176
 
@@ -257,13 +257,7 @@
257
257
  }
258
258
  </script>
259
259
 
260
- <form
261
- bind:this={formEl}
262
- class={_class}
263
- use:onSubmitValidityCheck
264
- novalidate
265
- {...rest}
266
- >
260
+ <form bind:this={formEl} class={_class} use:onSubmitValidityCheck novalidate {...rest}>
267
261
  <!-- General error alert -->
268
262
  <DismissibleMessage message={error} intent="destructive" />
269
263
 
@@ -4,14 +4,14 @@ A horizontal or vertical separator line. Renders an accessible `role="separator"
4
4
 
5
5
  ## Props
6
6
 
7
- | Prop | Type | Default | Description |
8
- | ------------- | ---------------------------- | -------------- | -------------------------------------------------------- |
9
- | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction. Also published as `data-orientation`. |
7
+ | Prop | Type | Default | Description |
8
+ | ------------- | ---------------------------- | -------------- | ----------------------------------------------------------- |
9
+ | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction. Also published as `data-orientation`. |
10
10
  | `decorative` | `boolean` | `false` | Drop `role` + `aria-orientation`, set `aria-hidden="true"`. |
11
- | `unstyled` | `boolean` | `false` | Skip all default styling. |
12
- | `class` | `string` | - | Additional CSS classes. |
13
- | `style` | `string` | - | Inline style. |
14
- | `el` | `HTMLDivElement` | - | Bindable root element. |
11
+ | `unstyled` | `boolean` | `false` | Skip all default styling. |
12
+ | `class` | `string` | - | Additional CSS classes. |
13
+ | `style` | `string` | - | Inline style. |
14
+ | `el` | `HTMLDivElement` | - | Bindable root element. |
15
15
 
16
16
  ## Usage
17
17
 
@@ -46,7 +46,8 @@
46
46
  display: block;
47
47
  width: 100%;
48
48
  padding: var(--stuic-tabbed-menu-padding-y) var(--stuic-tabbed-menu-padding-x);
49
- border-radius: var(--stuic-tabbed-menu-radius, var(--stuic-radius)) var(--stuic-tabbed-menu-radius, var(--stuic-radius)) 0 0;
49
+ border-radius: var(--stuic-tabbed-menu-radius, var(--stuic-radius))
50
+ var(--stuic-tabbed-menu-radius, var(--stuic-radius)) 0 0;
50
51
  border: 1px solid var(--stuic-tabbed-menu-border);
51
52
  border-bottom: 0;
52
53
  background: var(--stuic-tabbed-menu-tab-bg);
@@ -102,13 +103,15 @@
102
103
  }
103
104
 
104
105
  .stuic-tabbed-menu[data-orientation="vertical"] .stuic-tabbed-menu-tab {
105
- border-radius: var(--stuic-tabbed-menu-radius, var(--stuic-radius)) 0 0 var(--stuic-tabbed-menu-radius, var(--stuic-radius));
106
+ border-radius: var(--stuic-tabbed-menu-radius, var(--stuic-radius)) 0 0
107
+ var(--stuic-tabbed-menu-radius, var(--stuic-radius));
106
108
  border: 1px solid var(--stuic-tabbed-menu-border);
107
109
  border-right: 0;
108
110
  text-align: left;
109
111
  }
110
112
 
111
- .stuic-tabbed-menu[data-orientation="vertical"] .stuic-tabbed-menu-tab[aria-selected="true"] {
113
+ .stuic-tabbed-menu[data-orientation="vertical"]
114
+ .stuic-tabbed-menu-tab[aria-selected="true"] {
112
115
  border-color: var(--stuic-tabbed-menu-border-active);
113
116
  }
114
117
  }
@@ -84,49 +84,49 @@ Collapsing a branch also collapses all of its descendants.
84
84
 
85
85
  ## Props
86
86
 
87
- | Prop | Type | Default | Description |
88
- | ------------------- | ---------------------------------------------------- | ----------------- | ------------------------------------------------------------------------ |
89
- | `items` | `TreeNodeDTO<T>[]` | required | Tree data (e.g. from `tree.toJSON().children`) |
90
- | `activeId` | `string` | - | ID of the currently active/selected node |
91
- | `isActive` | `(item) => boolean` | - | Alternative to `activeId` for custom active detection |
92
- | `onSelect` | `(item) => void` | - | Called when a node is selected (click or Enter/Space) |
93
- | `onToggle` | `(item, expanded) => void` | - | Called when a branch is expanded/collapsed |
94
- | `sort` | `(a, b) => number` | - | Per-level sort comparator |
95
- | `defaultExpanded` | `boolean` | `false` | Default expansion state for branches |
96
- | `expandedIds` | `Set<string>` | - | Initially-expanded branch IDs |
97
- | `persistState` | `boolean` | `false` | Persist expand/collapse to `localStorage` |
98
- | `storageKeyPrefix` | `string` | `"stuic-tree"` | Prefix used for `localStorage` keys |
99
- | `draggable` | `boolean` | `false` | Enable drag-and-drop reordering |
100
- | `isDraggable` | `(item) => boolean` | - | Return `false` to block dragging a specific node |
101
- | `isDropTarget` | `(item) => boolean` | - | Return `false` to block dropping onto a specific node |
102
- | `onMove` | `(event) => void \| false \| Promise<void \| false>` | - | Called when a valid drop happens; return `false` to reject |
103
- | `onError` | `(err) => void` | - | Called when `onMove` throws |
104
- | `dragExpandDelay` | `number` | `800` | ms before auto-expanding a collapsed branch hovered during drag |
105
- | `t` | `TranslateFn` | built-in | Optional translation function (used for drag-drop a11y announcements) |
106
- | `getNodeLabel` | `(item) => string` | `String(v.value)` | String used in a11y announcements when a node is moved |
107
- | `renderItem` | `Snippet<[item, depth, isExpanded]>` | - | Custom node label renderer |
108
- | `renderIcon` | `Snippet<[item, depth, isExpanded]>` | - | Custom node icon renderer |
109
- | `unstyled` | `boolean` | `false` | Skip default styling |
110
- | `class` | `string` | - | Classes for wrapper |
111
- | `classItem` | `string` | - | Classes for each item button |
112
- | `classItemActive` | `string` | - | Extra classes when an item is active |
113
- | `classIcon` | `string` | - | Classes for the icon wrapper |
114
- | `classLabel` | `string` | - | Classes for the label wrapper |
115
- | `classChevron` | `string` | - | Classes for the expand/collapse chevron |
116
- | `classChildren` | `string` | - | Classes for the children container |
117
- | `el` | `HTMLElement` | - | Bindable wrapper reference |
87
+ | Prop | Type | Default | Description |
88
+ | ------------------ | ---------------------------------------------------- | ----------------- | --------------------------------------------------------------------- |
89
+ | `items` | `TreeNodeDTO<T>[]` | required | Tree data (e.g. from `tree.toJSON().children`) |
90
+ | `activeId` | `string` | - | ID of the currently active/selected node |
91
+ | `isActive` | `(item) => boolean` | - | Alternative to `activeId` for custom active detection |
92
+ | `onSelect` | `(item) => void` | - | Called when a node is selected (click or Enter/Space) |
93
+ | `onToggle` | `(item, expanded) => void` | - | Called when a branch is expanded/collapsed |
94
+ | `sort` | `(a, b) => number` | - | Per-level sort comparator |
95
+ | `defaultExpanded` | `boolean` | `false` | Default expansion state for branches |
96
+ | `expandedIds` | `Set<string>` | - | Initially-expanded branch IDs |
97
+ | `persistState` | `boolean` | `false` | Persist expand/collapse to `localStorage` |
98
+ | `storageKeyPrefix` | `string` | `"stuic-tree"` | Prefix used for `localStorage` keys |
99
+ | `draggable` | `boolean` | `false` | Enable drag-and-drop reordering |
100
+ | `isDraggable` | `(item) => boolean` | - | Return `false` to block dragging a specific node |
101
+ | `isDropTarget` | `(item) => boolean` | - | Return `false` to block dropping onto a specific node |
102
+ | `onMove` | `(event) => void \| false \| Promise<void \| false>` | - | Called when a valid drop happens; return `false` to reject |
103
+ | `onError` | `(err) => void` | - | Called when `onMove` throws |
104
+ | `dragExpandDelay` | `number` | `800` | ms before auto-expanding a collapsed branch hovered during drag |
105
+ | `t` | `TranslateFn` | built-in | Optional translation function (used for drag-drop a11y announcements) |
106
+ | `getNodeLabel` | `(item) => string` | `String(v.value)` | String used in a11y announcements when a node is moved |
107
+ | `renderItem` | `Snippet<[item, depth, isExpanded]>` | - | Custom node label renderer |
108
+ | `renderIcon` | `Snippet<[item, depth, isExpanded]>` | - | Custom node icon renderer |
109
+ | `unstyled` | `boolean` | `false` | Skip default styling |
110
+ | `class` | `string` | - | Classes for wrapper |
111
+ | `classItem` | `string` | - | Classes for each item button |
112
+ | `classItemActive` | `string` | - | Extra classes when an item is active |
113
+ | `classIcon` | `string` | - | Classes for the icon wrapper |
114
+ | `classLabel` | `string` | - | Classes for the label wrapper |
115
+ | `classChevron` | `string` | - | Classes for the expand/collapse chevron |
116
+ | `classChildren` | `string` | - | Classes for the children container |
117
+ | `el` | `HTMLElement` | - | Bindable wrapper reference |
118
118
 
119
119
  ## Keyboard Navigation
120
120
 
121
- | Key | Action |
122
- | --------------------- | --------------------------------------------------------------------- |
123
- | `ArrowDown` | Focus next visible node |
124
- | `ArrowUp` | Focus previous visible node |
125
- | `ArrowRight` | Expand a collapsed branch, or move to first child of an expanded one |
126
- | `ArrowLeft` | Collapse an expanded branch, or move to parent |
127
- | `Home` | Focus first visible node |
128
- | `End` | Focus last visible node |
129
- | `Enter` / `Space` | Toggle expansion (if branch) and fire `onSelect` |
121
+ | Key | Action |
122
+ | ----------------- | -------------------------------------------------------------------- |
123
+ | `ArrowDown` | Focus next visible node |
124
+ | `ArrowUp` | Focus previous visible node |
125
+ | `ArrowRight` | Expand a collapsed branch, or move to first child of an expanded one |
126
+ | `ArrowLeft` | Collapse an expanded branch, or move to parent |
127
+ | `Home` | Focus first visible node |
128
+ | `End` | Focus last visible node |
129
+ | `Enter` / `Space` | Toggle expansion (if branch) and fire `onSelect` |
130
130
 
131
131
  Focus follows the **roving tabindex** pattern: only the currently-focused node has `tabindex=0`, all others are `-1`.
132
132
 
@@ -147,39 +147,39 @@ Override globally in `:root` or locally via `style=""`. Radius / border-width /
147
147
 
148
148
  ### Structure
149
149
 
150
- | Variable | Default | Description |
151
- | ---------------------------------- | ------------------------ | --------------------------------- |
152
- | `--stuic-tree-indent` | `1.25rem` | Indentation per depth level |
153
- | `--stuic-tree-item-padding-x` | `0.375rem` | Item horizontal padding |
154
- | `--stuic-tree-item-padding-y` | `0.125rem` | Item vertical padding |
155
- | `--stuic-tree-item-height` | `1.75rem` | Item row height |
156
- | `--stuic-tree-item-font-size` | `var(--text-sm)` | Item font size |
157
- | `--stuic-tree-item-gap` | `0.25rem` | Gap between chevron, icon, label |
158
- | `--stuic-tree-chevron-size` | `14px` | Chevron icon size |
159
- | `--stuic-tree-chevron-opacity` | `0.5` | Chevron opacity |
160
- | `--stuic-tree-icon-opacity` | `0.7` | Icon opacity |
161
- | `--stuic-tree-item-radius` | `var(--stuic-radius)` | Item border radius |
162
- | `--stuic-tree-transition` | `var(--stuic-transition)`| Transition duration |
150
+ | Variable | Default | Description |
151
+ | ------------------------------ | ------------------------- | -------------------------------- |
152
+ | `--stuic-tree-indent` | `1.25rem` | Indentation per depth level |
153
+ | `--stuic-tree-item-padding-x` | `0.375rem` | Item horizontal padding |
154
+ | `--stuic-tree-item-padding-y` | `0.125rem` | Item vertical padding |
155
+ | `--stuic-tree-item-height` | `1.75rem` | Item row height |
156
+ | `--stuic-tree-item-font-size` | `var(--text-sm)` | Item font size |
157
+ | `--stuic-tree-item-gap` | `0.25rem` | Gap between chevron, icon, label |
158
+ | `--stuic-tree-chevron-size` | `14px` | Chevron icon size |
159
+ | `--stuic-tree-chevron-opacity` | `0.5` | Chevron opacity |
160
+ | `--stuic-tree-icon-opacity` | `0.7` | Icon opacity |
161
+ | `--stuic-tree-item-radius` | `var(--stuic-radius)` | Item border radius |
162
+ | `--stuic-tree-transition` | `var(--stuic-transition)` | Transition duration |
163
163
 
164
164
  ### Colors
165
165
 
166
- | Variable | Default | Description |
167
- | ------------------------------------ | -------------------------------------- | ------------------------- |
168
- | `--stuic-tree-item-bg` | `transparent` | Item background |
169
- | `--stuic-tree-item-text` | `inherit` | Item text color |
170
- | `--stuic-tree-item-bg-hover` | `rgb(0 0 0 / 0.06)` | Hover background |
171
- | `--stuic-tree-item-bg-focus` | `rgb(0 0 0 / 0.06)` | Keyboard-focus background |
172
- | `--stuic-tree-item-bg-active` | `var(--stuic-color-primary)` | Active/selected bg |
173
- | `--stuic-tree-item-text-active` | `var(--stuic-color-primary-foreground)`| Active/selected text |
166
+ | Variable | Default | Description |
167
+ | ------------------------------- | --------------------------------------- | ------------------------- |
168
+ | `--stuic-tree-item-bg` | `transparent` | Item background |
169
+ | `--stuic-tree-item-text` | `inherit` | Item text color |
170
+ | `--stuic-tree-item-bg-hover` | `rgb(0 0 0 / 0.06)` | Hover background |
171
+ | `--stuic-tree-item-bg-focus` | `rgb(0 0 0 / 0.06)` | Keyboard-focus background |
172
+ | `--stuic-tree-item-bg-active` | `var(--stuic-color-primary)` | Active/selected bg |
173
+ | `--stuic-tree-item-text-active` | `var(--stuic-color-primary-foreground)` | Active/selected text |
174
174
 
175
175
  ### Drag & drop
176
176
 
177
- | Variable | Default | Description |
178
- | --------------------------------------- | ---------------------------------- | ------------------------------- |
179
- | `--stuic-tree-item-opacity-dragging` | `0.4` | Opacity of the dragged item |
180
- | `--stuic-tree-drop-indicator-color` | `var(--stuic-color-primary)` | Before/after drop line color |
181
- | `--stuic-tree-drop-indicator-height` | `2px` | Before/after drop line height |
182
- | `--stuic-tree-item-bg-dragover` | `rgb(0 0 0 / 0.04)` | "Inside"-drop highlight |
177
+ | Variable | Default | Description |
178
+ | ------------------------------------ | ---------------------------- | ----------------------------- |
179
+ | `--stuic-tree-item-opacity-dragging` | `0.4` | Opacity of the dragged item |
180
+ | `--stuic-tree-drop-indicator-color` | `var(--stuic-color-primary)` | Before/after drop line color |
181
+ | `--stuic-tree-drop-indicator-height` | `2px` | Before/after drop line height |
182
+ | `--stuic-tree-item-bg-dragover` | `rgb(0 0 0 / 0.04)` | "Inside"-drop highlight |
183
183
 
184
184
  ## Limitations
185
185
 
@@ -219,11 +219,7 @@
219
219
  );
220
220
 
221
221
  const isDark = $derived(
222
- csEnabled
223
- ? cs.isDark
224
- ? cs.isDark()
225
- : ColorScheme.current === "dark"
226
- : false
222
+ csEnabled ? (cs.isDark ? cs.isDark() : ColorScheme.current === "dark") : false
227
223
  );
228
224
 
229
225
  function toggleColorScheme() {
@@ -34,14 +34,14 @@
34
34
 
35
35
  /* border between panels: left position */
36
36
  .stuic-with-side-panel[data-position="left"] .stuic-with-side-panel-side {
37
- border-right: var(--stuic-with-side-panel-border-width, var(--stuic-border-width)) solid
38
- var(--stuic-with-side-panel-border-color);
37
+ border-right: var(--stuic-with-side-panel-border-width, var(--stuic-border-width))
38
+ solid var(--stuic-with-side-panel-border-color);
39
39
  }
40
40
 
41
41
  /* border between panels: right position */
42
42
  .stuic-with-side-panel[data-position="right"] .stuic-with-side-panel-side {
43
- border-left: var(--stuic-with-side-panel-border-width, var(--stuic-border-width)) solid
44
- var(--stuic-with-side-panel-border-color);
43
+ border-left: var(--stuic-with-side-panel-border-width, var(--stuic-border-width))
44
+ solid var(--stuic-with-side-panel-border-color);
45
45
  }
46
46
 
47
47
  .stuic-with-side-panel-main {
package/dist/index.css CHANGED
@@ -32,19 +32,23 @@ In practice:
32
32
  ============================================================================ */
33
33
  :root {
34
34
  /* Radius: three tiers */
35
- --stuic-radius: var(--radius-md); /* elements: inputs, badges, list items */
36
- --stuic-radius-button: var(--radius-md); /* buttons: independent from general elements */
37
- --stuic-radius-container: var(--radius-lg); /* containers: cards, modals, dropdowns, notifications */
35
+ --stuic-radius: var(--radius-md); /* elements: inputs, badges, list items */
36
+ --stuic-radius-button: var(
37
+ --radius-md
38
+ ); /* buttons: independent from general elements */
39
+ --stuic-radius-container: var(
40
+ --radius-lg
41
+ ); /* containers: cards, modals, dropdowns, notifications */
38
42
 
39
43
  /* Shadows */
40
- --stuic-shadow: var(--shadow-sm); /* default resting shadow */
41
- --stuic-shadow-hover: var(--shadow-md); /* hover/elevated shadow */
42
- --stuic-shadow-overlay: var(--shadow-lg); /* overlays: dropdowns, notifications */
43
- --stuic-shadow-dialog: var(--shadow-xl); /* dialogs/modals */
44
+ --stuic-shadow: var(--shadow-sm); /* default resting shadow */
45
+ --stuic-shadow-hover: var(--shadow-md); /* hover/elevated shadow */
46
+ --stuic-shadow-overlay: var(--shadow-lg); /* overlays: dropdowns, notifications */
47
+ --stuic-shadow-dialog: var(--shadow-xl); /* dialogs/modals */
44
48
 
45
49
  /* Border */
46
50
  --stuic-border-width: 1px;
47
- --stuic-border-width-button: 1px; /* buttons: independent from general elements */
51
+ --stuic-border-width-button: 1px; /* buttons: independent from general elements */
48
52
 
49
53
  /* Transition */
50
54
  --stuic-transition: 150ms;
@@ -1,2 +1,2 @@
1
- export { generateCssTokens, generateThemeCss, toCssString } from "@marianmeres/design-tokens";
1
+ export { generateCssTokens, generateThemeCss, toCssString, } from "@marianmeres/design-tokens";
2
2
  export type { ColorPair, ColorValue, GeneratedTokens, IntentColorKey, RolePairedKey, RoleSingleKey, SingleColor, ThemeSchema, TokenSchema, WithOptional, WithRequired, } from "@marianmeres/design-tokens";
@@ -1,2 +1,2 @@
1
1
  // Re-export core API from @marianmeres/design-tokens
2
- export { generateCssTokens, generateThemeCss, toCssString } from "@marianmeres/design-tokens";
2
+ export { generateCssTokens, generateThemeCss, toCssString, } from "@marianmeres/design-tokens";
@@ -124,13 +124,13 @@ Props → Component → Data Attributes → CSS Selectors
124
124
 
125
125
  ## Key Files
126
126
 
127
- | File | Purpose |
128
- | -------------------------------- | --------------------------------------------------------------------------- |
129
- | `src/lib/index.css` | CSS entry point (import this) |
130
- | `src/lib/index.ts` | JS entry point (barrel export) |
131
- | `@marianmeres/design-tokens/css/stone.css` | Default theme (42 themes available) |
132
- | `src/lib/utils/tw-merge.ts` | Tailwind class merging |
133
- | `src/lib/utils/design-tokens.ts` | Theme types (`ThemeSchema`, `ColorPair`, etc.) and CSS generation functions |
127
+ | File | Purpose |
128
+ | ------------------------------------------ | --------------------------------------------------------------------------- |
129
+ | `src/lib/index.css` | CSS entry point (import this) |
130
+ | `src/lib/index.ts` | JS entry point (barrel export) |
131
+ | `@marianmeres/design-tokens/css/stone.css` | Default theme (42 themes available) |
132
+ | `src/lib/utils/tw-merge.ts` | Tailwind class merging |
133
+ | `src/lib/utils/design-tokens.ts` | Theme types (`ThemeSchema`, `ColorPair`, etc.) and CSS generation functions |
134
134
 
135
135
  ---
136
136
 
@@ -8,9 +8,9 @@ vitest-browser-svelte docs. Planning artifact; no code was changed.
8
8
  # @marianmeres/stuic — Component Testing: Overview & Roadmap
9
9
 
10
10
  > **Verdict:** the proposed stack — Vitest Browser Mode + `vitest-browser-svelte` + Playwright/Chromium
11
- > — is the right default for *this* library, where the value being shipped is precisely the DOM/layout/
11
+ > — is the right default for _this_ library, where the value being shipped is precisely the DOM/layout/
12
12
  > focus/positioning behavior that the current node/server-build test setup **cannot exercise at all**.
13
- > The claim is *mostly* correct rather than gospel: it's not an officially-mandated singular standard
13
+ > The claim is _mostly_ correct rather than gospel: it's not an officially-mandated singular standard
14
14
  > (svelte.dev still nominally leads with jsdom + @testing-library), and one term was dated — modern
15
15
  > spelling is `vitest-browser-svelte` + **`@vitest/browser-playwright`** + `playwright` on **Vitest 4**.
16
16
  >
@@ -32,28 +32,28 @@ vitest-browser-svelte docs. Planning artifact; no code was changed.
32
32
 
33
33
  [`docs/testing.md`](../testing.md) records a deliberate decision **not** to test component rendering
34
34
  ("50+ components × prop combos = slow suite, tiny yield; rendering is gated by svelte-check + publint +
35
- build"). That reasoning holds for *"does it render"* and we keep it. What changes: browser mode lets
36
- us test *"does it **behave**"* — events, two-way binding, aria/disabled/active state, focus traps,
35
+ build"). That reasoning holds for _"does it render"_ and we keep it. What changes: browser mode lets
36
+ us test _"does it **behave**"_ — events, two-way binding, aria/disabled/active state, focus traps,
37
37
  viewport-clamped positioning (cf. the `9d8c974` annotation regression) — which the build does **not**
38
38
  cover and which was **previously impossible**. Updating `docs/testing.md` to add this layer is an
39
39
  explicit sprint task so the docs don't contradict each other.
40
40
 
41
41
  ## Top recommendations across all dimensions (ranked)
42
42
 
43
- | Rank | Recommendation | Dimension | Value | Effort | Risk | Why now |
44
- |------|----------------|-----------|-------|--------|------|---------|
45
- | 1 | Upgrade vitest 3→4, verify 9 suites green | [01](./01-framework-setup.md) | high | S | med | Gating prerequisite; nothing installs without it |
46
- | 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
- | 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 |
49
- | 5 | Button — flagship; sets every assertion pattern | [03](./03-component-coverage-roadmap.md) | high | S | low | Most-used primitive; template for the rest |
50
- | 6 | Pill, Switch — events + binding patterns | [03](./03-component-coverage-roadmap.md) | high | S | low | Cover dismiss/toggle/bind once, reuse everywhere |
51
- | 7 | Spinner, Skeleton, DismissibleMessage, Avatar, Progress | [03](./03-component-coverage-roadmap.md) | high | S | low | Deterministic, high-traffic; quick wins |
52
- | 8 | **One hard proof** — anchor-position viewport clamp (or focus trap) | [04](./04-hard-cases-and-e2e.md) | high | M | med | Guards a real recent regression; proves browser mode's worth |
53
- | 9 | Minimal GitHub Actions workflow | [05](./05-ci.md) | high | S | low | Stops broken tests reaching npm; once a few tests pass |
54
- | 10 | Tier-2 form fields (`FieldInput` first, then the family) | [03](./03-component-coverage-roadmap.md) | med | M | low | Largest component group; one pattern unlocks many |
55
- | 11 | Portals/focus-traps in browser mode (Modal/Drawer/Backdrop) | [04](./04-hard-cases-and-e2e.md) | med | M | med | High-value a11y contracts; after patterns settle |
56
- | 12 | Standalone Playwright E2E layer (drag, Milkdown, checkout flows) | [04](./04-hard-cases-and-e2e.md) | med | L | med | Separate later initiative; explicitly out of sprint 1 |
43
+ | Rank | Recommendation | Dimension | Value | Effort | Risk | Why now |
44
+ | ---- | ------------------------------------------------------------------- | ---------------------------------------- | ----- | ------ | ---- | ------------------------------------------------------------ |
45
+ | 1 | Upgrade vitest 3→4, verify 9 suites green | [01](./01-framework-setup.md) | high | S | med | Gating prerequisite; nothing installs without it |
46
+ | 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
+ | 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 |
49
+ | 5 | Button — flagship; sets every assertion pattern | [03](./03-component-coverage-roadmap.md) | high | S | low | Most-used primitive; template for the rest |
50
+ | 6 | Pill, Switch — events + binding patterns | [03](./03-component-coverage-roadmap.md) | high | S | low | Cover dismiss/toggle/bind once, reuse everywhere |
51
+ | 7 | Spinner, Skeleton, DismissibleMessage, Avatar, Progress | [03](./03-component-coverage-roadmap.md) | high | S | low | Deterministic, high-traffic; quick wins |
52
+ | 8 | **One hard proof** — anchor-position viewport clamp (or focus trap) | [04](./04-hard-cases-and-e2e.md) | high | M | med | Guards a real recent regression; proves browser mode's worth |
53
+ | 9 | Minimal GitHub Actions workflow | [05](./05-ci.md) | high | S | low | Stops broken tests reaching npm; once a few tests pass |
54
+ | 10 | Tier-2 form fields (`FieldInput` first, then the family) | [03](./03-component-coverage-roadmap.md) | med | M | low | Largest component group; one pattern unlocks many |
55
+ | 11 | Portals/focus-traps in browser mode (Modal/Drawer/Backdrop) | [04](./04-hard-cases-and-e2e.md) | med | M | med | High-value a11y contracts; after patterns settle |
56
+ | 12 | Standalone Playwright E2E layer (drag, Milkdown, checkout flows) | [04](./04-hard-cases-and-e2e.md) | med | L | med | Separate later initiative; explicitly out of sprint 1 |
57
57
 
58
58
  > **Deliberately deferred as low-yield:** visual-regression / `toMatchScreenshot`, multi-browser
59
59
  > (Firefox/WebKit) matrix, and exhaustive prop-matrix coverage. Revisit only if motivated by a real bug.
@@ -65,7 +65,7 @@ Branch: `feat/component-testing`. One commit per task.
65
65
  1. **Vitest 4 upgrade (#1)** — `pnpm add -D vitest@^4`, run `pnpm test`, confirm 9 suites green. Why
66
66
  first: everything else peer-depends on it; isolating it makes the one risky bump reversible.
67
67
  2. **Browser harness (#2, #3)** — add browser deps, the `projects` config, `playwright install
68
- chromium`, fix the test scripts, and land the Separator smoke test. Unblocks all component tests
68
+ chromium`, fix the test scripts, and land the Separator smoke test. Unblocks all component tests
69
69
  and proves the server-build blocker is gone. Detail in [01](./01-framework-setup.md).
70
70
  3. **Reconcile `docs/testing.md` (#4)** — small doc edit so the philosophy matches reality.
71
71
  4. **Button (#5)** — establishes the assertion vocabulary ([02](./02-test-conventions.md)) every later