@sveltier/ui 0.0.0-stage → 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +82 -2
- package/dist/components/Alert/Alert.svelte +88 -0
- package/dist/components/Alert/Alert.svelte.d.ts +4 -0
- package/dist/components/Alert/alert.css +78 -0
- package/dist/components/Alert/index.d.ts +2 -0
- package/dist/components/Alert/index.js +1 -0
- package/dist/components/Alert/types.d.ts +67 -0
- package/dist/components/Alert/types.js +1 -0
- package/dist/components/Avatar/Avatar.svelte +47 -0
- package/dist/components/Avatar/Avatar.svelte.d.ts +4 -0
- package/dist/components/Avatar/avatar.css +34 -0
- package/dist/components/Avatar/index.d.ts +2 -0
- package/dist/components/Avatar/index.js +1 -0
- package/dist/components/Avatar/types.d.ts +47 -0
- package/dist/components/Avatar/types.js +1 -0
- package/dist/components/Badge/Badge.svelte +53 -0
- package/dist/components/Badge/Badge.svelte.d.ts +4 -0
- package/dist/components/Badge/badge.css +57 -0
- package/dist/components/Badge/index.d.ts +2 -0
- package/dist/components/Badge/index.js +1 -0
- package/dist/components/Badge/types.d.ts +43 -0
- package/dist/components/Badge/types.js +1 -0
- package/dist/components/Button/Button.svelte +85 -0
- package/dist/components/Button/Button.svelte.d.ts +4 -0
- package/dist/components/Button/button.css +132 -0
- package/dist/components/Button/index.d.ts +2 -0
- package/dist/components/Button/index.js +1 -0
- package/dist/components/Button/types.d.ts +76 -0
- package/dist/components/Button/types.js +1 -0
- package/dist/components/ButtonGroup/ButtonGroup.svelte +61 -0
- package/dist/components/ButtonGroup/ButtonGroup.svelte.d.ts +4 -0
- package/dist/components/ButtonGroup/button-group.css +39 -0
- package/dist/components/ButtonGroup/index.d.ts +2 -0
- package/dist/components/ButtonGroup/index.js +1 -0
- package/dist/components/ButtonGroup/types.d.ts +48 -0
- package/dist/components/ButtonGroup/types.js +1 -0
- package/dist/components/Card/Card.svelte +53 -0
- package/dist/components/Card/Card.svelte.d.ts +4 -0
- package/dist/components/Card/card.css +62 -0
- package/dist/components/Card/index.d.ts +2 -0
- package/dist/components/Card/index.js +1 -0
- package/dist/components/Card/types.d.ts +56 -0
- package/dist/components/Card/types.js +1 -0
- package/dist/components/Checkbox/Checkbox.svelte +105 -0
- package/dist/components/Checkbox/Checkbox.svelte.d.ts +4 -0
- package/dist/components/Checkbox/checkbox.css +138 -0
- package/dist/components/Checkbox/index.d.ts +2 -0
- package/dist/components/Checkbox/index.js +1 -0
- package/dist/components/Checkbox/types.d.ts +76 -0
- package/dist/components/Checkbox/types.js +1 -0
- package/dist/components/Chip/Chip.svelte +109 -0
- package/dist/components/Chip/Chip.svelte.d.ts +4 -0
- package/dist/components/Chip/chip.css +101 -0
- package/dist/components/Chip/index.d.ts +2 -0
- package/dist/components/Chip/index.js +1 -0
- package/dist/components/Chip/types.d.ts +69 -0
- package/dist/components/Chip/types.js +1 -0
- package/dist/components/Choice/Choice.svelte +60 -0
- package/dist/components/Choice/Choice.svelte.d.ts +4 -0
- package/dist/components/Choice/choice.css +75 -0
- package/dist/components/Choice/types.d.ts +43 -0
- package/dist/components/Choice/types.js +1 -0
- package/dist/components/Divider/Divider.svelte +19 -0
- package/dist/components/Divider/Divider.svelte.d.ts +4 -0
- package/dist/components/Divider/divider.css +23 -0
- package/dist/components/Divider/index.d.ts +2 -0
- package/dist/components/Divider/index.js +1 -0
- package/dist/components/Divider/types.d.ts +10 -0
- package/dist/components/Divider/types.js +1 -0
- package/dist/components/Field/Field.svelte +95 -0
- package/dist/components/Field/Field.svelte.d.ts +4 -0
- package/dist/components/Field/field.css +210 -0
- package/dist/components/Field/index.d.ts +3 -0
- package/dist/components/Field/index.js +2 -0
- package/dist/components/Field/types.d.ts +109 -0
- package/dist/components/Field/types.js +2 -0
- package/dist/components/Form/Form.svelte +93 -0
- package/dist/components/Form/Form.svelte.d.ts +8 -0
- package/dist/components/Form/form.css +16 -0
- package/dist/components/Form/index.d.ts +2 -0
- package/dist/components/Form/index.js +1 -0
- package/dist/components/Form/types.d.ts +38 -0
- package/dist/components/Form/types.js +1 -0
- package/dist/components/Icon/Icon.svelte +38 -0
- package/dist/components/Icon/Icon.svelte.d.ts +4 -0
- package/dist/components/Icon/icon.css +33 -0
- package/dist/components/Icon/index.d.ts +2 -0
- package/dist/components/Icon/index.js +1 -0
- package/dist/components/Icon/types.d.ts +34 -0
- package/dist/components/Icon/types.js +1 -0
- package/dist/components/List/List.svelte +39 -0
- package/dist/components/List/List.svelte.d.ts +4 -0
- package/dist/components/List/ListItem.svelte +57 -0
- package/dist/components/List/ListItem.svelte.d.ts +4 -0
- package/dist/components/List/index.d.ts +3 -0
- package/dist/components/List/index.js +2 -0
- package/dist/components/List/list.css +127 -0
- package/dist/components/List/types.d.ts +67 -0
- package/dist/components/List/types.js +1 -0
- package/dist/components/NativeSelect/NativeSelect.svelte +110 -0
- package/dist/components/NativeSelect/NativeSelect.svelte.d.ts +25 -0
- package/dist/components/NativeSelect/index.d.ts +2 -0
- package/dist/components/NativeSelect/index.js +1 -0
- package/dist/components/NativeSelect/native-select.css +45 -0
- package/dist/components/NativeSelect/types.d.ts +58 -0
- package/dist/components/NativeSelect/types.js +1 -0
- package/dist/components/NumberField/NumberField.svelte +246 -0
- package/dist/components/NumberField/NumberField.svelte.d.ts +4 -0
- package/dist/components/NumberField/index.d.ts +2 -0
- package/dist/components/NumberField/index.js +1 -0
- package/dist/components/NumberField/number-field.css +74 -0
- package/dist/components/NumberField/types.d.ts +73 -0
- package/dist/components/NumberField/types.js +1 -0
- package/dist/components/Progress/ProgressCircular.svelte +61 -0
- package/dist/components/Progress/ProgressCircular.svelte.d.ts +4 -0
- package/dist/components/Progress/ProgressLinear.svelte +44 -0
- package/dist/components/Progress/ProgressLinear.svelte.d.ts +4 -0
- package/dist/components/Progress/index.d.ts +3 -0
- package/dist/components/Progress/index.js +2 -0
- package/dist/components/Progress/progress-circular.css +61 -0
- package/dist/components/Progress/progress-linear.css +67 -0
- package/dist/components/Progress/types.d.ts +53 -0
- package/dist/components/Progress/types.js +1 -0
- package/dist/components/RadioGroup/RadioGroup.svelte +149 -0
- package/dist/components/RadioGroup/RadioGroup.svelte.d.ts +25 -0
- package/dist/components/RadioGroup/index.d.ts +2 -0
- package/dist/components/RadioGroup/index.js +1 -0
- package/dist/components/RadioGroup/radio-group.css +139 -0
- package/dist/components/RadioGroup/types.d.ts +93 -0
- package/dist/components/RadioGroup/types.js +1 -0
- package/dist/components/SegmentedControl/SegmentedControl.svelte +70 -0
- package/dist/components/SegmentedControl/SegmentedControl.svelte.d.ts +25 -0
- package/dist/components/SegmentedControl/index.d.ts +2 -0
- package/dist/components/SegmentedControl/index.js +1 -0
- package/dist/components/SegmentedControl/segmented-control.css +68 -0
- package/dist/components/SegmentedControl/types.d.ts +60 -0
- package/dist/components/SegmentedControl/types.js +1 -0
- package/dist/components/Skeleton/Skeleton.svelte +33 -0
- package/dist/components/Skeleton/Skeleton.svelte.d.ts +4 -0
- package/dist/components/Skeleton/index.d.ts +2 -0
- package/dist/components/Skeleton/index.js +1 -0
- package/dist/components/Skeleton/skeleton.css +70 -0
- package/dist/components/Skeleton/types.d.ts +25 -0
- package/dist/components/Skeleton/types.js +1 -0
- package/dist/components/Slider/Slider.svelte +153 -0
- package/dist/components/Slider/Slider.svelte.d.ts +4 -0
- package/dist/components/Slider/index.d.ts +2 -0
- package/dist/components/Slider/index.js +1 -0
- package/dist/components/Slider/slider.css +206 -0
- package/dist/components/Slider/types.d.ts +102 -0
- package/dist/components/Slider/types.js +1 -0
- package/dist/components/Switch/Switch.svelte +94 -0
- package/dist/components/Switch/Switch.svelte.d.ts +4 -0
- package/dist/components/Switch/index.d.ts +2 -0
- package/dist/components/Switch/index.js +1 -0
- package/dist/components/Switch/switch.css +118 -0
- package/dist/components/Switch/types.d.ts +67 -0
- package/dist/components/Switch/types.js +1 -0
- package/dist/components/Tabs/Tabs.svelte +93 -0
- package/dist/components/Tabs/Tabs.svelte.d.ts +25 -0
- package/dist/components/Tabs/index.d.ts +2 -0
- package/dist/components/Tabs/index.js +1 -0
- package/dist/components/Tabs/tabs.css +73 -0
- package/dist/components/Tabs/types.d.ts +59 -0
- package/dist/components/Tabs/types.js +1 -0
- package/dist/components/TextField/TextField.svelte +115 -0
- package/dist/components/TextField/TextField.svelte.d.ts +4 -0
- package/dist/components/TextField/index.d.ts +2 -0
- package/dist/components/TextField/index.js +1 -0
- package/dist/components/TextField/types.d.ts +57 -0
- package/dist/components/TextField/types.js +1 -0
- package/dist/components/Textarea/Textarea.svelte +116 -0
- package/dist/components/Textarea/Textarea.svelte.d.ts +4 -0
- package/dist/components/Textarea/index.d.ts +2 -0
- package/dist/components/Textarea/index.js +1 -0
- package/dist/components/Textarea/textarea.css +35 -0
- package/dist/components/Textarea/types.d.ts +60 -0
- package/dist/components/Textarea/types.js +1 -0
- package/dist/core/defaults/DefaultsProvider.svelte +30 -0
- package/dist/core/defaults/DefaultsProvider.svelte.d.ts +13 -0
- package/dist/core/defaults/context.d.ts +45 -0
- package/dist/core/defaults/context.js +38 -0
- package/dist/core/display/display.d.ts +39 -0
- package/dist/core/display/display.js +53 -0
- package/dist/core/errors/codes.d.ts +112 -0
- package/dist/core/errors/codes.js +96 -0
- package/dist/core/errors/errors.d.ts +8 -0
- package/dist/core/errors/errors.js +7 -0
- package/dist/core/form/constraints.d.ts +15 -0
- package/dist/core/form/constraints.js +42 -0
- package/dist/core/form/context.d.ts +19 -0
- package/dist/core/form/context.js +4 -0
- package/dist/core/form/field.svelte.d.ts +34 -0
- package/dist/core/form/field.svelte.js +66 -0
- package/dist/core/form/number.d.ts +24 -0
- package/dist/core/form/number.js +71 -0
- package/dist/core/form/types.d.ts +22 -0
- package/dist/core/form/types.js +1 -0
- package/dist/core/locale/locale.d.ts +55 -0
- package/dist/core/locale/locale.js +80 -0
- package/dist/core/props/classes.d.ts +21 -0
- package/dist/core/props/classes.js +44 -0
- package/dist/core/props/element.d.ts +18 -0
- package/dist/core/props/element.js +1 -0
- package/dist/core/props/types.d.ts +47 -0
- package/dist/core/props/types.js +16 -0
- package/dist/core/provider/UIProvider.svelte +60 -0
- package/dist/core/provider/UIProvider.svelte.d.ts +29 -0
- package/dist/core/theme/ThemeProvider.svelte +178 -0
- package/dist/core/theme/ThemeProvider.svelte.d.ts +25 -0
- package/dist/core/theme/color.d.ts +23 -0
- package/dist/core/theme/color.js +54 -0
- package/dist/core/theme/context.d.ts +25 -0
- package/dist/core/theme/context.js +14 -0
- package/dist/core/theme/css.d.ts +25 -0
- package/dist/core/theme/css.js +69 -0
- package/dist/core/theme/define.d.ts +11 -0
- package/dist/core/theme/define.js +21 -0
- package/dist/core/theme/resolve.d.ts +6 -0
- package/dist/core/theme/resolve.js +18 -0
- package/dist/core/theme/theme-provider.css +13 -0
- package/dist/core/theme/types.d.ts +35 -0
- package/dist/core/theme/types.js +61 -0
- package/dist/dev/off.d.ts +4 -0
- package/dist/dev/off.js +3 -0
- package/dist/dev/on.d.ts +8 -0
- package/dist/dev/on.js +33 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +72 -0
- package/dist/primitives/anchored/anchored.d.ts +39 -0
- package/dist/primitives/anchored/anchored.js +104 -0
- package/dist/primitives/anchored/fallback.d.ts +31 -0
- package/dist/primitives/anchored/fallback.js +73 -0
- package/dist/primitives/focus/focus-trap.d.ts +25 -0
- package/dist/primitives/focus/focus-trap.js +86 -0
- package/dist/primitives/focus/roving-focus.d.ts +22 -0
- package/dist/primitives/focus/roving-focus.js +86 -0
- package/dist/primitives/focus/tabbable.d.ts +9 -0
- package/dist/primitives/focus/tabbable.js +75 -0
- package/dist/primitives/inert/inert-others.d.ts +12 -0
- package/dist/primitives/inert/inert-others.js +44 -0
- package/dist/primitives/keyboard/keymap.d.ts +17 -0
- package/dist/primitives/keyboard/keymap.js +39 -0
- package/dist/primitives/keyboard/typeahead.d.ts +18 -0
- package/dist/primitives/keyboard/typeahead.js +50 -0
- package/dist/primitives/layer/dismissable.d.ts +26 -0
- package/dist/primitives/layer/dismissable.js +84 -0
- package/dist/primitives/motion/easing.d.ts +9 -0
- package/dist/primitives/motion/easing.js +46 -0
- package/dist/primitives/motion/motion.d.ts +59 -0
- package/dist/primitives/motion/motion.js +80 -0
- package/dist/primitives/portal/Portal.svelte +37 -0
- package/dist/primitives/portal/Portal.svelte.d.ts +9 -0
- package/dist/primitives/portal/PortalContent.svelte +15 -0
- package/dist/primitives/portal/PortalContent.svelte.d.ts +9 -0
- package/dist/primitives/portal/portal.css +8 -0
- package/dist/primitives/scroll-lock/scroll-lock.d.ts +10 -0
- package/dist/primitives/scroll-lock/scroll-lock.js +33 -0
- package/dist/primitives/virtual/virtual-list.svelte.d.ts +65 -0
- package/dist/primitives/virtual/virtual-list.svelte.js +291 -0
- package/dist/styles/base.css +44 -0
- package/dist/styles/props.css +137 -0
- package/dist/styles/tokens.css +84 -0
- package/dist/styles/visually-hidden.css +16 -0
- package/dist/themes/aurora.d.ts +13 -0
- package/dist/themes/aurora.js +103 -0
- package/dist/themes/heritage.d.ts +13 -0
- package/dist/themes/heritage.js +102 -0
- package/dist/themes/maison.d.ts +13 -0
- package/dist/themes/maison.js +99 -0
- package/dist/themes/milano.d.ts +13 -0
- package/dist/themes/milano.js +101 -0
- package/dist/themes/monolith.d.ts +13 -0
- package/dist/themes/monolith.js +101 -0
- package/package.json +87 -5
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
@layer sv.tokens, sv.base, sv.components, sv.utilities;
|
|
2
|
+
|
|
3
|
+
@layer sv.components {
|
|
4
|
+
/*
|
|
5
|
+
* A control beside its label, with a hint or an error under them. The row is the label, so a
|
|
6
|
+
* press anywhere on it toggles the control. It is at least 24px tall (WCAG 2.5.8) and grows
|
|
7
|
+
* with the size and density. The control sets --sv-choice-control to its width, so the
|
|
8
|
+
* messages line up with the label text.
|
|
9
|
+
*/
|
|
10
|
+
.sv-choice {
|
|
11
|
+
--sv-choice-gap: 0.618em;
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
align-items: flex-start;
|
|
15
|
+
min-inline-size: 0;
|
|
16
|
+
color: var(--sv-color-text);
|
|
17
|
+
font-family: var(--sv-font-ui);
|
|
18
|
+
font-size: var(--sv-size-text);
|
|
19
|
+
line-height: var(--sv-leading-snug);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.sv-choice__row {
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
gap: var(--sv-choice-gap);
|
|
25
|
+
align-items: flex-start;
|
|
26
|
+
max-inline-size: 100%;
|
|
27
|
+
padding-block: max(
|
|
28
|
+
0px,
|
|
29
|
+
(max(1.5rem, var(--sv-size-height) * var(--sv-density) * 0.618) - 1lh) / 2
|
|
30
|
+
);
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.sv-choice__label {
|
|
35
|
+
min-inline-size: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.sv-choice__required {
|
|
39
|
+
margin-inline-start: 0.236em;
|
|
40
|
+
color: var(--sv-color-error);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.sv-choice__messages {
|
|
44
|
+
padding-inline-start: calc(var(--sv-choice-control) + var(--sv-choice-gap));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sv-choice__hint,
|
|
48
|
+
.sv-choice__error {
|
|
49
|
+
margin: 0;
|
|
50
|
+
font-size: var(--sv-text-xs);
|
|
51
|
+
line-height: var(--sv-leading-snug);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.sv-choice__hint {
|
|
55
|
+
color: var(--sv-color-text-muted);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.sv-choice__error {
|
|
59
|
+
color: var(--sv-color-error);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.sv-choice--invalid {
|
|
63
|
+
--sv-color: var(--sv-color-error);
|
|
64
|
+
--sv-on-color: var(--sv-on-error);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
* The control and its label are muted at φ⁻², as a disabled Button. The hint stays legible:
|
|
69
|
+
* it often says why the control is off.
|
|
70
|
+
*/
|
|
71
|
+
.sv-choice--disabled > .sv-choice__row {
|
|
72
|
+
opacity: 0.382;
|
|
73
|
+
cursor: not-allowed;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { FieldError } from '../../core/form/types.js';
|
|
4
|
+
import type { ElementProps } from '../../core/props/element.js';
|
|
5
|
+
/** The props of a choice's label, help and error, shared by Checkbox and Switch. */
|
|
6
|
+
export interface ChoiceContentProps {
|
|
7
|
+
/**
|
|
8
|
+
* The label beside the control: text, or a snippet when it holds a link. A press anywhere on
|
|
9
|
+
* it toggles the control. Without it, name the control with `aria-label`.
|
|
10
|
+
*/
|
|
11
|
+
label?: string | Snippet;
|
|
12
|
+
/** Help under the label. An error replaces it. */
|
|
13
|
+
hint?: string;
|
|
14
|
+
/**
|
|
15
|
+
* An error from outside the control, such as the server's answer: a message, a list of which
|
|
16
|
+
* the first is shown, or `true` for an invalid state described elsewhere.
|
|
17
|
+
*/
|
|
18
|
+
error?: FieldError;
|
|
19
|
+
}
|
|
20
|
+
/** What a Choice passes to the control it lays out. Spread it onto the `<input>`. */
|
|
21
|
+
export interface ChoiceControlAttributes {
|
|
22
|
+
/** The id the hint and the error are named after. */
|
|
23
|
+
id: string;
|
|
24
|
+
/** The hint or the error, whichever is shown. */
|
|
25
|
+
'aria-describedby': string | undefined;
|
|
26
|
+
/** Set while the choice shows an error. */
|
|
27
|
+
'aria-invalid': true | undefined;
|
|
28
|
+
required: boolean;
|
|
29
|
+
disabled: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** A control beside its label, with a hint or an error under them. Internal. */
|
|
32
|
+
export type ChoiceProps = ElementProps<HTMLDivElement, HTMLAttributes<HTMLDivElement>, Omit<ChoiceContentProps, 'error'> & {
|
|
33
|
+
/** The id of the control. Generated when not given. */
|
|
34
|
+
id?: string | null;
|
|
35
|
+
/** What the choice shows: a message, `true` for an invalid state without one, or `false`. */
|
|
36
|
+
error?: string | boolean;
|
|
37
|
+
/** Adds an asterisk to the label. */
|
|
38
|
+
required?: boolean;
|
|
39
|
+
/** Mutes the choice. The control carries `disabled` itself. */
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
/** The control, drawn at the start of the label. */
|
|
42
|
+
children: Snippet<[ChoiceControlAttributes]>;
|
|
43
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// A hairline separator. See DividerProps for the props.
|
|
3
|
+
import './divider.css';
|
|
4
|
+
import type { DividerProps } from './types.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
vertical = false,
|
|
8
|
+
class: className,
|
|
9
|
+
ref = $bindable(null),
|
|
10
|
+
...rest
|
|
11
|
+
}: DividerProps = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<hr
|
|
15
|
+
bind:this={ref}
|
|
16
|
+
{...rest}
|
|
17
|
+
aria-orientation={vertical ? 'vertical' : undefined}
|
|
18
|
+
class={['sv-divider', vertical && 'sv-divider--vertical', className]}
|
|
19
|
+
/>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@layer sv.tokens, sv.base, sv.components, sv.utilities;
|
|
2
|
+
|
|
3
|
+
@layer sv.components {
|
|
4
|
+
.sv-divider {
|
|
5
|
+
flex: none;
|
|
6
|
+
align-self: stretch;
|
|
7
|
+
block-size: 0;
|
|
8
|
+
margin: 0;
|
|
9
|
+
border: 0;
|
|
10
|
+
border-block-start: var(--sv-hairline) solid var(--sv-color-border);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Stretches in a flex row; standing alone in text, it is one line tall. */
|
|
14
|
+
.sv-divider--vertical {
|
|
15
|
+
display: inline-block;
|
|
16
|
+
inline-size: 0;
|
|
17
|
+
block-size: auto;
|
|
18
|
+
min-block-size: 1em;
|
|
19
|
+
border-block-start: 0;
|
|
20
|
+
border-inline-start: var(--sv-hairline) solid var(--sv-color-border);
|
|
21
|
+
vertical-align: middle;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Divider } from './Divider.svelte';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import type { ElementProps } from '../../core/props/element.js';
|
|
3
|
+
/** A hairline between groups of content: an `<hr>`, which assistive technology reads as a separator. */
|
|
4
|
+
export type DividerProps = ElementProps<HTMLHRElement, HTMLAttributes<HTMLHRElement>, {
|
|
5
|
+
/**
|
|
6
|
+
* Runs top to bottom, between items in a row, and stretches to the row's height.
|
|
7
|
+
* @default false
|
|
8
|
+
*/
|
|
9
|
+
vertical?: boolean;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// A label, a box for a control, and its hint or error. See FieldProps for the props.
|
|
3
|
+
import { useDefaults } from '../../core/defaults/context.js';
|
|
4
|
+
import { readError } from '../../core/form/field.svelte.js';
|
|
5
|
+
import { sharedClasses } from '../../core/props/classes.js';
|
|
6
|
+
import './field.css';
|
|
7
|
+
import { FIELD_VARIANTS, type FieldDefaults, type FieldProps } from './types.js';
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
variant,
|
|
11
|
+
color,
|
|
12
|
+
size,
|
|
13
|
+
density,
|
|
14
|
+
rounded,
|
|
15
|
+
disabled,
|
|
16
|
+
id: idProp,
|
|
17
|
+
label,
|
|
18
|
+
hint,
|
|
19
|
+
error,
|
|
20
|
+
required = false,
|
|
21
|
+
readonly = false,
|
|
22
|
+
prepend,
|
|
23
|
+
append,
|
|
24
|
+
children,
|
|
25
|
+
class: className,
|
|
26
|
+
ref = $bindable(null),
|
|
27
|
+
...rest
|
|
28
|
+
}: FieldProps = $props();
|
|
29
|
+
|
|
30
|
+
const uid = $props.id();
|
|
31
|
+
const id = $derived(idProp ?? uid);
|
|
32
|
+
const resolve = useDefaults<FieldDefaults>('Field', {
|
|
33
|
+
variant: 'outlined',
|
|
34
|
+
color: 'primary',
|
|
35
|
+
size: 'md',
|
|
36
|
+
density: 'default',
|
|
37
|
+
rounded: true,
|
|
38
|
+
disabled: false
|
|
39
|
+
});
|
|
40
|
+
const p = $derived(resolve({ variant, color, size, density, rounded, disabled }));
|
|
41
|
+
const shown = $derived(readError(error));
|
|
42
|
+
const message = $derived(typeof shown === 'string' ? shown : undefined);
|
|
43
|
+
|
|
44
|
+
// A press anywhere in the box focuses the control, as a press on the label does, except on
|
|
45
|
+
// something that takes focus itself (the control, a button in `append`).
|
|
46
|
+
const FOCUSABLE = 'input, textarea, select, button, a[href], [tabindex]';
|
|
47
|
+
|
|
48
|
+
function press(event: PointerEvent) {
|
|
49
|
+
const box = event.currentTarget as HTMLElement;
|
|
50
|
+
const hit = (event.target as Element).closest(FOCUSABLE);
|
|
51
|
+
const control = box.querySelector<HTMLElement>('.sv-field__control');
|
|
52
|
+
if ((hit && box.contains(hit)) || !control || p.disabled) return;
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
control.focus();
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<div
|
|
59
|
+
bind:this={ref}
|
|
60
|
+
{...rest}
|
|
61
|
+
class={[
|
|
62
|
+
'sv-field',
|
|
63
|
+
sharedClasses(p, 'Field', FIELD_VARIANTS),
|
|
64
|
+
shown !== false && 'sv-field--invalid',
|
|
65
|
+
p.disabled && 'sv-field--disabled',
|
|
66
|
+
readonly && 'sv-field--readonly',
|
|
67
|
+
className
|
|
68
|
+
]}
|
|
69
|
+
>
|
|
70
|
+
{#if label}
|
|
71
|
+
<label class="sv-field__label" for={id} id="{id}-label"
|
|
72
|
+
>{label}{#if required}<span class="sv-field__required" aria-hidden="true">*</span>{/if}</label
|
|
73
|
+
>
|
|
74
|
+
{/if}
|
|
75
|
+
<!-- The label and the control are the accessible targets; this only widens the pointer target. -->
|
|
76
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
77
|
+
<div class="sv-field__box" onpointerdown={press}>
|
|
78
|
+
{#if prepend}<span class="sv-field__prepend">{@render prepend()}</span>{/if}
|
|
79
|
+
{@render children({
|
|
80
|
+
id,
|
|
81
|
+
class: 'sv-field__control',
|
|
82
|
+
'aria-describedby': message ? `${id}-error` : hint ? `${id}-hint` : undefined,
|
|
83
|
+
'aria-invalid': shown !== false || undefined,
|
|
84
|
+
required,
|
|
85
|
+
disabled: p.disabled,
|
|
86
|
+
readonly
|
|
87
|
+
})}
|
|
88
|
+
{#if append}<span class="sv-field__append">{@render append()}</span>{/if}
|
|
89
|
+
</div>
|
|
90
|
+
{#if hint && !message}<p class="sv-field__hint" id="{id}-hint">{hint}</p>{/if}
|
|
91
|
+
<!-- Announces an error that appears after the user has moved on. -->
|
|
92
|
+
<div class="sv-field__alert" aria-live="polite">
|
|
93
|
+
{#if message}<p class="sv-field__error" id="{id}-error">{message}</p>{/if}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
@layer sv.tokens, sv.base, sv.components, sv.utilities;
|
|
2
|
+
|
|
3
|
+
@layer sv.components {
|
|
4
|
+
/*
|
|
5
|
+
* A label above a box above a hint or an error. The box is as tall as a Button of the same
|
|
6
|
+
* size and density. Its border is --sv-field-border; the accent of a focused or invalid
|
|
7
|
+
* field is a layer in --sv-color whose opacity changes.
|
|
8
|
+
*/
|
|
9
|
+
.sv-field {
|
|
10
|
+
--sv-field-border: var(--sv-color-border-strong);
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
min-inline-size: 0;
|
|
14
|
+
font-family: var(--sv-font-ui);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* The theme's label voice: Heritage and Maison set it in tracked capitals. */
|
|
18
|
+
.sv-field__label {
|
|
19
|
+
align-self: flex-start;
|
|
20
|
+
margin-block-end: var(--sv-space-8);
|
|
21
|
+
color: var(--sv-color-text);
|
|
22
|
+
font-size: var(--sv-label-size);
|
|
23
|
+
font-weight: var(--sv-weight-medium);
|
|
24
|
+
line-height: var(--sv-leading-snug);
|
|
25
|
+
letter-spacing: var(--sv-label-tracking);
|
|
26
|
+
text-transform: var(--sv-label-transform);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.sv-field__required {
|
|
30
|
+
margin-inline-start: 0.236em;
|
|
31
|
+
color: var(--sv-color-error);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.sv-field__box {
|
|
35
|
+
position: relative;
|
|
36
|
+
isolation: isolate;
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
gap: 0.618em;
|
|
40
|
+
box-sizing: border-box;
|
|
41
|
+
min-block-size: calc(var(--sv-size-height) * var(--sv-density));
|
|
42
|
+
padding-inline: calc(var(--sv-size-height) * var(--sv-density) * 0.382);
|
|
43
|
+
border: var(--sv-hairline) solid var(--sv-field-border);
|
|
44
|
+
/* A control that grows to several lines sets --sv-field-radius, so it never turns oval. */
|
|
45
|
+
border-radius: var(--sv-rounded, var(--sv-field-radius, var(--sv-radius-md)));
|
|
46
|
+
color: var(--sv-color-text);
|
|
47
|
+
font-size: var(--sv-size-text);
|
|
48
|
+
line-height: var(--sv-leading-snug);
|
|
49
|
+
cursor: text;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sv-field__box::after {
|
|
53
|
+
content: '';
|
|
54
|
+
position: absolute;
|
|
55
|
+
inset: calc(var(--sv-hairline) * -1);
|
|
56
|
+
border: var(--sv-focus-width) solid var(--sv-color);
|
|
57
|
+
border-radius: inherit;
|
|
58
|
+
opacity: 0;
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
transition: opacity var(--sv-duration-fast) var(--sv-ease);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.sv-field__box:focus-within::after {
|
|
64
|
+
opacity: 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@media (hover: hover) {
|
|
68
|
+
.sv-field:where(:not(.sv-field--disabled, .sv-field--readonly)) .sv-field__box:hover {
|
|
69
|
+
--sv-field-border: var(--sv-color-text-muted);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.sv-field--invalid,
|
|
74
|
+
.sv-field--invalid .sv-field__box:hover {
|
|
75
|
+
--sv-color: var(--sv-color-error);
|
|
76
|
+
--sv-field-border: var(--sv-color-error);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Variants. Tonal is a wash of the text colour, not of the accent. */
|
|
80
|
+
.sv-variant-tonal > .sv-field__box {
|
|
81
|
+
border-width: 0 0 var(--sv-hairline);
|
|
82
|
+
border-end-start-radius: 0;
|
|
83
|
+
border-end-end-radius: 0;
|
|
84
|
+
background-color: color-mix(in oklch, var(--sv-color-text) 5%, transparent);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.sv-variant-text > .sv-field__box {
|
|
88
|
+
border-width: 0 0 var(--sv-hairline);
|
|
89
|
+
border-radius: 0;
|
|
90
|
+
padding-inline: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.sv-variant-tonal > .sv-field__box::after,
|
|
94
|
+
.sv-variant-text > .sv-field__box::after {
|
|
95
|
+
border-width: 0 0 var(--sv-focus-width);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.sv-variant-elevated > .sv-field__box {
|
|
99
|
+
border-color: transparent;
|
|
100
|
+
background-color: var(--sv-color-surface);
|
|
101
|
+
box-shadow: var(--sv-shadow);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.sv-field--invalid.sv-variant-elevated > .sv-field__box {
|
|
105
|
+
border-color: var(--sv-field-border);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.sv-variant-plain > .sv-field__box {
|
|
109
|
+
border-color: transparent;
|
|
110
|
+
padding-inline: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* A plain field draws no box, so its focus is the theme's ring. */
|
|
114
|
+
.sv-variant-plain > .sv-field__box::after {
|
|
115
|
+
content: none;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.sv-variant-plain > .sv-field__box:focus-within {
|
|
119
|
+
outline: var(--sv-focus-width) solid var(--sv-color);
|
|
120
|
+
outline-offset: var(--sv-focus-offset);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Muted at φ⁻², as a disabled Button. */
|
|
124
|
+
.sv-field--disabled {
|
|
125
|
+
opacity: 0.382;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.sv-field--disabled .sv-field__box {
|
|
129
|
+
cursor: not-allowed;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.sv-field--readonly .sv-field__box {
|
|
133
|
+
cursor: default;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.sv-field__prepend,
|
|
137
|
+
.sv-field__append {
|
|
138
|
+
display: inline-flex;
|
|
139
|
+
flex: none;
|
|
140
|
+
align-items: center;
|
|
141
|
+
color: var(--sv-color-text-muted);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
:where(.sv-field__prepend, .sv-field__append) > svg {
|
|
145
|
+
inline-size: 1.272em;
|
|
146
|
+
block-size: 1.272em;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Padding, not margin: an app's paragraph reset (p { margin: 0 }) would win over a layer. */
|
|
150
|
+
.sv-field__hint,
|
|
151
|
+
.sv-field__error {
|
|
152
|
+
margin: 0;
|
|
153
|
+
padding-block-start: var(--sv-space-5);
|
|
154
|
+
font-size: var(--sv-text-xs);
|
|
155
|
+
line-height: var(--sv-leading-snug);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.sv-field__hint {
|
|
159
|
+
color: var(--sv-color-text-muted);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.sv-field__error {
|
|
163
|
+
color: var(--sv-color-error);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/*
|
|
167
|
+
* The control fills the box; the box shows focus. Text is at least 16px on touch screens,
|
|
168
|
+
* where smaller text makes iOS zoom into the page on focus.
|
|
169
|
+
*/
|
|
170
|
+
.sv-field__control {
|
|
171
|
+
flex: 1;
|
|
172
|
+
align-self: stretch;
|
|
173
|
+
min-inline-size: 0;
|
|
174
|
+
box-sizing: border-box;
|
|
175
|
+
margin: 0;
|
|
176
|
+
padding: 0;
|
|
177
|
+
border: 0;
|
|
178
|
+
background: none;
|
|
179
|
+
color: inherit;
|
|
180
|
+
font: inherit;
|
|
181
|
+
letter-spacing: inherit;
|
|
182
|
+
appearance: none;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.sv-field__control:focus-visible {
|
|
186
|
+
outline: none;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.sv-field__control::placeholder {
|
|
190
|
+
color: var(--sv-color-text-subtle);
|
|
191
|
+
opacity: 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.sv-field__control:disabled {
|
|
195
|
+
cursor: not-allowed;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* Autofill keeps the field's colours: the browser's tint is delayed for good. */
|
|
199
|
+
.sv-field__control:autofill {
|
|
200
|
+
transition:
|
|
201
|
+
background-color 0s 600000s,
|
|
202
|
+
color 0s 600000s;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@media (pointer: coarse) {
|
|
206
|
+
.sv-field__control {
|
|
207
|
+
font-size: max(1rem, 1em);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { FieldError } from '../../core/form/types.js';
|
|
4
|
+
import type { ElementProps } from '../../core/props/element.js';
|
|
5
|
+
import type { Color, Density, Rounded, Size } from '../../core/props/types.js';
|
|
6
|
+
/** The variants a boxed field takes, a subset of the shared `VARIANTS`. */
|
|
7
|
+
export declare const FIELD_VARIANTS: readonly ["outlined", "tonal", "elevated", "text", "plain"];
|
|
8
|
+
/**
|
|
9
|
+
* How a field's box is drawn: `outlined` (a border), `tonal` (a wash with a rule under it),
|
|
10
|
+
* `elevated` (a surface with a shadow), `text` (only the rule) and `plain` (nothing, for a field
|
|
11
|
+
* inside another component).
|
|
12
|
+
*/
|
|
13
|
+
export type FieldVariant = (typeof FIELD_VARIANTS)[number];
|
|
14
|
+
/** The Field props that `DefaultsProvider` and `UIProvider` can set for a subtree. */
|
|
15
|
+
export interface FieldDefaults {
|
|
16
|
+
/**
|
|
17
|
+
* How the box is drawn.
|
|
18
|
+
* @default 'outlined'
|
|
19
|
+
*/
|
|
20
|
+
variant: FieldVariant;
|
|
21
|
+
/**
|
|
22
|
+
* The accent of the focused field. An invalid field uses `error`.
|
|
23
|
+
* @default 'primary'
|
|
24
|
+
*/
|
|
25
|
+
color: Color;
|
|
26
|
+
/**
|
|
27
|
+
* Height and text size, matching a Button of the same size.
|
|
28
|
+
* @default 'md'
|
|
29
|
+
*/
|
|
30
|
+
size: Size;
|
|
31
|
+
/**
|
|
32
|
+
* Multiplies the height and padding, never the text.
|
|
33
|
+
* @default 'default'
|
|
34
|
+
*/
|
|
35
|
+
density: Density;
|
|
36
|
+
/**
|
|
37
|
+
* Corner rounding. `true` keeps the theme's radius.
|
|
38
|
+
* @default true
|
|
39
|
+
*/
|
|
40
|
+
rounded: Rounded;
|
|
41
|
+
/**
|
|
42
|
+
* Mutes the field and disables its control.
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
disabled: boolean;
|
|
46
|
+
}
|
|
47
|
+
declare module '../../core/defaults/context.js' {
|
|
48
|
+
interface ComponentDefaults {
|
|
49
|
+
Field: FieldDefaults;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** What a Field passes to the control it wraps. Spread it onto the control element. */
|
|
53
|
+
export interface FieldControlAttributes {
|
|
54
|
+
/** The id the label points at. */
|
|
55
|
+
id: string;
|
|
56
|
+
/** `sv-field__control`: fills the box and takes its text style. */
|
|
57
|
+
class: string;
|
|
58
|
+
/** The hint or the error, whichever is shown. */
|
|
59
|
+
'aria-describedby': string | undefined;
|
|
60
|
+
/** Set while the field shows an error. */
|
|
61
|
+
'aria-invalid': true | undefined;
|
|
62
|
+
required: boolean;
|
|
63
|
+
disabled: boolean;
|
|
64
|
+
readonly: boolean;
|
|
65
|
+
}
|
|
66
|
+
/** The props of a field's label, help and error, shared by every boxed field. */
|
|
67
|
+
export interface FieldContentProps {
|
|
68
|
+
/** The visible label, linked to the control. Without it, name the control with `aria-label`. */
|
|
69
|
+
label?: string;
|
|
70
|
+
/** Help under the field. An error replaces it. */
|
|
71
|
+
hint?: string;
|
|
72
|
+
/**
|
|
73
|
+
* An error from outside the field, such as the server's answer: a message, a list of which
|
|
74
|
+
* the first is shown, or `true` for an invalid state described elsewhere.
|
|
75
|
+
*/
|
|
76
|
+
error?: FieldError;
|
|
77
|
+
/** Content at the start of the box, usually an icon. */
|
|
78
|
+
prepend?: Snippet;
|
|
79
|
+
/** Content at the end of the box: an icon, a unit or a button. */
|
|
80
|
+
append?: Snippet;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* A label, a box for a control, and its hint or error. Wrap your own control in it: the
|
|
84
|
+
* children snippet receives the id, ARIA links and state to spread onto the control. Other
|
|
85
|
+
* attributes go to the root element.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* <Field label="Start date" hint="The first payment goes out on this day" required>
|
|
89
|
+
* {#snippet children(attributes)}
|
|
90
|
+
* <input type="date" name="start" {...attributes} bind:value={start} />
|
|
91
|
+
* {/snippet}
|
|
92
|
+
* </Field>
|
|
93
|
+
*/
|
|
94
|
+
export type FieldProps = ElementProps<HTMLDivElement, HTMLAttributes<HTMLDivElement>, Partial<FieldDefaults> & FieldContentProps & {
|
|
95
|
+
/** The id of the control. Generated when not given. */
|
|
96
|
+
id?: string | null;
|
|
97
|
+
/**
|
|
98
|
+
* Adds an asterisk to the label. The control carries `required` itself.
|
|
99
|
+
* @default false
|
|
100
|
+
*/
|
|
101
|
+
required?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* The control can be read and focused but not changed.
|
|
104
|
+
* @default false
|
|
105
|
+
*/
|
|
106
|
+
readonly?: boolean;
|
|
107
|
+
/** The control. Spread the attributes it receives onto it. */
|
|
108
|
+
children: Snippet<[FieldControlAttributes]>;
|
|
109
|
+
}>;
|