@marianmeres/stuic 3.66.1 → 3.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/autoscroll.d.ts +7 -0
- package/dist/actions/autoscroll.js +7 -0
- package/dist/actions/focus-trap.d.ts +7 -0
- package/dist/actions/focus-trap.js +8 -3
- package/dist/actions/typeahead.svelte.js +40 -4
- package/dist/components/Carousel/Carousel.svelte +9 -2
- package/dist/components/Carousel/README.md +8 -2
- package/dist/components/Cart/Cart.svelte +3 -0
- package/dist/components/Cart/README.md +18 -1
- package/dist/components/Checkout/CheckoutOrderReview.svelte +4 -14
- package/dist/components/Checkout/README.md +184 -0
- package/dist/components/Checkout/_internal/checkout-utils.d.ts +6 -0
- package/dist/components/Checkout/_internal/checkout-utils.js +24 -0
- package/dist/components/Checkout/index.d.ts +1 -1
- package/dist/components/Checkout/index.js +1 -1
- package/dist/components/CommandMenu/CommandMenu.svelte +23 -7
- package/dist/components/CommandMenu/CommandMenu.svelte.d.ts +2 -0
- package/dist/components/CronInput/CronInput.svelte +44 -9
- package/dist/components/CronInput/CronInput.svelte.d.ts +2 -0
- package/dist/components/CronInput/README.md +145 -0
- package/dist/components/CronInput/cron-next-run.svelte.d.ts +11 -0
- package/dist/components/CronInput/cron-next-run.svelte.js +11 -0
- package/dist/components/CronInput/index.css +0 -8
- package/dist/components/DataTable/DataTable.svelte +276 -83
- package/dist/components/DataTable/DataTable.svelte.d.ts +58 -6
- package/dist/components/DataTable/README.md +155 -25
- package/dist/components/DataTable/index.css +31 -0
- package/dist/components/DropdownMenu/DropdownMenu.svelte +43 -26
- package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +5 -1
- package/dist/components/DropdownMenu/README.md +37 -9
- package/dist/components/Input/FieldAssets.svelte +9 -7
- package/dist/components/Input/FieldAssets.svelte.d.ts +3 -7
- package/dist/components/Input/FieldFile.svelte +13 -7
- package/dist/components/Input/FieldFile.svelte.d.ts +4 -7
- package/dist/components/Input/FieldInput.svelte +10 -8
- package/dist/components/Input/FieldInput.svelte.d.ts +3 -8
- package/dist/components/Input/FieldInputLocalized.svelte +8 -7
- package/dist/components/Input/FieldInputLocalized.svelte.d.ts +2 -7
- package/dist/components/Input/FieldKeyValues.svelte +8 -7
- package/dist/components/Input/FieldKeyValues.svelte.d.ts +2 -7
- package/dist/components/Input/FieldLikeButton.svelte +9 -7
- package/dist/components/Input/FieldLikeButton.svelte.d.ts +3 -7
- package/dist/components/Input/FieldObject.svelte +8 -7
- package/dist/components/Input/FieldObject.svelte.d.ts +2 -7
- package/dist/components/Input/FieldOptions.svelte +9 -7
- package/dist/components/Input/FieldOptions.svelte.d.ts +3 -7
- package/dist/components/Input/FieldPhoneNumber.svelte +7 -8
- package/dist/components/Input/FieldPhoneNumber.svelte.d.ts +3 -8
- package/dist/components/Input/FieldSelect.svelte +9 -8
- package/dist/components/Input/FieldSelect.svelte.d.ts +3 -8
- package/dist/components/Input/FieldSwitch.svelte +9 -7
- package/dist/components/Input/FieldSwitch.svelte.d.ts +3 -7
- package/dist/components/Input/FieldTextarea.svelte +7 -8
- package/dist/components/Input/FieldTextarea.svelte.d.ts +3 -8
- package/dist/components/Input/README.md +20 -0
- package/dist/components/Input/_internal/InputWrap.svelte +2 -10
- package/dist/components/Input/_internal/InputWrap.svelte.d.ts +2 -10
- package/dist/components/Input/types.d.ts +28 -0
- package/dist/components/Nav/Nav.svelte +5 -4
- package/dist/components/Nav/Nav.svelte.d.ts +2 -2
- package/dist/components/Nav/README.md +2 -2
- package/dist/components/Nav/index.css +4 -0
- package/dist/components/Tree/README.md +189 -0
- package/dist/components/Tree/Tree.svelte +46 -2
- package/dist/components/Tree/Tree.svelte.d.ts +5 -0
- package/dist/utils/input-history.svelte.d.ts +12 -0
- package/dist/utils/input-history.svelte.js +12 -0
- package/dist/utils/observe-exists.svelte.d.ts +1 -0
- package/dist/utils/observe-exists.svelte.js +11 -3
- package/dist/utils/switch.svelte.d.ts +12 -0
- package/dist/utils/switch.svelte.js +12 -1
- package/docs/architecture.md +0 -1
- package/docs/testing.md +72 -0
- package/docs/upgrading.md +281 -0
- package/package.json +12 -13
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
import type { HTMLSelectAttributes } from "svelte/elements";
|
|
4
4
|
import type { ValidateOptions } from "../../actions/validate.svelte.js";
|
|
5
5
|
import type { THC } from "../Thc/Thc.svelte";
|
|
6
|
-
import type { FieldSelectOption } from "./types.js";
|
|
6
|
+
import type { FieldSelectOption, InputWrapClassProps } from "./types.js";
|
|
7
7
|
|
|
8
8
|
type SnippetWithId = Snippet<[{ id: string }]>;
|
|
9
9
|
|
|
10
|
-
export interface Props extends HTMLSelectAttributes {
|
|
10
|
+
export interface Props extends HTMLSelectAttributes, InputWrapClassProps {
|
|
11
11
|
input?: HTMLSelectElement;
|
|
12
12
|
value?: string | number;
|
|
13
13
|
label?: SnippetWithId | THC;
|
|
@@ -28,13 +28,8 @@
|
|
|
28
28
|
labelLeft?: boolean;
|
|
29
29
|
labelLeftWidth?: "normal" | "wide";
|
|
30
30
|
labelLeftBreakpoint?: number;
|
|
31
|
+
/** Classes for the <select> element */
|
|
31
32
|
classInput?: string;
|
|
32
|
-
classLabel?: string;
|
|
33
|
-
classLabelBox?: string;
|
|
34
|
-
classInputBox?: string;
|
|
35
|
-
classInputBoxWrap?: string;
|
|
36
|
-
classDescBox?: string;
|
|
37
|
-
classBelowBox?: string;
|
|
38
33
|
style?: string;
|
|
39
34
|
}
|
|
40
35
|
</script>
|
|
@@ -82,8 +77,11 @@
|
|
|
82
77
|
classLabelBox,
|
|
83
78
|
classInputBox,
|
|
84
79
|
classInputBoxWrap,
|
|
80
|
+
classInputBoxWrapInvalid,
|
|
85
81
|
classDescBox,
|
|
82
|
+
classDescBoxToggle,
|
|
86
83
|
classBelowBox,
|
|
84
|
+
classValidationBox,
|
|
87
85
|
style,
|
|
88
86
|
//
|
|
89
87
|
...rest
|
|
@@ -128,8 +126,11 @@
|
|
|
128
126
|
{classLabelBox}
|
|
129
127
|
{classInputBox}
|
|
130
128
|
{classInputBoxWrap}
|
|
129
|
+
{classInputBoxWrapInvalid}
|
|
131
130
|
{classDescBox}
|
|
131
|
+
{classDescBoxToggle}
|
|
132
132
|
{classBelowBox}
|
|
133
|
+
{classValidationBox}
|
|
133
134
|
{validation}
|
|
134
135
|
{style}
|
|
135
136
|
>
|
|
@@ -2,11 +2,11 @@ import type { Snippet } from "svelte";
|
|
|
2
2
|
import type { HTMLSelectAttributes } from "svelte/elements";
|
|
3
3
|
import type { ValidateOptions } from "../../actions/validate.svelte.js";
|
|
4
4
|
import type { THC } from "../Thc/Thc.svelte";
|
|
5
|
-
import type { FieldSelectOption } from "./types.js";
|
|
5
|
+
import type { FieldSelectOption, InputWrapClassProps } from "./types.js";
|
|
6
6
|
type SnippetWithId = Snippet<[{
|
|
7
7
|
id: string;
|
|
8
8
|
}]>;
|
|
9
|
-
export interface Props extends HTMLSelectAttributes {
|
|
9
|
+
export interface Props extends HTMLSelectAttributes, InputWrapClassProps {
|
|
10
10
|
input?: HTMLSelectElement;
|
|
11
11
|
value?: string | number;
|
|
12
12
|
label?: SnippetWithId | THC;
|
|
@@ -27,13 +27,8 @@ export interface Props extends HTMLSelectAttributes {
|
|
|
27
27
|
labelLeft?: boolean;
|
|
28
28
|
labelLeftWidth?: "normal" | "wide";
|
|
29
29
|
labelLeftBreakpoint?: number;
|
|
30
|
+
/** Classes for the <select> element */
|
|
30
31
|
classInput?: string;
|
|
31
|
-
classLabel?: string;
|
|
32
|
-
classLabelBox?: string;
|
|
33
|
-
classInputBox?: string;
|
|
34
|
-
classInputBoxWrap?: string;
|
|
35
|
-
classDescBox?: string;
|
|
36
|
-
classBelowBox?: string;
|
|
37
32
|
style?: string;
|
|
38
33
|
}
|
|
39
34
|
declare const FieldSelect: import("svelte").Component<Props, {}, "value" | "input">;
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
import type { ValidateOptions } from "../../actions/validate.svelte.js";
|
|
4
4
|
import type { THC } from "../Thc/Thc.svelte";
|
|
5
|
+
import type { InputWrapClassProps } from "./types.js";
|
|
5
6
|
|
|
6
7
|
type SnippetWithId = Snippet<[{ id: string }]>;
|
|
7
8
|
|
|
8
|
-
export interface Props extends Record<string, any> {
|
|
9
|
+
export interface Props extends InputWrapClassProps, Record<string, any> {
|
|
9
10
|
input?: HTMLInputElement;
|
|
10
11
|
checked?: boolean;
|
|
11
12
|
label?: SnippetWithId | THC;
|
|
@@ -27,13 +28,8 @@
|
|
|
27
28
|
labelLeft?: boolean;
|
|
28
29
|
labelLeftWidth?: "normal" | "wide";
|
|
29
30
|
labelLeftBreakpoint?: number;
|
|
31
|
+
/** Classes for the underlying <Switch> element */
|
|
30
32
|
classInput?: string;
|
|
31
|
-
classLabel?: string;
|
|
32
|
-
classLabelBox?: string;
|
|
33
|
-
classInputBox?: string;
|
|
34
|
-
classInputBoxWrap?: string;
|
|
35
|
-
classDescBox?: string;
|
|
36
|
-
classBelowBox?: string;
|
|
37
33
|
style?: string;
|
|
38
34
|
renderValue?: (rawValue: any) => string;
|
|
39
35
|
}
|
|
@@ -79,8 +75,11 @@
|
|
|
79
75
|
classLabelBox,
|
|
80
76
|
classInputBox,
|
|
81
77
|
classInputBoxWrap,
|
|
78
|
+
classInputBoxWrapInvalid,
|
|
82
79
|
classDescBox,
|
|
80
|
+
classDescBoxToggle,
|
|
83
81
|
classBelowBox,
|
|
82
|
+
classValidationBox,
|
|
84
83
|
style = "",
|
|
85
84
|
//
|
|
86
85
|
renderValue,
|
|
@@ -111,8 +110,11 @@
|
|
|
111
110
|
{classLabel}
|
|
112
111
|
{classLabelBox}
|
|
113
112
|
{classInputBox}
|
|
113
|
+
{classInputBoxWrapInvalid}
|
|
114
114
|
{classDescBox}
|
|
115
|
+
{classDescBoxToggle}
|
|
115
116
|
{classBelowBox}
|
|
117
|
+
{classValidationBox}
|
|
116
118
|
{validation}
|
|
117
119
|
classInputBoxWrap={twMerge("input-wrap-transparent", classInputBoxWrap)}
|
|
118
120
|
{style}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
2
|
import type { ValidateOptions } from "../../actions/validate.svelte.js";
|
|
3
3
|
import type { THC } from "../Thc/Thc.svelte";
|
|
4
|
+
import type { InputWrapClassProps } from "./types.js";
|
|
4
5
|
type SnippetWithId = Snippet<[{
|
|
5
6
|
id: string;
|
|
6
7
|
}]>;
|
|
7
|
-
export interface Props extends Record<string, any> {
|
|
8
|
+
export interface Props extends InputWrapClassProps, Record<string, any> {
|
|
8
9
|
input?: HTMLInputElement;
|
|
9
10
|
checked?: boolean;
|
|
10
11
|
label?: SnippetWithId | THC;
|
|
@@ -26,13 +27,8 @@ export interface Props extends Record<string, any> {
|
|
|
26
27
|
labelLeft?: boolean;
|
|
27
28
|
labelLeftWidth?: "normal" | "wide";
|
|
28
29
|
labelLeftBreakpoint?: number;
|
|
30
|
+
/** Classes for the underlying <Switch> element */
|
|
29
31
|
classInput?: string;
|
|
30
|
-
classLabel?: string;
|
|
31
|
-
classLabelBox?: string;
|
|
32
|
-
classInputBox?: string;
|
|
33
|
-
classInputBoxWrap?: string;
|
|
34
|
-
classDescBox?: string;
|
|
35
|
-
classBelowBox?: string;
|
|
36
32
|
style?: string;
|
|
37
33
|
renderValue?: (rawValue: any) => string;
|
|
38
34
|
}
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
import type { HTMLTextareaAttributes } from "svelte/elements";
|
|
4
4
|
import type { ValidateOptions } from "../../actions/validate.svelte.js";
|
|
5
5
|
import type { THC } from "../Thc/Thc.svelte";
|
|
6
|
+
import type { InputWrapClassProps } from "./types.js";
|
|
6
7
|
|
|
7
8
|
type SnippetWithId = Snippet<[{ id: string }]>;
|
|
8
9
|
|
|
9
|
-
export interface Props extends HTMLTextareaAttributes {
|
|
10
|
+
export interface Props extends HTMLTextareaAttributes, InputWrapClassProps {
|
|
10
11
|
input?: HTMLTextAreaElement;
|
|
11
12
|
value?: string;
|
|
12
13
|
label?: SnippetWithId | THC;
|
|
@@ -28,14 +29,8 @@
|
|
|
28
29
|
labelLeft?: boolean;
|
|
29
30
|
labelLeftWidth?: "normal" | "wide";
|
|
30
31
|
labelLeftBreakpoint?: number;
|
|
32
|
+
/** Classes for the <textarea> element */
|
|
31
33
|
classInput?: string;
|
|
32
|
-
classLabel?: string;
|
|
33
|
-
classLabelBox?: string;
|
|
34
|
-
classInputBox?: string;
|
|
35
|
-
classInputBoxWrap?: string;
|
|
36
|
-
classInputBoxWrapInvalid?: string;
|
|
37
|
-
classDescBox?: string;
|
|
38
|
-
classBelowBox?: string;
|
|
39
34
|
style?: string;
|
|
40
35
|
}
|
|
41
36
|
</script>
|
|
@@ -85,7 +80,9 @@
|
|
|
85
80
|
classInputBoxWrap,
|
|
86
81
|
classInputBoxWrapInvalid,
|
|
87
82
|
classDescBox,
|
|
83
|
+
classDescBoxToggle,
|
|
88
84
|
classBelowBox,
|
|
85
|
+
classValidationBox,
|
|
89
86
|
style,
|
|
90
87
|
//
|
|
91
88
|
...rest
|
|
@@ -117,7 +114,9 @@
|
|
|
117
114
|
{classInputBoxWrap}
|
|
118
115
|
{classInputBoxWrapInvalid}
|
|
119
116
|
{classDescBox}
|
|
117
|
+
{classDescBoxToggle}
|
|
120
118
|
{classBelowBox}
|
|
119
|
+
{classValidationBox}
|
|
121
120
|
{validation}
|
|
122
121
|
{style}
|
|
123
122
|
>
|
|
@@ -2,10 +2,11 @@ import type { Snippet } from "svelte";
|
|
|
2
2
|
import type { HTMLTextareaAttributes } from "svelte/elements";
|
|
3
3
|
import type { ValidateOptions } from "../../actions/validate.svelte.js";
|
|
4
4
|
import type { THC } from "../Thc/Thc.svelte";
|
|
5
|
+
import type { InputWrapClassProps } from "./types.js";
|
|
5
6
|
type SnippetWithId = Snippet<[{
|
|
6
7
|
id: string;
|
|
7
8
|
}]>;
|
|
8
|
-
export interface Props extends HTMLTextareaAttributes {
|
|
9
|
+
export interface Props extends HTMLTextareaAttributes, InputWrapClassProps {
|
|
9
10
|
input?: HTMLTextAreaElement;
|
|
10
11
|
value?: string;
|
|
11
12
|
label?: SnippetWithId | THC;
|
|
@@ -30,14 +31,8 @@ export interface Props extends HTMLTextareaAttributes {
|
|
|
30
31
|
labelLeft?: boolean;
|
|
31
32
|
labelLeftWidth?: "normal" | "wide";
|
|
32
33
|
labelLeftBreakpoint?: number;
|
|
34
|
+
/** Classes for the <textarea> element */
|
|
33
35
|
classInput?: string;
|
|
34
|
-
classLabel?: string;
|
|
35
|
-
classLabelBox?: string;
|
|
36
|
-
classInputBox?: string;
|
|
37
|
-
classInputBoxWrap?: string;
|
|
38
|
-
classInputBoxWrapInvalid?: string;
|
|
39
|
-
classDescBox?: string;
|
|
40
|
-
classBelowBox?: string;
|
|
41
36
|
style?: string;
|
|
42
37
|
}
|
|
43
38
|
declare const FieldTextarea: import("svelte").Component<Props, {}, "value" | "input">;
|
|
@@ -48,6 +48,26 @@ A comprehensive form input system with multiple field components, validation sup
|
|
|
48
48
|
| `inputBelow` | `Snippet \| THC` | Content below input |
|
|
49
49
|
| `below` | `Snippet \| THC` | Content below entire field |
|
|
50
50
|
|
|
51
|
+
## Shared wrapper class props (`InputWrapClassProps`)
|
|
52
|
+
|
|
53
|
+
Every `Field*` component that uses the shared label/input/description scaffolding accepts **the same 9 class props**, exported as the `InputWrapClassProps` interface from `@marianmeres/stuic`. Each Field extends that interface in its own `Props`, so the shape stays in sync and new wrapper targets are added in one place.
|
|
54
|
+
|
|
55
|
+
| Prop | Target |
|
|
56
|
+
| --------------------------- | ------------------------------------------------------------------ |
|
|
57
|
+
| `classLabel` | `<label>` element |
|
|
58
|
+
| `classLabelBox` | Wrapper around the label area |
|
|
59
|
+
| `classInputBox` | Wrapper around the whole input area |
|
|
60
|
+
| `classInputBoxWrap` | Inner input wrap (sibling to description/validation/below) |
|
|
61
|
+
| `classInputBoxWrapInvalid` | Added to `classInputBoxWrap` when validation fails |
|
|
62
|
+
| `classDescBox` | Description/help text box |
|
|
63
|
+
| `classDescBoxToggle` | Collapsible description's toggle button |
|
|
64
|
+
| `classBelowBox` | "Below" slot (rendered under the description) |
|
|
65
|
+
| `classValidationBox` | Validation message box |
|
|
66
|
+
|
|
67
|
+
Component-specific targets (e.g. `classInput` for the inner `<input>`/`<select>`/`<textarea>`, `classFileList` on `FieldFile`, `classOption`/`classOptgroup` on `FieldOptions`, `classPrefixTrigger` on `FieldPhoneNumber`, etc.) live on the component itself alongside these shared props.
|
|
68
|
+
|
|
69
|
+
> `FieldCheckbox` and `FieldRadios` have bespoke inline layouts and don't use the shared wrapper — they declare only the class props relevant to their own layout.
|
|
70
|
+
|
|
51
71
|
## Usage
|
|
52
72
|
|
|
53
73
|
### Basic Text Input
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
import { twMerge } from "../../../utils/tw-merge.js";
|
|
6
6
|
import { Collapsible } from "../../Collapsible/index.js";
|
|
7
7
|
import Thc, { isTHCNotEmpty, type THC } from "../../Thc/Thc.svelte";
|
|
8
|
+
import type { InputWrapClassProps } from "../types.js";
|
|
8
9
|
|
|
9
10
|
type SnippetWithId = Snippet<[{ id: string }]>;
|
|
10
11
|
|
|
11
|
-
interface Props {
|
|
12
|
+
interface Props extends InputWrapClassProps {
|
|
12
13
|
id: string;
|
|
13
14
|
size?: "sm" | "md" | "lg" | string;
|
|
14
15
|
class?: string;
|
|
@@ -28,15 +29,6 @@
|
|
|
28
29
|
labelLeftWidth?: "normal" | "wide";
|
|
29
30
|
labelLeftBreakpoint?: number;
|
|
30
31
|
//
|
|
31
|
-
classLabel?: string;
|
|
32
|
-
classLabelBox?: string;
|
|
33
|
-
classInputBox?: string;
|
|
34
|
-
classInputBoxWrap?: string;
|
|
35
|
-
classInputBoxWrapInvalid?: string;
|
|
36
|
-
classDescBox?: string;
|
|
37
|
-
classDescBoxToggle?: string;
|
|
38
|
-
classBelowBox?: string;
|
|
39
|
-
classValidationBox?: string;
|
|
40
32
|
descriptionCollapsible?: boolean;
|
|
41
33
|
descriptionDefaultExpanded?: boolean;
|
|
42
34
|
style?: string;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
2
|
import type { ValidationResult } from "../../../actions/validate.svelte.js";
|
|
3
3
|
import { type THC } from "../../Thc/Thc.svelte";
|
|
4
|
+
import type { InputWrapClassProps } from "../types.js";
|
|
4
5
|
type SnippetWithId = Snippet<[{
|
|
5
6
|
id: string;
|
|
6
7
|
}]>;
|
|
7
|
-
interface Props {
|
|
8
|
+
interface Props extends InputWrapClassProps {
|
|
8
9
|
id: string;
|
|
9
10
|
size?: "sm" | "md" | "lg" | string;
|
|
10
11
|
class?: string;
|
|
@@ -22,15 +23,6 @@ interface Props {
|
|
|
22
23
|
labelLeft?: boolean;
|
|
23
24
|
labelLeftWidth?: "normal" | "wide";
|
|
24
25
|
labelLeftBreakpoint?: number;
|
|
25
|
-
classLabel?: string;
|
|
26
|
-
classLabelBox?: string;
|
|
27
|
-
classInputBox?: string;
|
|
28
|
-
classInputBoxWrap?: string;
|
|
29
|
-
classInputBoxWrapInvalid?: string;
|
|
30
|
-
classDescBox?: string;
|
|
31
|
-
classDescBoxToggle?: string;
|
|
32
|
-
classBelowBox?: string;
|
|
33
|
-
classValidationBox?: string;
|
|
34
26
|
descriptionCollapsible?: boolean;
|
|
35
27
|
descriptionDefaultExpanded?: boolean;
|
|
36
28
|
style?: string;
|
|
@@ -9,3 +9,31 @@ export interface FieldRadiosOption {
|
|
|
9
9
|
value?: string;
|
|
10
10
|
description?: THC;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Class props forwarded to the shared `InputWrap` scaffolding used by every
|
|
14
|
+
* `Field*` component that has a label/input/description layout.
|
|
15
|
+
*
|
|
16
|
+
* Field components extend this interface in their own `Props` so the class-prop
|
|
17
|
+
* shape stays consistent across the library. Component-specific class props
|
|
18
|
+
* (e.g. `classInput`, `classFileList`, `classOption`) live on the component itself.
|
|
19
|
+
*/
|
|
20
|
+
export interface InputWrapClassProps {
|
|
21
|
+
/** Classes for the <label> element */
|
|
22
|
+
classLabel?: string;
|
|
23
|
+
/** Classes for the wrapper around the label area */
|
|
24
|
+
classLabelBox?: string;
|
|
25
|
+
/** Classes for the wrapper around the input area (contains input + inputBefore/After/Below) */
|
|
26
|
+
classInputBox?: string;
|
|
27
|
+
/** Classes for the inner input wrap (sibling to description/validation/below) */
|
|
28
|
+
classInputBoxWrap?: string;
|
|
29
|
+
/** Extra classes applied to `classInputBoxWrap` when the field is invalid */
|
|
30
|
+
classInputBoxWrapInvalid?: string;
|
|
31
|
+
/** Classes for the description box (the subtle hint below the input) */
|
|
32
|
+
classDescBox?: string;
|
|
33
|
+
/** Classes for the description's collapsible toggle button */
|
|
34
|
+
classDescBoxToggle?: string;
|
|
35
|
+
/** Classes for the "below" slot — rendered under the description */
|
|
36
|
+
classBelowBox?: string;
|
|
37
|
+
/** Classes for the validation message box */
|
|
38
|
+
classValidationBox?: string;
|
|
39
|
+
}
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
id: string;
|
|
12
12
|
/** Display label (supports localization) */
|
|
13
13
|
label: MaybeLocalized;
|
|
14
|
-
/** Navigation URL
|
|
14
|
+
/** Navigation URL. If both `href` and `onClick` are provided, `onClick` runs first (useful for analytics) and the browser then follows the href. */
|
|
15
15
|
href?: string;
|
|
16
|
-
/** Click handler
|
|
16
|
+
/** Click handler. Alternative to `href`, or runs before navigation if both are set. */
|
|
17
17
|
onClick?: () => void;
|
|
18
18
|
/** Icon content (THC for flexibility: string, html, component) */
|
|
19
19
|
icon?: THC;
|
|
@@ -603,7 +603,7 @@
|
|
|
603
603
|
class={twMerge(!unstyled && NAV_CHILDREN_CLASSES, classChildren)}
|
|
604
604
|
transition:slide={{ duration: transitionDuration }}
|
|
605
605
|
>
|
|
606
|
-
{#each item.children ?? [] as child}
|
|
606
|
+
{#each item.children ?? [] as child (child.id)}
|
|
607
607
|
{@render renderItem(child, depth + 1)}
|
|
608
608
|
{/each}
|
|
609
609
|
</ul>
|
|
@@ -627,6 +627,7 @@
|
|
|
627
627
|
data-expanding={!unstyled && isExpanding ? "" : undefined}
|
|
628
628
|
data-disabled={!unstyled && item.disabled ? "" : undefined}
|
|
629
629
|
data-touch-friendly={!unstyled && isTouchFriendly ? "" : undefined}
|
|
630
|
+
aria-current={active ? "page" : undefined}
|
|
630
631
|
aria-disabled={item.disabled}
|
|
631
632
|
tabindex={item.disabled ? -1 : 0}
|
|
632
633
|
use:tooltip={() => ({
|
|
@@ -691,7 +692,7 @@
|
|
|
691
692
|
</li>
|
|
692
693
|
{/snippet}
|
|
693
694
|
|
|
694
|
-
{#each group.items ?? [] as item}
|
|
695
|
+
{#each group.items ?? [] as item (item.id)}
|
|
695
696
|
{@render renderItem(item, 0)}
|
|
696
697
|
{/each}
|
|
697
698
|
</ul>
|
|
@@ -9,9 +9,9 @@ export interface NavItem {
|
|
|
9
9
|
id: string;
|
|
10
10
|
/** Display label (supports localization) */
|
|
11
11
|
label: MaybeLocalized;
|
|
12
|
-
/** Navigation URL
|
|
12
|
+
/** Navigation URL. If both `href` and `onClick` are provided, `onClick` runs first (useful for analytics) and the browser then follows the href. */
|
|
13
13
|
href?: string;
|
|
14
|
-
/** Click handler
|
|
14
|
+
/** Click handler. Alternative to `href`, or runs before navigation if both are set. */
|
|
15
15
|
onClick?: () => void;
|
|
16
16
|
/** Icon content (THC for flexibility: string, html, component) */
|
|
17
17
|
icon?: THC;
|
|
@@ -48,8 +48,8 @@ interface NavGroup {
|
|
|
48
48
|
items?: NavItem[];
|
|
49
49
|
/** Group icon (optional) */
|
|
50
50
|
icon?: THC;
|
|
51
|
-
/** Whether the group starts collapsed */
|
|
52
|
-
|
|
51
|
+
/** Whether the group starts expanded (default: false — groups are collapsed by default) */
|
|
52
|
+
defaultExpanded?: boolean;
|
|
53
53
|
/** Navigation URL for groups without items */
|
|
54
54
|
href?: string;
|
|
55
55
|
/** Click handler for groups without items */
|
|
@@ -66,6 +66,10 @@
|
|
|
66
66
|
--stuic-nav-item-text-focus: var(--stuic-color-primary-foreground);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
:root.dark {
|
|
70
|
+
--stuic-nav-item-bg-hover: rgb(255 255 255 / 0.08);
|
|
71
|
+
}
|
|
72
|
+
|
|
69
73
|
@layer components {
|
|
70
74
|
/* =============================================================================
|
|
71
75
|
BASE CONTAINER
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Tree
|
|
2
|
+
|
|
3
|
+
A generic hierarchical tree view with drag-and-drop reordering, keyboard navigation, expand/collapse state, optional `localStorage` persistence, and full ARIA `treeview` semantics.
|
|
4
|
+
|
|
5
|
+
Backed by [`@marianmeres/tree`](https://www.npmjs.com/package/@marianmeres/tree) for the data model — pass `tree.toJSON().children` or any compatible `TreeNodeDTO[]`.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
### Basic
|
|
10
|
+
|
|
11
|
+
```svelte
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import { Tree } from "@marianmeres/stuic";
|
|
14
|
+
|
|
15
|
+
const items = [
|
|
16
|
+
{
|
|
17
|
+
id: "root",
|
|
18
|
+
value: "Projects",
|
|
19
|
+
children: [
|
|
20
|
+
{ id: "a", value: "Alpha", children: [] },
|
|
21
|
+
{ id: "b", value: "Beta", children: [] },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<Tree {items} activeId="a" onSelect={(item) => console.log(item.id)} />
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Drag & Drop
|
|
31
|
+
|
|
32
|
+
```svelte
|
|
33
|
+
<Tree
|
|
34
|
+
{items}
|
|
35
|
+
draggable
|
|
36
|
+
onMove={({ source, target, position }) => {
|
|
37
|
+
// Mutate your data to move `source` relative to `target`.
|
|
38
|
+
// Return `false` (or throw) to reject the move.
|
|
39
|
+
moveNode(source.id, target.id, position);
|
|
40
|
+
}}
|
|
41
|
+
isDraggable={(item) => !item.data?.locked}
|
|
42
|
+
isDropTarget={(item) => item.data?.acceptsChildren !== false}
|
|
43
|
+
/>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`onMove` receives `{ source, target, position: "before" | "after" | "inside" }`. The component does **not** mutate items itself — you own the data. Return `false` or throw to reject.
|
|
47
|
+
|
|
48
|
+
### Expansion Control
|
|
49
|
+
|
|
50
|
+
Four priority tiers resolve a node's initial expansion state, highest wins:
|
|
51
|
+
|
|
52
|
+
1. `localStorage` value (when `persistState={true}`)
|
|
53
|
+
2. `expandedIds` prop (explicit initial set)
|
|
54
|
+
3. Auto-expanded if any descendant is active
|
|
55
|
+
4. `defaultExpanded` prop
|
|
56
|
+
|
|
57
|
+
```svelte
|
|
58
|
+
<Tree
|
|
59
|
+
{items}
|
|
60
|
+
defaultExpanded
|
|
61
|
+
persistState
|
|
62
|
+
storageKeyPrefix="my-app-tree"
|
|
63
|
+
onToggle={(item, expanded) => console.log(item.id, expanded)}
|
|
64
|
+
/>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Collapsing a branch also collapses all of its descendants.
|
|
68
|
+
|
|
69
|
+
### Custom Rendering
|
|
70
|
+
|
|
71
|
+
```svelte
|
|
72
|
+
<Tree {items}>
|
|
73
|
+
{#snippet renderIcon(item, depth, isExpanded)}
|
|
74
|
+
<MyIcon type={item.data.kind} />
|
|
75
|
+
{/snippet}
|
|
76
|
+
{#snippet renderItem(item, depth, isExpanded)}
|
|
77
|
+
<span class="font-medium">{item.value}</span>
|
|
78
|
+
{#if item.data?.count}
|
|
79
|
+
<span class="ml-auto text-xs opacity-60">{item.data.count}</span>
|
|
80
|
+
{/if}
|
|
81
|
+
{/snippet}
|
|
82
|
+
</Tree>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Props
|
|
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 |
|
|
118
|
+
|
|
119
|
+
## Keyboard Navigation
|
|
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` |
|
|
130
|
+
|
|
131
|
+
Focus follows the **roving tabindex** pattern: only the currently-focused node has `tabindex=0`, all others are `-1`.
|
|
132
|
+
|
|
133
|
+
## Accessibility
|
|
134
|
+
|
|
135
|
+
- Root has `role="tree"`; each node has `role="treeitem"`.
|
|
136
|
+
- Branches have `aria-expanded`; active nodes have `aria-selected`; all nodes have `aria-level`.
|
|
137
|
+
- Children wrappers have `role="group"`.
|
|
138
|
+
- Successful `onMove` calls announce the change via a visually-hidden `aria-live="polite"` region. Translate via the `t` prop — keys: `move_before`, `move_after`, `move_inside` with `{source}` and `{target}` placeholders.
|
|
139
|
+
|
|
140
|
+
### Drag-drop limitations
|
|
141
|
+
|
|
142
|
+
HTML5 drag-and-drop is **mouse-only** and not keyboard accessible. Touch support is device-dependent and unreliable. If keyboard/touch reordering matters for your app, layer your own UI on top (e.g. a context menu with "Move up / Move down / Move into…" actions that call your move logic directly).
|
|
143
|
+
|
|
144
|
+
## CSS Variables
|
|
145
|
+
|
|
146
|
+
Override globally in `:root` or locally via `style=""`. Radius / border-width / transition use the standard STUIC fallback pattern and inherit from their shared structural tokens unless overridden.
|
|
147
|
+
|
|
148
|
+
### Structure
|
|
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 |
|
|
163
|
+
|
|
164
|
+
### Colors
|
|
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 |
|
|
174
|
+
|
|
175
|
+
### Drag & drop
|
|
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 |
|
|
183
|
+
|
|
184
|
+
## Limitations
|
|
185
|
+
|
|
186
|
+
- **No multi-select / checkbox selection.** Single active node only.
|
|
187
|
+
- **No lazy loading.** All nodes must be present in `items`.
|
|
188
|
+
- **Expansion state is private.** Observe via `onToggle`; set initial via `expandedIds`. There is currently no way to read the full set of expanded IDs from outside.
|
|
189
|
+
- **Drag-drop is mouse-only.** See the a11y section above.
|