@sveltier/ui 0.0.0-stage → 0.1.0-alpha.1
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/Accordion/Accordion.svelte +74 -0
- package/dist/components/Accordion/Accordion.svelte.d.ts +25 -0
- package/dist/components/Accordion/accordion.css +88 -0
- package/dist/components/Accordion/index.d.ts +2 -0
- package/dist/components/Accordion/index.js +1 -0
- package/dist/components/Accordion/types.d.ts +64 -0
- package/dist/components/Accordion/types.js +1 -0
- 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/Dialog/Dialog.svelte +36 -0
- package/dist/components/Dialog/Dialog.svelte.d.ts +4 -0
- package/dist/components/Dialog/index.d.ts +2 -0
- package/dist/components/Dialog/index.js +1 -0
- package/dist/components/Dialog/types.d.ts +50 -0
- package/dist/components/Dialog/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/Menu/Menu.svelte +195 -0
- package/dist/components/Menu/Menu.svelte.d.ts +4 -0
- package/dist/components/Menu/index.d.ts +2 -0
- package/dist/components/Menu/index.js +1 -0
- package/dist/components/Menu/menu.css +103 -0
- package/dist/components/Menu/types.d.ts +90 -0
- package/dist/components/Menu/types.js +1 -0
- package/dist/components/Modal/Modal.svelte +146 -0
- package/dist/components/Modal/Modal.svelte.d.ts +4 -0
- package/dist/components/Modal/modal.css +150 -0
- package/dist/components/Modal/types.d.ts +54 -0
- package/dist/components/Modal/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/Popover/Popover.svelte +115 -0
- package/dist/components/Popover/Popover.svelte.d.ts +4 -0
- package/dist/components/Popover/index.d.ts +2 -0
- package/dist/components/Popover/index.js +1 -0
- package/dist/components/Popover/popover.css +67 -0
- package/dist/components/Popover/types.d.ts +83 -0
- package/dist/components/Popover/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/Sheet/Sheet.svelte +56 -0
- package/dist/components/Sheet/Sheet.svelte.d.ts +4 -0
- package/dist/components/Sheet/index.d.ts +2 -0
- package/dist/components/Sheet/index.js +2 -0
- package/dist/components/Sheet/sheet.css +61 -0
- package/dist/components/Sheet/types.d.ts +51 -0
- package/dist/components/Sheet/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/components/Tooltip/Tooltip.svelte +161 -0
- package/dist/components/Tooltip/Tooltip.svelte.d.ts +4 -0
- package/dist/components/Tooltip/index.d.ts +2 -0
- package/dist/components/Tooltip/index.js +1 -0
- package/dist/components/Tooltip/tooltip.css +44 -0
- package/dist/components/Tooltip/types.d.ts +55 -0
- package/dist/components/Tooltip/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 +124 -0
- package/dist/core/errors/codes.js +108 -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 +56 -0
- package/dist/core/locale/locale.js +82 -0
- package/dist/core/overlay/types.d.ts +33 -0
- package/dist/core/overlay/types.js +1 -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 +63 -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 +73 -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/core/toast/ToastOutlet.svelte +192 -0
- package/dist/core/toast/ToastOutlet.svelte.d.ts +11 -0
- package/dist/core/toast/ToastProvider.svelte +39 -0
- package/dist/core/toast/ToastProvider.svelte.d.ts +19 -0
- package/dist/core/toast/toast.css +134 -0
- package/dist/core/toast/toast.svelte.d.ts +117 -0
- package/dist/core/toast/toast.svelte.js +133 -0
- package/dist/dev/off.d.ts +5 -0
- package/dist/dev/off.js +4 -0
- package/dist/dev/on.d.ts +20 -0
- package/dist/dev/on.js +49 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +83 -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 +32 -0
- package/dist/primitives/layer/dismissable.js +91 -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 +140 -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,246 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// A number in a Field: typed in the locale's format, stepped with the keyboard or buttons, and
|
|
3
|
+
// validated. See NumberFieldProps for the props.
|
|
4
|
+
import { untrack } from 'svelte';
|
|
5
|
+
import { checkValue, DEV, warnUnnamed } from '#dev';
|
|
6
|
+
import { useDefaults } from '../../core/defaults/context.js';
|
|
7
|
+
import { createField } from '../../core/form/field.svelte.js';
|
|
8
|
+
import { numberConstraintMessage, parseNumber } from '../../core/form/number.js';
|
|
9
|
+
import type { Rule } from '../../core/form/types.js';
|
|
10
|
+
import { useLocale } from '../../core/locale/locale.js';
|
|
11
|
+
import Field from '../Field/Field.svelte';
|
|
12
|
+
import './number-field.css';
|
|
13
|
+
import type { NumberFieldDefaults, NumberFieldProps } from './types.js';
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
variant,
|
|
17
|
+
color,
|
|
18
|
+
size,
|
|
19
|
+
density,
|
|
20
|
+
rounded,
|
|
21
|
+
disabled,
|
|
22
|
+
value = $bindable(null),
|
|
23
|
+
min,
|
|
24
|
+
max,
|
|
25
|
+
step,
|
|
26
|
+
format,
|
|
27
|
+
controls = true,
|
|
28
|
+
label,
|
|
29
|
+
hint,
|
|
30
|
+
error,
|
|
31
|
+
rules,
|
|
32
|
+
validateOn,
|
|
33
|
+
required = false,
|
|
34
|
+
readonly = false,
|
|
35
|
+
name,
|
|
36
|
+
prepend,
|
|
37
|
+
append,
|
|
38
|
+
id,
|
|
39
|
+
'aria-describedby': describedBy,
|
|
40
|
+
onkeydown,
|
|
41
|
+
class: className,
|
|
42
|
+
style,
|
|
43
|
+
ref = $bindable(null),
|
|
44
|
+
...rest
|
|
45
|
+
}: NumberFieldProps = $props();
|
|
46
|
+
|
|
47
|
+
const resolve = useDefaults<NumberFieldDefaults>('NumberField', {
|
|
48
|
+
variant: 'outlined',
|
|
49
|
+
color: 'primary',
|
|
50
|
+
size: 'md',
|
|
51
|
+
density: 'default',
|
|
52
|
+
rounded: true,
|
|
53
|
+
disabled: false
|
|
54
|
+
});
|
|
55
|
+
const p = $derived(resolve({ variant, color, size, density, rounded, disabled }));
|
|
56
|
+
const locale = useLocale();
|
|
57
|
+
const uid = $props.id();
|
|
58
|
+
const inputId = $derived(id ?? uid);
|
|
59
|
+
|
|
60
|
+
/** The number written in the locale; empty for no number. */
|
|
61
|
+
const show = (number: number | null) =>
|
|
62
|
+
number === null || !Number.isFinite(number)
|
|
63
|
+
? ''
|
|
64
|
+
: locale.formatNumber(number, format ?? { maximumFractionDigits: 20 });
|
|
65
|
+
|
|
66
|
+
// What the input shows, and what it means: a number, null for empty, NaN for not a number. A
|
|
67
|
+
// form reset goes back to the text the field started with.
|
|
68
|
+
const initial = untrack(() => show(value ?? null));
|
|
69
|
+
let text = $state(initial);
|
|
70
|
+
let parsed = $state<number | null>(untrack(() => value ?? null));
|
|
71
|
+
|
|
72
|
+
// A value set from outside, not typed, is written in the locale format.
|
|
73
|
+
$effect(() => {
|
|
74
|
+
const next = value ?? null;
|
|
75
|
+
if (
|
|
76
|
+
!Object.is(
|
|
77
|
+
next,
|
|
78
|
+
untrack(() => parsed)
|
|
79
|
+
)
|
|
80
|
+
) {
|
|
81
|
+
parsed = next;
|
|
82
|
+
text = show(next);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const field = createField<number | null>({
|
|
87
|
+
value: () => parsed,
|
|
88
|
+
empty: (number) => number === null,
|
|
89
|
+
check: (number) => numberConstraintMessage(number, { required, min, max, step }, locale),
|
|
90
|
+
// Rules never see an empty or unreadable value: the checks above stop those first.
|
|
91
|
+
rules: () => rules as readonly Rule<number | null>[] | undefined,
|
|
92
|
+
error: () => error,
|
|
93
|
+
validateOn: () => validateOn,
|
|
94
|
+
control: () => ref
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (DEV) {
|
|
98
|
+
$effect(() => {
|
|
99
|
+
warnUnnamed('NumberField', ref);
|
|
100
|
+
// A currency or percent sign in the field would make the text unreadable as a number.
|
|
101
|
+
checkValue(
|
|
102
|
+
'NumberField',
|
|
103
|
+
'format.style',
|
|
104
|
+
format?.style,
|
|
105
|
+
['decimal'],
|
|
106
|
+
'Put a currency or a unit in prepend or append.'
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const places = (number: number | undefined) => (String(number ?? '').split('.')[1] ?? '').length;
|
|
112
|
+
// The keys and buttons move by step, or by 1 when no step is set (and then any number is
|
|
113
|
+
// valid). Enough decimals for the step and its starting point, so 0.1 + 0.2 lands on 0.3.
|
|
114
|
+
const increment = $derived(step ?? 1);
|
|
115
|
+
const decimals = $derived(Math.max(places(increment), places(min)));
|
|
116
|
+
const inert = $derived(p.disabled || readonly);
|
|
117
|
+
const current = $derived(parsed !== null && Number.isFinite(parsed) ? parsed : null);
|
|
118
|
+
|
|
119
|
+
// Called for typing and for a form reset alike, so the value always follows the text.
|
|
120
|
+
function read(next: string) {
|
|
121
|
+
text = next;
|
|
122
|
+
const number = parseNumber(next, locale.locale);
|
|
123
|
+
parsed = number;
|
|
124
|
+
if (number === null || Number.isFinite(number)) value = number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Sets a number the keys or buttons chose, within min and max. */
|
|
128
|
+
function commit(number: number) {
|
|
129
|
+
const bounded = Math.min(max ?? Infinity, Math.max(min ?? -Infinity, number));
|
|
130
|
+
const next = Number(bounded.toFixed(decimals));
|
|
131
|
+
parsed = next;
|
|
132
|
+
value = next;
|
|
133
|
+
text = show(next);
|
|
134
|
+
field.changed();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Moves along the steps counted from min, as a native number input does: from a value between
|
|
138
|
+
// two steps, up goes to the next one and down to the previous one. Empty starts at min, or 0.
|
|
139
|
+
function stepBy(steps: number) {
|
|
140
|
+
if (inert) return;
|
|
141
|
+
const start = min ?? 0;
|
|
142
|
+
if (current === null) return commit(start);
|
|
143
|
+
const offset = (current - start) / increment;
|
|
144
|
+
const from = steps > 0 ? Math.floor(offset + 1e-9) : Math.ceil(offset - 1e-9);
|
|
145
|
+
commit(start + (from + steps) * increment);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function keydown(event: KeyboardEvent & { currentTarget: EventTarget & HTMLInputElement }) {
|
|
149
|
+
(onkeydown as ((event: KeyboardEvent) => void) | null | undefined)?.(event);
|
|
150
|
+
if (event.defaultPrevented || inert) return;
|
|
151
|
+
const moves: Record<string, (() => void) | undefined> = {
|
|
152
|
+
ArrowUp: () => stepBy(1),
|
|
153
|
+
ArrowDown: () => stepBy(-1),
|
|
154
|
+
PageUp: () => stepBy(10),
|
|
155
|
+
PageDown: () => stepBy(-10),
|
|
156
|
+
Home: min == null ? undefined : () => commit(min),
|
|
157
|
+
End: max == null ? undefined : () => commit(max)
|
|
158
|
+
};
|
|
159
|
+
const move = moves[event.key];
|
|
160
|
+
if (!move) return;
|
|
161
|
+
event.preventDefault();
|
|
162
|
+
move();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Leaving means focus went outside the field, not to a button in it. The number is then
|
|
166
|
+
// written in the locale format.
|
|
167
|
+
function leave(event: FocusEvent) {
|
|
168
|
+
const root = event.currentTarget as HTMLElement;
|
|
169
|
+
if (root.contains(event.relatedTarget as Node | null)) return;
|
|
170
|
+
if (current !== null) text = show(current);
|
|
171
|
+
field.left();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Only whole numbers, and no minus sign, can use the numeric keypad.
|
|
175
|
+
const integer = $derived(step != null && decimals === 0);
|
|
176
|
+
</script>
|
|
177
|
+
|
|
178
|
+
{#snippet end()}
|
|
179
|
+
{@render append?.()}
|
|
180
|
+
{#if controls}
|
|
181
|
+
<span class="sv-number-field__steps">
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
class="sv-number-field__step"
|
|
185
|
+
tabindex="-1"
|
|
186
|
+
aria-label={locale.t('decrease')}
|
|
187
|
+
aria-controls={inputId}
|
|
188
|
+
disabled={inert || (min != null && current !== null && current <= min)}
|
|
189
|
+
onpointerdown={(event) => event.preventDefault()}
|
|
190
|
+
onclick={() => stepBy(-1)}
|
|
191
|
+
>
|
|
192
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14" /></svg>
|
|
193
|
+
</button>
|
|
194
|
+
<button
|
|
195
|
+
type="button"
|
|
196
|
+
class="sv-number-field__step"
|
|
197
|
+
tabindex="-1"
|
|
198
|
+
aria-label={locale.t('increase')}
|
|
199
|
+
aria-controls={inputId}
|
|
200
|
+
disabled={inert || (max != null && current !== null && current >= max)}
|
|
201
|
+
onpointerdown={(event) => event.preventDefault()}
|
|
202
|
+
onclick={() => stepBy(1)}
|
|
203
|
+
>
|
|
204
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
|
|
205
|
+
</button>
|
|
206
|
+
</span>
|
|
207
|
+
{/if}
|
|
208
|
+
{/snippet}
|
|
209
|
+
|
|
210
|
+
<Field
|
|
211
|
+
{...p}
|
|
212
|
+
id={inputId}
|
|
213
|
+
{label}
|
|
214
|
+
{hint}
|
|
215
|
+
error={field.error}
|
|
216
|
+
{required}
|
|
217
|
+
{readonly}
|
|
218
|
+
{prepend}
|
|
219
|
+
append={controls || append ? end : undefined}
|
|
220
|
+
class={['sv-number-field', className]}
|
|
221
|
+
{style}
|
|
222
|
+
oninput={field.changed}
|
|
223
|
+
onfocusout={leave}
|
|
224
|
+
>
|
|
225
|
+
{#snippet children(attributes)}
|
|
226
|
+
<input
|
|
227
|
+
bind:this={ref}
|
|
228
|
+
defaultValue={initial}
|
|
229
|
+
{...rest}
|
|
230
|
+
{...attributes}
|
|
231
|
+
aria-describedby={[describedBy, attributes['aria-describedby']].filter(Boolean).join(' ') ||
|
|
232
|
+
undefined}
|
|
233
|
+
type="text"
|
|
234
|
+
role="spinbutton"
|
|
235
|
+
inputmode={min != null && min >= 0 && integer ? 'numeric' : 'decimal'}
|
|
236
|
+
autocomplete="off"
|
|
237
|
+
aria-valuenow={current ?? undefined}
|
|
238
|
+
aria-valuemin={min}
|
|
239
|
+
aria-valuemax={max}
|
|
240
|
+
aria-valuetext={current === null ? undefined : text}
|
|
241
|
+
bind:value={() => text, read}
|
|
242
|
+
onkeydown={keydown}
|
|
243
|
+
/>
|
|
244
|
+
{#if name}<input type="hidden" {name} value={current ?? ''} />{/if}
|
|
245
|
+
{/snippet}
|
|
246
|
+
</Field>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as NumberField } from './NumberField.svelte';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
@layer sv.tokens, sv.base, sv.components, sv.utilities;
|
|
2
|
+
|
|
3
|
+
@layer sv.components {
|
|
4
|
+
/* Digits keep one width, so numbers that change or stack line up. */
|
|
5
|
+
.sv-number-field .sv-field__control {
|
|
6
|
+
font-variant-numeric: tabular-nums;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.sv-number-field .sv-field__append {
|
|
10
|
+
align-self: stretch;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.sv-number-field__steps {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: var(--sv-space-2);
|
|
17
|
+
margin-inline-end: calc(var(--sv-size-height) * var(--sv-density) * -0.146);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Square, φ⁻¹ of the field's height, and never under 24px (WCAG 2.5.8). */
|
|
21
|
+
.sv-number-field__step {
|
|
22
|
+
--sv-step: max(1.5rem, calc(var(--sv-size-height) * var(--sv-density) * 0.618));
|
|
23
|
+
position: relative;
|
|
24
|
+
display: grid;
|
|
25
|
+
place-items: center;
|
|
26
|
+
inline-size: var(--sv-step);
|
|
27
|
+
block-size: var(--sv-step);
|
|
28
|
+
padding: 0;
|
|
29
|
+
border: 0;
|
|
30
|
+
border-radius: var(--sv-radius-sm);
|
|
31
|
+
background: none;
|
|
32
|
+
color: var(--sv-color-text-muted);
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* The state layer, as on a Button: only its opacity changes. */
|
|
37
|
+
.sv-number-field__step::before {
|
|
38
|
+
content: '';
|
|
39
|
+
position: absolute;
|
|
40
|
+
inset: 0;
|
|
41
|
+
border-radius: inherit;
|
|
42
|
+
background-color: currentColor;
|
|
43
|
+
opacity: 0;
|
|
44
|
+
transition: opacity var(--sv-duration-fast) var(--sv-ease);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@media (hover: hover) {
|
|
48
|
+
.sv-number-field__step:enabled:hover {
|
|
49
|
+
color: var(--sv-color-text);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sv-number-field__step:enabled:hover::before {
|
|
53
|
+
opacity: var(--sv-state-hover);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.sv-number-field__step:enabled:active::before {
|
|
58
|
+
opacity: var(--sv-state-press);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.sv-number-field__step:disabled {
|
|
62
|
+
opacity: 0.382;
|
|
63
|
+
cursor: not-allowed;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.sv-number-field__step svg {
|
|
67
|
+
inline-size: 1.272em;
|
|
68
|
+
block-size: 1.272em;
|
|
69
|
+
fill: none;
|
|
70
|
+
stroke: currentColor;
|
|
71
|
+
stroke-width: 1.75;
|
|
72
|
+
stroke-linecap: round;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
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 { FieldContentProps, FieldDefaults } from '../Field/types.js';
|
|
5
|
+
/** The NumberField props that `DefaultsProvider` and `UIProvider` can set for a subtree. */
|
|
6
|
+
export type NumberFieldDefaults = FieldDefaults;
|
|
7
|
+
declare module '../../core/defaults/context.js' {
|
|
8
|
+
interface ComponentDefaults {
|
|
9
|
+
NumberField: NumberFieldDefaults;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
type NumberFieldOwnProps = Partial<NumberFieldDefaults> & FieldContentProps & {
|
|
13
|
+
/** The number, or `null` when the field is empty. Bindable. */
|
|
14
|
+
value?: number | null;
|
|
15
|
+
/** The smallest value. The keys and buttons stop here; a smaller typed value is an error. */
|
|
16
|
+
min?: number;
|
|
17
|
+
/** The largest value. The keys and buttons stop here; a larger typed value is an error. */
|
|
18
|
+
max?: number;
|
|
19
|
+
/**
|
|
20
|
+
* How far the arrow keys and the buttons move; Page Up and Page Down move ten steps. When
|
|
21
|
+
* set, a typed value off the steps, counted from `min`, is an error. When not set, they move
|
|
22
|
+
* by 1 and any number is accepted, decimals included.
|
|
23
|
+
*/
|
|
24
|
+
step?: number;
|
|
25
|
+
/**
|
|
26
|
+
* How the number is written once the user leaves the field, as `Intl.NumberFormat` options
|
|
27
|
+
* in the app's locale: digits only, such as `{ minimumFractionDigits: 2 }`, so the user can
|
|
28
|
+
* edit what they see. Put a currency or a unit in `prepend` or `append`.
|
|
29
|
+
* @default { maximumFractionDigits: 20 }
|
|
30
|
+
*/
|
|
31
|
+
format?: Intl.NumberFormatOptions;
|
|
32
|
+
/**
|
|
33
|
+
* Shows decrease and increase buttons at the end of the field.
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
controls?: boolean;
|
|
37
|
+
/** Checks run in order after the HTML constraints, with the number. The first message is shown. */
|
|
38
|
+
rules?: readonly Rule<number>[];
|
|
39
|
+
/** When the message shows. Defaults to the form's setting, else `'blur'`. */
|
|
40
|
+
validateOn?: ValidateOn;
|
|
41
|
+
/**
|
|
42
|
+
* The field must not be empty. Adds an asterisk to the label.
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
required?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* The number can be read and copied but not changed.
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
50
|
+
readonly?: boolean;
|
|
51
|
+
/** Submits the plain number, such as `1234.5`, under this name in a form. */
|
|
52
|
+
name?: string;
|
|
53
|
+
/** Inline style of the field's root element. */
|
|
54
|
+
style?: string | null;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* A number typed in the app's locale, stepped with the arrow keys or the buttons, and checked
|
|
58
|
+
* against `min`, `max` and `step`. The field shows the number formatted, for example
|
|
59
|
+
* `1,234.50`, and binds and submits the plain number. `class` and `style` apply to the field;
|
|
60
|
+
* `id`, `ref` and other attributes go to the `<input>`.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* <NumberField
|
|
64
|
+
* label="Quantity"
|
|
65
|
+
* name="quantity"
|
|
66
|
+
* min={1}
|
|
67
|
+
* max={99}
|
|
68
|
+
* required
|
|
69
|
+
* bind:value={quantity}
|
|
70
|
+
* />
|
|
71
|
+
*/
|
|
72
|
+
export type NumberFieldProps = ElementProps<HTMLInputElement, HTMLInputAttributes, NumberFieldOwnProps>;
|
|
73
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// A non-modal panel next to the control that opens it. See PopoverProps.
|
|
3
|
+
import { createAttachmentKey, type Attachment } from 'svelte/attachments';
|
|
4
|
+
import { checkValue, DEV, warnUnnamed } from '#dev';
|
|
5
|
+
import { useDefaults } from '../../core/defaults/context.js';
|
|
6
|
+
import type { CloseReason, TriggerAttributes } from '../../core/overlay/types.js';
|
|
7
|
+
import { sharedClasses } from '../../core/props/classes.js';
|
|
8
|
+
import { ALIGNS, anchored, SIDES } from '../../primitives/anchored/anchored.js';
|
|
9
|
+
import { tabbable } from '../../primitives/focus/tabbable.js';
|
|
10
|
+
import { dismissable } from '../../primitives/layer/dismissable.js';
|
|
11
|
+
import { pop } from '../../primitives/motion/motion.js';
|
|
12
|
+
import './popover.css';
|
|
13
|
+
import type { PopoverDefaults, PopoverProps } from './types.js';
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
size,
|
|
17
|
+
density,
|
|
18
|
+
rounded,
|
|
19
|
+
trigger,
|
|
20
|
+
open = $bindable(false),
|
|
21
|
+
title,
|
|
22
|
+
children,
|
|
23
|
+
actions,
|
|
24
|
+
side = 'bottom',
|
|
25
|
+
align = 'start',
|
|
26
|
+
offset = 8,
|
|
27
|
+
persistent = false,
|
|
28
|
+
onclose,
|
|
29
|
+
id: idProp,
|
|
30
|
+
class: className,
|
|
31
|
+
ref = $bindable(null),
|
|
32
|
+
...rest
|
|
33
|
+
}: PopoverProps = $props();
|
|
34
|
+
|
|
35
|
+
const resolve = useDefaults<PopoverDefaults>('Popover', {
|
|
36
|
+
size: 'md',
|
|
37
|
+
density: 'default',
|
|
38
|
+
rounded: true
|
|
39
|
+
});
|
|
40
|
+
const p = $derived(resolve({ size, density, rounded }));
|
|
41
|
+
|
|
42
|
+
if (DEV) {
|
|
43
|
+
$effect(() => checkValue('Popover', 'side', side, SIDES));
|
|
44
|
+
$effect(() => checkValue('Popover', 'align', align, ALIGNS));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const uid = $props.id();
|
|
48
|
+
const id = $derived(idProp ?? uid);
|
|
49
|
+
|
|
50
|
+
// The control that opens it: the panel is placed against it, and focus goes back to it.
|
|
51
|
+
let anchor = $state<HTMLElement | null>(null);
|
|
52
|
+
const capture: Attachment<HTMLElement> = (node) => {
|
|
53
|
+
anchor = node;
|
|
54
|
+
return () => {
|
|
55
|
+
if (anchor === node) anchor = null;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const ANCHOR = createAttachmentKey();
|
|
59
|
+
const triggerAttributes: TriggerAttributes = $derived({
|
|
60
|
+
'aria-haspopup': 'dialog',
|
|
61
|
+
'aria-expanded': open,
|
|
62
|
+
'aria-controls': open ? id : undefined,
|
|
63
|
+
onclick: () => (open ? close('action') : (open = true)),
|
|
64
|
+
[ANCHOR]: capture
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
function close(reason: CloseReason) {
|
|
68
|
+
if (!open) return;
|
|
69
|
+
// Closed from inside, focus goes back to the control; a press or a Tab elsewhere has
|
|
70
|
+
// already chosen where it goes.
|
|
71
|
+
if (ref?.contains(document.activeElement)) anchor?.focus({ preventScroll: true });
|
|
72
|
+
open = false;
|
|
73
|
+
onclose?.(reason);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// In the top layer, with focus on the element marked autofocus, else the first that takes
|
|
77
|
+
// it, else the panel itself.
|
|
78
|
+
const show: Attachment<HTMLElement> = (panel) => {
|
|
79
|
+
panel.showPopover();
|
|
80
|
+
if (DEV) warnUnnamed('Popover', panel, 'a title');
|
|
81
|
+
const target = panel.querySelector<HTMLElement>('[autofocus]') ?? tabbable(panel)[0] ?? panel;
|
|
82
|
+
target.focus({ preventScroll: true });
|
|
83
|
+
};
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
{@render trigger(triggerAttributes)}
|
|
87
|
+
|
|
88
|
+
{#if open}
|
|
89
|
+
<div
|
|
90
|
+
bind:this={ref}
|
|
91
|
+
aria-labelledby={title ? `${id}-title` : undefined}
|
|
92
|
+
tabindex="-1"
|
|
93
|
+
{...rest}
|
|
94
|
+
{id}
|
|
95
|
+
role="dialog"
|
|
96
|
+
popover="manual"
|
|
97
|
+
class={['sv-popover', sharedClasses(p, 'Popover'), className]}
|
|
98
|
+
{@attach show}
|
|
99
|
+
{@attach anchored({ anchor, side, align, offset })}
|
|
100
|
+
{@attach dismissable({
|
|
101
|
+
onDismiss: close,
|
|
102
|
+
escape: !persistent,
|
|
103
|
+
outside: !persistent,
|
|
104
|
+
focusOutside: !persistent,
|
|
105
|
+
inside: () => [anchor]
|
|
106
|
+
})}
|
|
107
|
+
transition:pop
|
|
108
|
+
>
|
|
109
|
+
{#if title}<div class="sv-popover__title" id="{id}-title">{title}</div>{/if}
|
|
110
|
+
{#if children}<div class="sv-popover__body">{@render children()}</div>{/if}
|
|
111
|
+
{#if actions}
|
|
112
|
+
<div class="sv-popover__actions">{@render actions({ close: () => close('action') })}</div>
|
|
113
|
+
{/if}
|
|
114
|
+
</div>
|
|
115
|
+
{/if}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Popover } from './Popover.svelte';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
@layer sv.tokens, sv.base, sv.components, sv.utilities;
|
|
2
|
+
|
|
3
|
+
@layer sv.components {
|
|
4
|
+
/*
|
|
5
|
+
* A raised surface beside its control, as wide as its content up to its size and never wider
|
|
6
|
+
* or taller than the screen, scrolling inside when its content is longer.
|
|
7
|
+
*/
|
|
8
|
+
.sv-popover {
|
|
9
|
+
--sv-popover-pad: calc(var(--sv-space-13) * var(--sv-density));
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
display: grid;
|
|
12
|
+
gap: var(--sv-space-13);
|
|
13
|
+
inline-size: max-content;
|
|
14
|
+
max-inline-size: min(var(--sv-popover-width), 100vw - 2 * var(--sv-space-8));
|
|
15
|
+
max-block-size: calc(100dvh - 2 * var(--sv-space-8));
|
|
16
|
+
padding: var(--sv-popover-pad);
|
|
17
|
+
overflow: auto;
|
|
18
|
+
overscroll-behavior: contain;
|
|
19
|
+
border: var(--sv-hairline) solid var(--sv-color-border-soft);
|
|
20
|
+
border-radius: var(--sv-rounded, var(--sv-radius-lg));
|
|
21
|
+
outline: none;
|
|
22
|
+
background-color: var(--sv-color-surface);
|
|
23
|
+
box-shadow: var(--sv-shadow);
|
|
24
|
+
color: var(--sv-color-text);
|
|
25
|
+
font-family: var(--sv-font-ui);
|
|
26
|
+
font-size: var(--sv-text-sm);
|
|
27
|
+
line-height: var(--sv-leading-normal);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Widths on the φ series: 13, 17, 21, 27 and 34rem. */
|
|
31
|
+
.sv-popover.sv-size-xs {
|
|
32
|
+
--sv-popover-width: 13rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.sv-popover.sv-size-sm {
|
|
36
|
+
--sv-popover-width: 17rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.sv-popover.sv-size-md {
|
|
40
|
+
--sv-popover-width: 21rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.sv-popover.sv-size-lg {
|
|
44
|
+
--sv-popover-width: 27rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sv-popover.sv-size-xl {
|
|
48
|
+
--sv-popover-width: 34rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* The heading in the theme's display voice, a step above the body. */
|
|
52
|
+
.sv-popover__title {
|
|
53
|
+
font: var(--sv-weight-display) var(--sv-text-md) / var(--sv-leading-snug) var(--sv-font-display);
|
|
54
|
+
letter-spacing: var(--sv-tracking-display);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.sv-popover__body {
|
|
58
|
+
min-inline-size: 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.sv-popover__actions {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-wrap: wrap;
|
|
64
|
+
gap: var(--sv-space-8);
|
|
65
|
+
justify-content: flex-end;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { CloseReason, TriggerAttributes } from '../../core/overlay/types.js';
|
|
4
|
+
import type { ElementProps } from '../../core/props/element.js';
|
|
5
|
+
import type { Density, Rounded, Size } from '../../core/props/types.js';
|
|
6
|
+
import type { Align, Side } from '../../primitives/anchored/anchored.js';
|
|
7
|
+
/** The Popover props that `DefaultsProvider` and `UIProvider` can set for a subtree. */
|
|
8
|
+
export interface PopoverDefaults {
|
|
9
|
+
/**
|
|
10
|
+
* The widest it grows, from `xs` (13rem) to `xl` (34rem); narrower content keeps it narrower.
|
|
11
|
+
* @default 'md'
|
|
12
|
+
*/
|
|
13
|
+
size: Size;
|
|
14
|
+
/**
|
|
15
|
+
* Multiplies the padding, never the text.
|
|
16
|
+
* @default 'default'
|
|
17
|
+
*/
|
|
18
|
+
density: Density;
|
|
19
|
+
/**
|
|
20
|
+
* Corner rounding. `true` keeps the theme's radius for large surfaces.
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
rounded: Rounded;
|
|
24
|
+
}
|
|
25
|
+
declare module '../../core/defaults/context.js' {
|
|
26
|
+
interface ComponentDefaults {
|
|
27
|
+
Popover: PopoverDefaults;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A panel next to the control that opens it, for content the user works in: a few fields, a
|
|
32
|
+
* choice, a short explanation with a link. It does not block the page: Escape, a press outside
|
|
33
|
+
* and focus moving out close it. Focus moves into it when it opens and back to the control when
|
|
34
|
+
* it closes from inside. For text that only describes a control, use a Tooltip; for a task that
|
|
35
|
+
* needs the whole screen, a Dialog. `class`, `style`, `ref` and other attributes go to the panel.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* <Popover title="Amount range">
|
|
39
|
+
* {#snippet trigger(attributes)}<Button {...attributes} variant="outlined">Amount</Button>{/snippet}
|
|
40
|
+
* <NumberField label="From" bind:value={from} />
|
|
41
|
+
* {#snippet actions({ close })}<Button onclick={close}>Apply</Button>{/snippet}
|
|
42
|
+
* </Popover>
|
|
43
|
+
*/
|
|
44
|
+
export type PopoverProps = ElementProps<HTMLDivElement, Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'children' | 'popover' | 'role'>, Partial<PopoverDefaults> & {
|
|
45
|
+
/** The control that opens and closes it. Spread the attributes it receives onto it. */
|
|
46
|
+
trigger: Snippet<[TriggerAttributes]>;
|
|
47
|
+
/**
|
|
48
|
+
* Whether it is open. Bindable; it sets this to `false` when it closes itself.
|
|
49
|
+
* @default false
|
|
50
|
+
*/
|
|
51
|
+
open?: boolean;
|
|
52
|
+
/** A heading, which also names it. Without it, name it with `aria-label`. */
|
|
53
|
+
title?: string;
|
|
54
|
+
/** The content. */
|
|
55
|
+
children?: Snippet;
|
|
56
|
+
/** Buttons at the end. `close()` closes it with the reason `'action'`. */
|
|
57
|
+
actions?: Snippet<[{
|
|
58
|
+
close: () => void;
|
|
59
|
+
}]>;
|
|
60
|
+
/**
|
|
61
|
+
* The side of the control it opens on, moved to the opposite side when there is no room.
|
|
62
|
+
* `start` and `end` follow the writing direction.
|
|
63
|
+
* @default 'bottom'
|
|
64
|
+
*/
|
|
65
|
+
side?: Side;
|
|
66
|
+
/**
|
|
67
|
+
* Its alignment along that side.
|
|
68
|
+
* @default 'start'
|
|
69
|
+
*/
|
|
70
|
+
align?: Align;
|
|
71
|
+
/**
|
|
72
|
+
* The room between the control and the panel, in px.
|
|
73
|
+
* @default 8
|
|
74
|
+
*/
|
|
75
|
+
offset?: number;
|
|
76
|
+
/**
|
|
77
|
+
* Escape, a press outside and focus moving out do not close it: an answer is needed.
|
|
78
|
+
* @default false
|
|
79
|
+
*/
|
|
80
|
+
persistent?: boolean;
|
|
81
|
+
/** Called when it closes itself, with the reason. */
|
|
82
|
+
onclose?: (reason: CloseReason) => void;
|
|
83
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|