@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,105 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// A box that is checked or not, with its label beside it and validation. See CheckboxProps.
|
|
3
|
+
import { untrack } from 'svelte';
|
|
4
|
+
import { DEV, warnUnnamed } from '#dev';
|
|
5
|
+
import { useDefaults } from '../../core/defaults/context.js';
|
|
6
|
+
import { createField } from '../../core/form/field.svelte.js';
|
|
7
|
+
import { useLocale } from '../../core/locale/locale.js';
|
|
8
|
+
import { sharedClasses } from '../../core/props/classes.js';
|
|
9
|
+
import Choice from '../Choice/Choice.svelte';
|
|
10
|
+
import './checkbox.css';
|
|
11
|
+
import type { CheckboxDefaults, CheckboxProps } from './types.js';
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
color,
|
|
15
|
+
size,
|
|
16
|
+
density,
|
|
17
|
+
disabled,
|
|
18
|
+
checked = $bindable(false),
|
|
19
|
+
indeterminate = $bindable(false),
|
|
20
|
+
label,
|
|
21
|
+
hint,
|
|
22
|
+
error,
|
|
23
|
+
rules,
|
|
24
|
+
validateOn,
|
|
25
|
+
required = false,
|
|
26
|
+
id,
|
|
27
|
+
'aria-describedby': describedBy,
|
|
28
|
+
class: className,
|
|
29
|
+
style,
|
|
30
|
+
ref = $bindable(null),
|
|
31
|
+
...rest
|
|
32
|
+
}: CheckboxProps = $props();
|
|
33
|
+
|
|
34
|
+
const resolve = useDefaults<CheckboxDefaults>('Checkbox', {
|
|
35
|
+
color: 'primary',
|
|
36
|
+
size: 'md',
|
|
37
|
+
density: 'default',
|
|
38
|
+
disabled: false
|
|
39
|
+
});
|
|
40
|
+
const p = $derived(resolve({ color, size, density, disabled }));
|
|
41
|
+
const locale = useLocale();
|
|
42
|
+
// A form reset goes back to the state the box started in, as it does for a form rendered on
|
|
43
|
+
// the server.
|
|
44
|
+
const initial = untrack(() => checked ?? false);
|
|
45
|
+
|
|
46
|
+
const field = createField<boolean>({
|
|
47
|
+
value: () => checked ?? false,
|
|
48
|
+
// Rules run on a checked box only, as they run on a filled field only.
|
|
49
|
+
empty: (on) => !on,
|
|
50
|
+
check: (on) => (!on && required ? locale.t('required') : undefined),
|
|
51
|
+
rules: () => rules,
|
|
52
|
+
error: () => error,
|
|
53
|
+
validateOn: () => validateOn,
|
|
54
|
+
control: () => ref
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (DEV) $effect(() => warnUnnamed('Checkbox', ref));
|
|
58
|
+
|
|
59
|
+
// A toggle is a whole edit, so it also counts as leaving: the message shows at once, even in
|
|
60
|
+
// Safari, where a click does not focus the checkbox.
|
|
61
|
+
function toggled() {
|
|
62
|
+
field.changed();
|
|
63
|
+
field.left();
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<Choice
|
|
68
|
+
{id}
|
|
69
|
+
{label}
|
|
70
|
+
{hint}
|
|
71
|
+
error={field.error}
|
|
72
|
+
{required}
|
|
73
|
+
disabled={p.disabled}
|
|
74
|
+
class={[
|
|
75
|
+
'sv-checkbox',
|
|
76
|
+
sharedClasses(p, 'Checkbox'),
|
|
77
|
+
indeterminate && 'sv-checkbox--indeterminate',
|
|
78
|
+
className
|
|
79
|
+
]}
|
|
80
|
+
{style}
|
|
81
|
+
onchange={toggled}
|
|
82
|
+
>
|
|
83
|
+
{#snippet children(attributes)}
|
|
84
|
+
<span class="sv-checkbox__control">
|
|
85
|
+
<input
|
|
86
|
+
bind:this={ref}
|
|
87
|
+
defaultChecked={initial}
|
|
88
|
+
{...rest}
|
|
89
|
+
{...attributes}
|
|
90
|
+
aria-describedby={[describedBy, attributes['aria-describedby']].filter(Boolean).join(' ') ||
|
|
91
|
+
undefined}
|
|
92
|
+
type="checkbox"
|
|
93
|
+
class="sv-checkbox__input"
|
|
94
|
+
bind:checked
|
|
95
|
+
bind:indeterminate
|
|
96
|
+
/>
|
|
97
|
+
<span class="sv-checkbox__box" aria-hidden="true">
|
|
98
|
+
<svg class="sv-checkbox__mark" viewBox="0 0 24 24" focusable="false">
|
|
99
|
+
<path class="sv-checkbox__check" d="M5 12.5l4.5 4.5 9.5-9.5" />
|
|
100
|
+
<path class="sv-checkbox__dash" d="M6.5 12h11" />
|
|
101
|
+
</svg>
|
|
102
|
+
</span>
|
|
103
|
+
</span>
|
|
104
|
+
{/snippet}
|
|
105
|
+
</Choice>
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
@layer sv.tokens, sv.base, sv.components, sv.utilities;
|
|
2
|
+
|
|
3
|
+
@layer sv.components {
|
|
4
|
+
/* The box is as tall as a line of the label, 1.272em with the label's √φ line height. */
|
|
5
|
+
.sv-checkbox {
|
|
6
|
+
--sv-checkbox-size: 1.272em;
|
|
7
|
+
--sv-checkbox-border: calc(var(--sv-hairline) * 1.5);
|
|
8
|
+
--sv-choice-control: var(--sv-checkbox-size);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* The native input lies over the drawn box, invisible, so focus, keys, forms and autofill stay
|
|
13
|
+
* native. The box sits on the first line of the label.
|
|
14
|
+
*/
|
|
15
|
+
.sv-checkbox__control {
|
|
16
|
+
position: relative;
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
flex: none;
|
|
19
|
+
margin-block-start: calc((1lh - var(--sv-checkbox-size)) / 2);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.sv-checkbox__input {
|
|
23
|
+
position: absolute;
|
|
24
|
+
inset: 0;
|
|
25
|
+
inline-size: 100%;
|
|
26
|
+
block-size: 100%;
|
|
27
|
+
margin: 0;
|
|
28
|
+
opacity: 0;
|
|
29
|
+
cursor: inherit;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Never rounder than a quarter of its side, so a checkbox does not pass for a radio. */
|
|
33
|
+
.sv-checkbox__box {
|
|
34
|
+
position: relative;
|
|
35
|
+
display: grid;
|
|
36
|
+
place-items: center;
|
|
37
|
+
box-sizing: border-box;
|
|
38
|
+
inline-size: var(--sv-checkbox-size);
|
|
39
|
+
block-size: var(--sv-checkbox-size);
|
|
40
|
+
border: var(--sv-checkbox-border) solid var(--sv-color-border-strong);
|
|
41
|
+
border-radius: min(var(--sv-radius-sm), var(--sv-checkbox-size) * 0.236);
|
|
42
|
+
background-color: var(--sv-color-surface);
|
|
43
|
+
color: var(--sv-on-color);
|
|
44
|
+
pointer-events: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* The fill (before) and the state layer (after) cover the border. */
|
|
48
|
+
.sv-checkbox__box::before,
|
|
49
|
+
.sv-checkbox__box::after {
|
|
50
|
+
content: '';
|
|
51
|
+
position: absolute;
|
|
52
|
+
inset: calc(var(--sv-checkbox-border) * -1);
|
|
53
|
+
border-radius: inherit;
|
|
54
|
+
opacity: 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.sv-checkbox__box::before {
|
|
58
|
+
background-color: var(--sv-color);
|
|
59
|
+
transform: scale(0.618);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.sv-checkbox__box::after {
|
|
63
|
+
background-color: var(--sv-color-text);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.sv-checkbox__mark {
|
|
67
|
+
position: relative;
|
|
68
|
+
inline-size: 100%;
|
|
69
|
+
block-size: 100%;
|
|
70
|
+
fill: none;
|
|
71
|
+
stroke: currentColor;
|
|
72
|
+
stroke-width: 3;
|
|
73
|
+
stroke-linecap: round;
|
|
74
|
+
stroke-linejoin: round;
|
|
75
|
+
opacity: 0;
|
|
76
|
+
transform: scale(0.618);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* A check scales in with its fill; nothing else moves. */
|
|
80
|
+
.sv-checkbox__box::before,
|
|
81
|
+
.sv-checkbox__mark {
|
|
82
|
+
transition:
|
|
83
|
+
opacity var(--sv-duration-fast) var(--sv-ease),
|
|
84
|
+
transform var(--sv-duration-fast) var(--sv-ease);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.sv-checkbox__input:checked + .sv-checkbox__box::before,
|
|
88
|
+
.sv-checkbox__input:checked + .sv-checkbox__box .sv-checkbox__mark,
|
|
89
|
+
.sv-checkbox--indeterminate .sv-checkbox__box::before,
|
|
90
|
+
.sv-checkbox--indeterminate .sv-checkbox__mark {
|
|
91
|
+
opacity: 1;
|
|
92
|
+
transform: none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.sv-checkbox__input:checked + .sv-checkbox__box::after,
|
|
96
|
+
.sv-checkbox--indeterminate .sv-checkbox__box::after {
|
|
97
|
+
background-color: var(--sv-on-color);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* The dash stands in for the check while the state is indeterminate. */
|
|
101
|
+
.sv-checkbox__dash,
|
|
102
|
+
.sv-checkbox--indeterminate .sv-checkbox__check {
|
|
103
|
+
display: none;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.sv-checkbox--indeterminate .sv-checkbox__dash {
|
|
107
|
+
display: inline;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/*
|
|
111
|
+
* Hover and press mix the mark's colour over the box, as they do over a flat Button. The
|
|
112
|
+
* disabled guard adds no weight, so an invalid box keeps its error border under the pointer.
|
|
113
|
+
*/
|
|
114
|
+
@media (hover: hover) {
|
|
115
|
+
:where(.sv-checkbox:not(.sv-choice--disabled)) > .sv-choice__row:hover .sv-checkbox__box {
|
|
116
|
+
border-color: var(--sv-color-text-muted);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
:where(.sv-checkbox:not(.sv-choice--disabled))
|
|
120
|
+
> .sv-choice__row:hover
|
|
121
|
+
.sv-checkbox__box::after {
|
|
122
|
+
opacity: var(--sv-state-hover);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
:where(.sv-checkbox:not(.sv-choice--disabled)) > .sv-choice__row:active .sv-checkbox__box::after {
|
|
127
|
+
opacity: var(--sv-state-press);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.sv-checkbox__input:focus-visible + .sv-checkbox__box {
|
|
131
|
+
outline: var(--sv-focus-width) solid var(--sv-color);
|
|
132
|
+
outline-offset: var(--sv-focus-offset);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.sv-choice--invalid > .sv-choice__row .sv-checkbox__box {
|
|
136
|
+
border-color: var(--sv-color-error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Checkbox } from './Checkbox.svelte';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
import type { Rule, ValidateOn } from '../../core/form/types.js';
|
|
3
|
+
import type { ElementProps } from '../../core/props/element.js';
|
|
4
|
+
import type { Color, Density, Size } from '../../core/props/types.js';
|
|
5
|
+
import type { ChoiceContentProps } from '../Choice/types.js';
|
|
6
|
+
/** The Checkbox props that `DefaultsProvider` and `UIProvider` can set for a subtree. */
|
|
7
|
+
export interface CheckboxDefaults {
|
|
8
|
+
/**
|
|
9
|
+
* The colour of the checked box. An invalid checkbox uses `error`.
|
|
10
|
+
* @default 'primary'
|
|
11
|
+
*/
|
|
12
|
+
color: Color;
|
|
13
|
+
/**
|
|
14
|
+
* The size of the box and the label text.
|
|
15
|
+
* @default 'md'
|
|
16
|
+
*/
|
|
17
|
+
size: Size;
|
|
18
|
+
/**
|
|
19
|
+
* Multiplies the height of the row, never the text.
|
|
20
|
+
* @default 'default'
|
|
21
|
+
*/
|
|
22
|
+
density: Density;
|
|
23
|
+
/**
|
|
24
|
+
* Mutes the checkbox and disables its input.
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
disabled: boolean;
|
|
28
|
+
}
|
|
29
|
+
declare module '../../core/defaults/context.js' {
|
|
30
|
+
interface ComponentDefaults {
|
|
31
|
+
Checkbox: CheckboxDefaults;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
type CheckboxOwnProps = Partial<CheckboxDefaults> & ChoiceContentProps & {
|
|
35
|
+
/**
|
|
36
|
+
* Whether the box is checked. Bindable.
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
checked?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Shows a dash for a state that is neither checked nor unchecked, such as a "select all"
|
|
42
|
+
* box over a partly selected list. A press clears it. Bindable.
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
indeterminate?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* What the form submits under `name` while the box is checked.
|
|
48
|
+
* @default 'on'
|
|
49
|
+
*/
|
|
50
|
+
value?: string;
|
|
51
|
+
/** Checks run in order after `required`, with the checked state. The first message is shown. */
|
|
52
|
+
rules?: readonly Rule<boolean>[];
|
|
53
|
+
/** When the message shows. Defaults to the form's setting, else `'blur'`. */
|
|
54
|
+
validateOn?: ValidateOn;
|
|
55
|
+
/**
|
|
56
|
+
* The box must be checked, as for accepting terms. Adds an asterisk to the label.
|
|
57
|
+
* @default false
|
|
58
|
+
*/
|
|
59
|
+
required?: boolean;
|
|
60
|
+
/** Inline style of the checkbox's root element. */
|
|
61
|
+
style?: string | null;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* A box that is checked or not, with its label beside it: the platform's own checkbox, drawn in
|
|
65
|
+
* the theme. `class` and `style` apply to the root; `id`, `ref`, `name` and every other attribute
|
|
66
|
+
* go to the `<input type="checkbox">`. The label is the press target, at least 24px tall.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* <Checkbox label="Email me a receipt" name="receipt" bind:checked={receipt} />
|
|
70
|
+
*
|
|
71
|
+
* <Checkbox name="terms" required bind:checked={accepted}>
|
|
72
|
+
* {#snippet label()}I accept the <a href="/terms">terms of service</a>{/snippet}
|
|
73
|
+
* </Checkbox>
|
|
74
|
+
*/
|
|
75
|
+
export type CheckboxProps = ElementProps<HTMLInputElement, Omit<HTMLInputAttributes, 'type' | 'children'>, CheckboxOwnProps>;
|
|
76
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// A tag, a filter, an action or a link, as a compact pill. See ChipProps for the props.
|
|
3
|
+
import { useDefaults } from '../../core/defaults/context.js';
|
|
4
|
+
import { sveltierError } from '../../core/errors/errors.js';
|
|
5
|
+
import { useLocale } from '../../core/locale/locale.js';
|
|
6
|
+
import { sharedClasses } from '../../core/props/classes.js';
|
|
7
|
+
import './chip.css';
|
|
8
|
+
import type { ChipDefaults, ChipProps } from './types.js';
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
variant,
|
|
12
|
+
size,
|
|
13
|
+
density,
|
|
14
|
+
color,
|
|
15
|
+
rounded,
|
|
16
|
+
disabled,
|
|
17
|
+
selected = $bindable(),
|
|
18
|
+
closable = false,
|
|
19
|
+
closeLabel,
|
|
20
|
+
onclose,
|
|
21
|
+
href,
|
|
22
|
+
onclick,
|
|
23
|
+
prepend,
|
|
24
|
+
append,
|
|
25
|
+
children,
|
|
26
|
+
class: className,
|
|
27
|
+
ref = $bindable(null),
|
|
28
|
+
...rest
|
|
29
|
+
}: ChipProps = $props();
|
|
30
|
+
|
|
31
|
+
const resolve = useDefaults<ChipDefaults>('Chip', {
|
|
32
|
+
variant: 'tonal',
|
|
33
|
+
size: 'sm',
|
|
34
|
+
density: 'default',
|
|
35
|
+
color: 'neutral',
|
|
36
|
+
rounded: true,
|
|
37
|
+
disabled: false
|
|
38
|
+
});
|
|
39
|
+
const p = $derived(resolve({ variant, size, density, color, rounded, disabled }));
|
|
40
|
+
const locale = useLocale();
|
|
41
|
+
|
|
42
|
+
const toggle = $derived(selected !== undefined);
|
|
43
|
+
const tag = $derived.by(() => {
|
|
44
|
+
const element = href != null ? 'a' : toggle || onclick ? 'button' : 'span';
|
|
45
|
+
// A close button inside a button or a link would nest one control in another.
|
|
46
|
+
if (closable && element !== 'span') {
|
|
47
|
+
throw sveltierError(
|
|
48
|
+
'SV4001',
|
|
49
|
+
'Chip: a closable chip cannot also be a button or a link, since its close button would sit inside it. Put the action in a Button next to the chip.'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return element;
|
|
53
|
+
});
|
|
54
|
+
const action = $derived(tag !== 'span');
|
|
55
|
+
|
|
56
|
+
function click(event: MouseEvent) {
|
|
57
|
+
if (p.disabled) {
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (toggle) selected = !selected;
|
|
62
|
+
(onclick as ((event: MouseEvent) => void) | null | undefined)?.(event);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const CHECK = 'M4 12.5 5.5 11l4 4 9-9L20 7.5 9.5 18z';
|
|
66
|
+
const CLOSE =
|
|
67
|
+
'M6.4 5 12 10.6 17.6 5 19 6.4 13.4 12l5.6 5.6-1.4 1.4-5.6-5.6L6.4 19 5 17.6l5.6-5.6L5 6.4z';
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<svelte:element
|
|
71
|
+
this={tag}
|
|
72
|
+
bind:this={ref}
|
|
73
|
+
{...rest}
|
|
74
|
+
href={tag === 'a' && !p.disabled ? href : undefined}
|
|
75
|
+
type={tag === 'button' ? 'button' : undefined}
|
|
76
|
+
role={tag === 'a' && p.disabled ? 'link' : undefined}
|
|
77
|
+
disabled={(tag === 'button' && p.disabled) || undefined}
|
|
78
|
+
aria-disabled={(tag === 'a' && p.disabled) || undefined}
|
|
79
|
+
aria-pressed={toggle ? selected : undefined}
|
|
80
|
+
class={[
|
|
81
|
+
'sv-chip',
|
|
82
|
+
sharedClasses({ ...p, variant: selected ? 'flat' : p.variant }, 'Chip'),
|
|
83
|
+
action && 'sv-chip--action',
|
|
84
|
+
p.disabled && 'sv-chip--disabled',
|
|
85
|
+
className
|
|
86
|
+
]}
|
|
87
|
+
onclick={action ? click : undefined}
|
|
88
|
+
>
|
|
89
|
+
{#if selected}
|
|
90
|
+
<span class="sv-chip__prepend">
|
|
91
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d={CHECK} /></svg>
|
|
92
|
+
</span>
|
|
93
|
+
{:else if prepend}
|
|
94
|
+
<span class="sv-chip__prepend">{@render prepend()}</span>
|
|
95
|
+
{/if}
|
|
96
|
+
{#if children}<span class="sv-chip__label">{@render children()}</span>{/if}
|
|
97
|
+
{#if append}<span class="sv-chip__append">{@render append()}</span>{/if}
|
|
98
|
+
{#if closable}
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
class="sv-chip__close"
|
|
102
|
+
aria-label={closeLabel ?? locale.t('close')}
|
|
103
|
+
disabled={p.disabled}
|
|
104
|
+
onclick={() => onclose?.()}
|
|
105
|
+
>
|
|
106
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d={CLOSE} /></svg>
|
|
107
|
+
</button>
|
|
108
|
+
{/if}
|
|
109
|
+
</svelte:element>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
@layer sv.tokens, sv.base, sv.components, sv.utilities;
|
|
2
|
+
|
|
3
|
+
@layer sv.components {
|
|
4
|
+
/* A pill a control tall; text follows size only. */
|
|
5
|
+
.sv-chip {
|
|
6
|
+
position: relative;
|
|
7
|
+
isolation: isolate;
|
|
8
|
+
display: inline-flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: 0.382em;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
max-inline-size: 100%;
|
|
13
|
+
block-size: calc(var(--sv-size-height) * var(--sv-density));
|
|
14
|
+
margin: 0;
|
|
15
|
+
padding: 0 calc(var(--sv-size-height) * var(--sv-density) * 0.382);
|
|
16
|
+
border: var(--sv-hairline) solid var(--sv-variant-border);
|
|
17
|
+
border-radius: var(--sv-rounded, var(--sv-radius-full));
|
|
18
|
+
background-color: var(--sv-variant-bg);
|
|
19
|
+
box-shadow: var(--sv-variant-shadow);
|
|
20
|
+
color: var(--sv-variant-fg);
|
|
21
|
+
font: var(--sv-weight-medium) var(--sv-size-text) / 1 var(--sv-font-ui);
|
|
22
|
+
text-decoration: none;
|
|
23
|
+
white-space: nowrap;
|
|
24
|
+
vertical-align: middle;
|
|
25
|
+
-webkit-tap-highlight-color: transparent;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Pressable chips use the same state layer as Button. */
|
|
29
|
+
.sv-chip--action {
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
user-select: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.sv-chip--action::before {
|
|
35
|
+
content: '';
|
|
36
|
+
position: absolute;
|
|
37
|
+
inset: calc(var(--sv-hairline) * -1);
|
|
38
|
+
z-index: -1;
|
|
39
|
+
border-radius: inherit;
|
|
40
|
+
background-color: currentColor;
|
|
41
|
+
opacity: 0;
|
|
42
|
+
pointer-events: none;
|
|
43
|
+
transition: opacity var(--sv-duration-fast) var(--sv-ease);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@media (hover: hover) {
|
|
47
|
+
.sv-chip--action:where(:not(.sv-chip--disabled)):hover::before {
|
|
48
|
+
opacity: var(--sv-state-hover);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sv-chip--action:where(:not(.sv-chip--disabled)):active::before {
|
|
53
|
+
opacity: var(--sv-state-press);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.sv-chip--disabled {
|
|
57
|
+
opacity: 0.382;
|
|
58
|
+
cursor: not-allowed;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.sv-chip__label {
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
text-overflow: ellipsis;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.sv-chip__prepend,
|
|
67
|
+
.sv-chip__append {
|
|
68
|
+
display: inline-flex;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
:where(.sv-chip) svg {
|
|
72
|
+
inline-size: 1.272em;
|
|
73
|
+
block-size: 1.272em;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* The close button: a round target at the end, tinted on hover. */
|
|
77
|
+
.sv-chip__close {
|
|
78
|
+
display: inline-grid;
|
|
79
|
+
place-items: center;
|
|
80
|
+
inline-size: 1.618em;
|
|
81
|
+
block-size: 1.618em;
|
|
82
|
+
margin-inline-end: -0.382em;
|
|
83
|
+
padding: 0;
|
|
84
|
+
border: 0;
|
|
85
|
+
border-radius: 50%;
|
|
86
|
+
background: transparent;
|
|
87
|
+
color: inherit;
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@media (hover: hover) {
|
|
92
|
+
.sv-chip__close:hover {
|
|
93
|
+
background-color: color-mix(in oklch, currentColor var(--sv-state-hover), transparent);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.sv-chip__close svg {
|
|
98
|
+
inline-size: 1em;
|
|
99
|
+
block-size: 1em;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Chip } from './Chip.svelte';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAnchorAttributes, HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
import type { ElementProps } from '../../core/props/element.js';
|
|
4
|
+
import type { Color, Density, Rounded, Size, Variant } from '../../core/props/types.js';
|
|
5
|
+
/** The Chip props that `DefaultsProvider` and `UIProvider` can set for a subtree. */
|
|
6
|
+
export interface ChipDefaults {
|
|
7
|
+
/**
|
|
8
|
+
* Visual weight. A selected chip is `flat`.
|
|
9
|
+
* @default 'tonal'
|
|
10
|
+
*/
|
|
11
|
+
variant: Variant;
|
|
12
|
+
/**
|
|
13
|
+
* Height and text size.
|
|
14
|
+
* @default 'sm'
|
|
15
|
+
*/
|
|
16
|
+
size: Size;
|
|
17
|
+
/**
|
|
18
|
+
* Multiplies the height and padding, never the text.
|
|
19
|
+
* @default 'default'
|
|
20
|
+
*/
|
|
21
|
+
density: Density;
|
|
22
|
+
/**
|
|
23
|
+
* The colour the variant is painted with.
|
|
24
|
+
* @default 'neutral'
|
|
25
|
+
*/
|
|
26
|
+
color: Color;
|
|
27
|
+
/**
|
|
28
|
+
* Corner rounding. `true` keeps the pill.
|
|
29
|
+
* @default true
|
|
30
|
+
*/
|
|
31
|
+
rounded: Rounded;
|
|
32
|
+
/**
|
|
33
|
+
* A disabled chip cannot be pressed; a disabled link loses its `href`.
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
disabled: boolean;
|
|
37
|
+
}
|
|
38
|
+
declare module '../../core/defaults/context.js' {
|
|
39
|
+
interface ComponentDefaults {
|
|
40
|
+
Chip: ChipDefaults;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A compact label: a tag (optionally `closable`), a filter that toggles (`selected`), an
|
|
45
|
+
* action (`onclick`) or a link (`href`).
|
|
46
|
+
*/
|
|
47
|
+
export type ChipProps = ElementProps<HTMLSpanElement | HTMLButtonElement | HTMLAnchorElement, HTMLAttributes<HTMLSpanElement> & HTMLButtonAttributes & HTMLAnchorAttributes, Partial<ChipDefaults> & {
|
|
48
|
+
/**
|
|
49
|
+
* Makes the chip a toggle button (`aria-pressed`) that flips it when pressed: a filter.
|
|
50
|
+
* Bindable. Shows a check while selected.
|
|
51
|
+
*/
|
|
52
|
+
selected?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Adds a button that removes the chip by calling `onclose`. Only for chips that are not
|
|
55
|
+
* themselves buttons or links.
|
|
56
|
+
* @default false
|
|
57
|
+
*/
|
|
58
|
+
closable?: boolean;
|
|
59
|
+
/** The name of the close button, such as `Remove Euro`. Defaults to the locale's `close`. */
|
|
60
|
+
closeLabel?: string;
|
|
61
|
+
/** Called by the close button. */
|
|
62
|
+
onclose?: () => void;
|
|
63
|
+
/** Content before the label, usually an icon or an avatar. */
|
|
64
|
+
prepend?: Snippet;
|
|
65
|
+
/** Content after the label. */
|
|
66
|
+
append?: Snippet;
|
|
67
|
+
/** The label. */
|
|
68
|
+
children?: Snippet;
|
|
69
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// A control beside its label, with a hint or an error under them: the layout Checkbox and
|
|
3
|
+
// Switch share. See ChoiceProps.
|
|
4
|
+
import './choice.css';
|
|
5
|
+
import type { ChoiceProps } from './types.js';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
id: idProp,
|
|
9
|
+
label,
|
|
10
|
+
hint,
|
|
11
|
+
error = false,
|
|
12
|
+
required = false,
|
|
13
|
+
disabled = false,
|
|
14
|
+
children,
|
|
15
|
+
class: className,
|
|
16
|
+
ref = $bindable(null),
|
|
17
|
+
...rest
|
|
18
|
+
}: ChoiceProps = $props();
|
|
19
|
+
|
|
20
|
+
const uid = $props.id();
|
|
21
|
+
const id = $derived(idProp ?? uid);
|
|
22
|
+
const message = $derived(typeof error === 'string' ? error : undefined);
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<div
|
|
26
|
+
bind:this={ref}
|
|
27
|
+
{...rest}
|
|
28
|
+
class={[
|
|
29
|
+
'sv-choice',
|
|
30
|
+
error !== false && 'sv-choice--invalid',
|
|
31
|
+
disabled && 'sv-choice--disabled',
|
|
32
|
+
className
|
|
33
|
+
]}
|
|
34
|
+
>
|
|
35
|
+
<!-- The label wraps the control, so a press anywhere on it toggles the control. -->
|
|
36
|
+
<label class="sv-choice__row">
|
|
37
|
+
{@render children({
|
|
38
|
+
id,
|
|
39
|
+
'aria-describedby': message ? `${id}-error` : hint ? `${id}-hint` : undefined,
|
|
40
|
+
'aria-invalid': error !== false || undefined,
|
|
41
|
+
required,
|
|
42
|
+
disabled
|
|
43
|
+
})}
|
|
44
|
+
{#if label}
|
|
45
|
+
<span class="sv-choice__label"
|
|
46
|
+
>{#if typeof label === 'string'}{label}{:else}{@render label()}{/if}{#if required}<span
|
|
47
|
+
class="sv-choice__required"
|
|
48
|
+
aria-hidden="true">*</span
|
|
49
|
+
>{/if}</span
|
|
50
|
+
>
|
|
51
|
+
{/if}
|
|
52
|
+
</label>
|
|
53
|
+
<div class="sv-choice__messages">
|
|
54
|
+
{#if hint && !message}<p class="sv-choice__hint" id="{id}-hint">{hint}</p>{/if}
|
|
55
|
+
<!-- Announces an error that appears after the user has moved on. -->
|
|
56
|
+
<div aria-live="polite">
|
|
57
|
+
{#if message}<p class="sv-choice__error" id="{id}-error">{message}</p>{/if}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|