@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,57 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// A row of a List: text, a link or a button. See ListItemProps for the props.
|
|
3
|
+
import './list.css';
|
|
4
|
+
import type { ListItemProps } from './types.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
title,
|
|
8
|
+
subtitle,
|
|
9
|
+
href,
|
|
10
|
+
target,
|
|
11
|
+
rel,
|
|
12
|
+
onclick,
|
|
13
|
+
active = false,
|
|
14
|
+
disabled = false,
|
|
15
|
+
prepend,
|
|
16
|
+
append,
|
|
17
|
+
children,
|
|
18
|
+
class: className,
|
|
19
|
+
ref = $bindable(null),
|
|
20
|
+
...rest
|
|
21
|
+
}: ListItemProps = $props();
|
|
22
|
+
|
|
23
|
+
const tag = $derived(href != null ? 'a' : onclick ? 'button' : 'div');
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<li
|
|
27
|
+
bind:this={ref}
|
|
28
|
+
{...rest}
|
|
29
|
+
class={[
|
|
30
|
+
'sv-list-item',
|
|
31
|
+
active && 'sv-list-item--active',
|
|
32
|
+
disabled && 'sv-list-item--disabled',
|
|
33
|
+
className
|
|
34
|
+
]}
|
|
35
|
+
>
|
|
36
|
+
<svelte:element
|
|
37
|
+
this={tag}
|
|
38
|
+
class="sv-list-item__main"
|
|
39
|
+
href={tag === 'a' && !disabled ? href : undefined}
|
|
40
|
+
target={tag === 'a' ? target : undefined}
|
|
41
|
+
rel={tag === 'a' ? rel : undefined}
|
|
42
|
+
type={tag === 'button' ? 'button' : undefined}
|
|
43
|
+
role={tag === 'a' && disabled ? 'link' : undefined}
|
|
44
|
+
disabled={(tag === 'button' && disabled) || undefined}
|
|
45
|
+
aria-disabled={(tag === 'a' && disabled) || undefined}
|
|
46
|
+
aria-current={tag === 'a' && active ? 'page' : undefined}
|
|
47
|
+
onclick={tag === 'button' ? onclick : undefined}
|
|
48
|
+
>
|
|
49
|
+
{#if prepend}<span class="sv-list-item__prepend">{@render prepend()}</span>{/if}
|
|
50
|
+
<span class="sv-list-item__text">
|
|
51
|
+
{#if title}<span class="sv-list-item__title">{title}</span>{/if}
|
|
52
|
+
{#if subtitle}<span class="sv-list-item__subtitle">{subtitle}</span>{/if}
|
|
53
|
+
{@render children?.()}
|
|
54
|
+
</span>
|
|
55
|
+
</svelte:element>
|
|
56
|
+
{#if append}<span class="sv-list-item__append">{@render append()}</span>{/if}
|
|
57
|
+
</li>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
@layer sv.tokens, sv.base, sv.components, sv.utilities;
|
|
2
|
+
|
|
3
|
+
@layer sv.components {
|
|
4
|
+
.sv-list {
|
|
5
|
+
display: grid;
|
|
6
|
+
margin: 0;
|
|
7
|
+
padding: calc(var(--sv-space-5) * var(--sv-density)) 0;
|
|
8
|
+
list-style: none;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.sv-list-item {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
min-inline-size: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* The row: a control tall, its text a φ step from what is before it. */
|
|
18
|
+
.sv-list-item__main {
|
|
19
|
+
position: relative;
|
|
20
|
+
isolation: isolate;
|
|
21
|
+
display: flex;
|
|
22
|
+
flex: 1;
|
|
23
|
+
gap: var(--sv-space-13);
|
|
24
|
+
align-items: center;
|
|
25
|
+
box-sizing: border-box;
|
|
26
|
+
min-inline-size: 0;
|
|
27
|
+
min-block-size: calc(var(--sv-control-md) * var(--sv-density));
|
|
28
|
+
margin: 0;
|
|
29
|
+
padding: calc(var(--sv-space-5) * var(--sv-density))
|
|
30
|
+
calc(var(--sv-space-13) * var(--sv-density));
|
|
31
|
+
border: 0;
|
|
32
|
+
background: transparent;
|
|
33
|
+
color: inherit;
|
|
34
|
+
font: inherit;
|
|
35
|
+
font-size: var(--sv-text-sm);
|
|
36
|
+
line-height: var(--sv-leading-snug);
|
|
37
|
+
text-align: start;
|
|
38
|
+
text-decoration: none;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Rows that act share Button's state layer. */
|
|
42
|
+
:is(a, button).sv-list-item__main {
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
-webkit-tap-highlight-color: transparent;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
:is(a, button).sv-list-item__main::before {
|
|
48
|
+
content: '';
|
|
49
|
+
position: absolute;
|
|
50
|
+
inset: 0;
|
|
51
|
+
z-index: -1;
|
|
52
|
+
border-radius: inherit;
|
|
53
|
+
background-color: currentColor;
|
|
54
|
+
opacity: 0;
|
|
55
|
+
pointer-events: none;
|
|
56
|
+
transition: opacity var(--sv-duration-fast) var(--sv-ease);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@media (hover: hover) {
|
|
60
|
+
.sv-list-item:not(.sv-list-item--disabled) :is(a, button).sv-list-item__main:hover::before {
|
|
61
|
+
opacity: var(--sv-state-hover);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.sv-list-item:not(.sv-list-item--disabled) :is(a, button).sv-list-item__main:active::before {
|
|
66
|
+
opacity: var(--sv-state-press);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Navigation: rounded rows, set in from the edges. */
|
|
70
|
+
.sv-list--nav {
|
|
71
|
+
padding-inline: var(--sv-space-5);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.sv-list--nav .sv-list-item__main {
|
|
75
|
+
border-radius: var(--sv-radius-md);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.sv-list-item--active .sv-list-item__main {
|
|
79
|
+
background-color: color-mix(in oklch, var(--sv-color) var(--sv-tonal-wash), transparent);
|
|
80
|
+
color: color-mix(in oklch, var(--sv-color) var(--sv-tonal-text-mix), var(--sv-color-text));
|
|
81
|
+
font-weight: var(--sv-weight-medium);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.sv-list-item--disabled {
|
|
85
|
+
opacity: 0.382;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.sv-list-item--disabled .sv-list-item__main {
|
|
89
|
+
cursor: not-allowed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.sv-list-item__prepend,
|
|
93
|
+
.sv-list-item__append {
|
|
94
|
+
display: flex;
|
|
95
|
+
flex: none;
|
|
96
|
+
align-items: center;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.sv-list-item__append {
|
|
100
|
+
padding-inline-end: calc(var(--sv-space-13) * var(--sv-density));
|
|
101
|
+
font-size: var(--sv-text-sm);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
:where(.sv-list-item__prepend, .sv-list-item__append) svg {
|
|
105
|
+
inline-size: 1.272em;
|
|
106
|
+
block-size: 1.272em;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.sv-list-item__text {
|
|
110
|
+
display: grid;
|
|
111
|
+
flex: 1;
|
|
112
|
+
gap: var(--sv-space-2);
|
|
113
|
+
min-inline-size: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.sv-list-item__title {
|
|
117
|
+
overflow: hidden;
|
|
118
|
+
text-overflow: ellipsis;
|
|
119
|
+
white-space: nowrap;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.sv-list-item__subtitle {
|
|
123
|
+
color: var(--sv-color-text-subtle);
|
|
124
|
+
font-size: var(--sv-text-xs);
|
|
125
|
+
font-weight: var(--sv-weight-regular);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAnchorAttributes, HTMLAttributes, HTMLLiAttributes } from 'svelte/elements';
|
|
3
|
+
import type { ElementProps } from '../../core/props/element.js';
|
|
4
|
+
import type { Color, Density } from '../../core/props/types.js';
|
|
5
|
+
/** The List props that `DefaultsProvider` and `UIProvider` can set for a subtree. */
|
|
6
|
+
export interface ListDefaults {
|
|
7
|
+
/**
|
|
8
|
+
* Multiplies the height and padding of its items.
|
|
9
|
+
* @default 'default'
|
|
10
|
+
*/
|
|
11
|
+
density: Density;
|
|
12
|
+
/**
|
|
13
|
+
* The colour of the active item.
|
|
14
|
+
* @default 'primary'
|
|
15
|
+
*/
|
|
16
|
+
color: Color;
|
|
17
|
+
/**
|
|
18
|
+
* Rounded rows set in from the edges, for navigation.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
nav: boolean;
|
|
22
|
+
}
|
|
23
|
+
declare module '../../core/defaults/context.js' {
|
|
24
|
+
interface ComponentDefaults {
|
|
25
|
+
List: ListDefaults;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** A list of rows: `ListItem`s. Density and colour are set here, for every item. */
|
|
29
|
+
export type ListProps = ElementProps<HTMLUListElement, HTMLAttributes<HTMLUListElement>, Partial<ListDefaults> & {
|
|
30
|
+
/** The `ListItem`s. */
|
|
31
|
+
children?: Snippet;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* A row of a `List`. With `href` it is a link and with `onclick` a button, filling the row;
|
|
35
|
+
* `append` stays outside it, so it can hold controls of its own.
|
|
36
|
+
*/
|
|
37
|
+
export type ListItemProps = ElementProps<HTMLLIElement, HTMLLiAttributes, {
|
|
38
|
+
/** The main line. */
|
|
39
|
+
title?: string;
|
|
40
|
+
/** A second line, under the title. */
|
|
41
|
+
subtitle?: string;
|
|
42
|
+
/** Makes the row a link. */
|
|
43
|
+
href?: string;
|
|
44
|
+
/** Where a link opens, as on `<a>`. */
|
|
45
|
+
target?: HTMLAnchorAttributes['target'];
|
|
46
|
+
/** The relationship of a link, as on `<a>`. */
|
|
47
|
+
rel?: string;
|
|
48
|
+
/** Makes the row a button that calls this. */
|
|
49
|
+
onclick?: (event: MouseEvent) => void;
|
|
50
|
+
/**
|
|
51
|
+
* The current page or the selected row: tinted in the list's colour, and
|
|
52
|
+
* `aria-current="page"` on a link.
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
55
|
+
active?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* A disabled row cannot be pressed; a disabled link loses its `href`.
|
|
58
|
+
* @default false
|
|
59
|
+
*/
|
|
60
|
+
disabled?: boolean;
|
|
61
|
+
/** Content before the text, such as an avatar or an icon. */
|
|
62
|
+
prepend?: Snippet;
|
|
63
|
+
/** Content after the row's link or button, such as a value or a menu button. */
|
|
64
|
+
append?: Snippet;
|
|
65
|
+
/** More content under the title and subtitle. */
|
|
66
|
+
children?: Snippet;
|
|
67
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// A list of actions that opens from a control. See MenuProps.
|
|
3
|
+
import { createAttachmentKey, type Attachment } from 'svelte/attachments';
|
|
4
|
+
import { checkValue, DEV } 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 { rovingFocus } from '../../primitives/focus/roving-focus.js';
|
|
10
|
+
import { typeahead } from '../../primitives/keyboard/typeahead.js';
|
|
11
|
+
import { dismissable } from '../../primitives/layer/dismissable.js';
|
|
12
|
+
import { pop } from '../../primitives/motion/motion.js';
|
|
13
|
+
import './menu.css';
|
|
14
|
+
import type { MenuAction, MenuDefaults, MenuProps } from './types.js';
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
density,
|
|
18
|
+
items,
|
|
19
|
+
onselect,
|
|
20
|
+
trigger,
|
|
21
|
+
open = $bindable(false),
|
|
22
|
+
side = 'bottom',
|
|
23
|
+
align = 'start',
|
|
24
|
+
onclose,
|
|
25
|
+
id: idProp,
|
|
26
|
+
class: className,
|
|
27
|
+
ref = $bindable(null),
|
|
28
|
+
...rest
|
|
29
|
+
}: MenuProps = $props();
|
|
30
|
+
|
|
31
|
+
const resolve = useDefaults<MenuDefaults>('Menu', { density: 'default' });
|
|
32
|
+
const p = $derived(resolve({ density }));
|
|
33
|
+
|
|
34
|
+
if (DEV) {
|
|
35
|
+
$effect(() => checkValue('Menu', 'side', side, SIDES));
|
|
36
|
+
$effect(() => checkValue('Menu', 'align', align, ALIGNS));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const uid = $props.id();
|
|
40
|
+
const id = $derived(idProp ?? uid);
|
|
41
|
+
|
|
42
|
+
// Runs of actions. A heading starts a named group, which a separator or the next heading ends.
|
|
43
|
+
interface Group {
|
|
44
|
+
heading?: string;
|
|
45
|
+
actions: MenuAction[];
|
|
46
|
+
}
|
|
47
|
+
const blocks = $derived.by(() => {
|
|
48
|
+
const list: Array<Group | 'separator'> = [];
|
|
49
|
+
let group: Group | undefined;
|
|
50
|
+
for (const item of items) {
|
|
51
|
+
if ('separator' in item) {
|
|
52
|
+
list.push('separator');
|
|
53
|
+
group = undefined;
|
|
54
|
+
} else if ('heading' in item) {
|
|
55
|
+
group = { heading: item.heading, actions: [] };
|
|
56
|
+
list.push(group);
|
|
57
|
+
} else {
|
|
58
|
+
if (!group) list.push((group = { actions: [] }));
|
|
59
|
+
group.actions.push(item);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return list;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// The control that opens it: the menu is placed against it, named by it, and gives focus back.
|
|
66
|
+
let anchor = $state<HTMLElement | null>(null);
|
|
67
|
+
const capture: Attachment<HTMLElement> = (node) => {
|
|
68
|
+
node.id ||= `${uid}-trigger`;
|
|
69
|
+
anchor = node;
|
|
70
|
+
return () => {
|
|
71
|
+
if (anchor === node) anchor = null;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Which item takes focus as the menu opens: the last after ArrowUp, else the first.
|
|
76
|
+
let from: 'first' | 'last' = 'first';
|
|
77
|
+
function show(start: 'first' | 'last') {
|
|
78
|
+
from = start;
|
|
79
|
+
if (open && ref) focusEnd(ref);
|
|
80
|
+
open = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const ANCHOR = createAttachmentKey();
|
|
84
|
+
const triggerAttributes: TriggerAttributes = $derived({
|
|
85
|
+
'aria-haspopup': 'menu',
|
|
86
|
+
'aria-expanded': open,
|
|
87
|
+
'aria-controls': open ? id : undefined,
|
|
88
|
+
onclick: () => (open ? close('action') : show('first')),
|
|
89
|
+
onkeydown: (event) => {
|
|
90
|
+
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
show(event.key === 'ArrowUp' ? 'last' : 'first');
|
|
93
|
+
},
|
|
94
|
+
[ANCHOR]: capture
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
function close(reason: CloseReason) {
|
|
98
|
+
if (!open) return;
|
|
99
|
+
// Escape and a choice give focus back to the control; Tab, a press or focus elsewhere
|
|
100
|
+
// have already chosen where it goes.
|
|
101
|
+
if ((reason === 'escape' || reason === 'action') && ref?.contains(document.activeElement)) {
|
|
102
|
+
anchor?.focus({ preventScroll: true });
|
|
103
|
+
}
|
|
104
|
+
open = false;
|
|
105
|
+
onclose?.(reason);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function choose(item: MenuAction) {
|
|
109
|
+
close('action');
|
|
110
|
+
onselect?.(item);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function focusEnd(menu: HTMLElement) {
|
|
114
|
+
const enabled = menu.querySelectorAll<HTMLElement>('[data-sv-item]:not(:disabled)');
|
|
115
|
+
(from === 'last' ? enabled[enabled.length - 1] : enabled[0])?.focus({ preventScroll: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const enter: Attachment<HTMLElement> = (menu) => {
|
|
119
|
+
menu.showPopover();
|
|
120
|
+
focusEnd(menu);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Tab and Shift+Tab close the menu once focus has moved on: the menu turns inert as it fades,
|
|
124
|
+
// and the move must not start from there.
|
|
125
|
+
function tab(event: KeyboardEvent) {
|
|
126
|
+
if (event.key === 'Tab') setTimeout(() => close('focus'));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// The pointer and the keyboard share one highlight: the item under the pointer takes focus.
|
|
130
|
+
function follow(event: PointerEvent & { currentTarget: HTMLButtonElement }) {
|
|
131
|
+
if (document.activeElement !== event.currentTarget) {
|
|
132
|
+
event.currentTarget.focus({ preventScroll: true });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
{#snippet action(item: MenuAction)}
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
role="menuitem"
|
|
141
|
+
tabindex="-1"
|
|
142
|
+
disabled={item.disabled}
|
|
143
|
+
data-sv-item
|
|
144
|
+
data-sv-label={item.label}
|
|
145
|
+
class={['sv-menu__item', item.danger && 'sv-menu__item--danger']}
|
|
146
|
+
onclick={() => choose(item)}
|
|
147
|
+
onpointermove={follow}
|
|
148
|
+
>
|
|
149
|
+
{#if item.icon}
|
|
150
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d={item.icon} /></svg>
|
|
151
|
+
{/if}
|
|
152
|
+
<span class="sv-menu__label">{item.label}</span>
|
|
153
|
+
{#if item.shortcut}<kbd class="sv-menu__shortcut">{item.shortcut}</kbd>{/if}
|
|
154
|
+
</button>
|
|
155
|
+
{/snippet}
|
|
156
|
+
|
|
157
|
+
{@render trigger(triggerAttributes)}
|
|
158
|
+
|
|
159
|
+
{#if open}
|
|
160
|
+
<div
|
|
161
|
+
bind:this={ref}
|
|
162
|
+
aria-labelledby={anchor?.id}
|
|
163
|
+
tabindex="-1"
|
|
164
|
+
{...rest}
|
|
165
|
+
{id}
|
|
166
|
+
role="menu"
|
|
167
|
+
popover="manual"
|
|
168
|
+
class={['sv-menu', sharedClasses(p, 'Menu'), className]}
|
|
169
|
+
{@attach anchored({ anchor, side, align })}
|
|
170
|
+
{@attach dismissable({ onDismiss: close, inside: () => [anchor], focusOutside: true })}
|
|
171
|
+
{@attach rovingFocus({ orientation: 'vertical' })}
|
|
172
|
+
{@attach typeahead()}
|
|
173
|
+
{@attach enter}
|
|
174
|
+
onkeydown={tab}
|
|
175
|
+
transition:pop
|
|
176
|
+
>
|
|
177
|
+
{#each blocks as block, index (index)}
|
|
178
|
+
{#if block === 'separator'}
|
|
179
|
+
<div role="separator" class="sv-menu__separator"></div>
|
|
180
|
+
{:else}
|
|
181
|
+
<div
|
|
182
|
+
role={block.heading ? 'group' : 'none'}
|
|
183
|
+
aria-labelledby={block.heading ? `${id}-${index}` : undefined}
|
|
184
|
+
>
|
|
185
|
+
{#if block.heading}
|
|
186
|
+
<div role="presentation" id="{id}-{index}" class="sv-menu__heading">
|
|
187
|
+
{block.heading}
|
|
188
|
+
</div>
|
|
189
|
+
{/if}
|
|
190
|
+
{#each block.actions as item, i (i)}{@render action(item)}{/each}
|
|
191
|
+
</div>
|
|
192
|
+
{/if}
|
|
193
|
+
{/each}
|
|
194
|
+
</div>
|
|
195
|
+
{/if}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Menu } from './Menu.svelte';
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
@layer sv.tokens, sv.base, sv.components, sv.utilities;
|
|
2
|
+
|
|
3
|
+
@layer sv.components {
|
|
4
|
+
/* A raised surface of rows, as wide as its longest item within a short line. */
|
|
5
|
+
.sv-menu {
|
|
6
|
+
--sv-menu-pad: var(--sv-space-5);
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
min-inline-size: 13rem;
|
|
11
|
+
max-inline-size: min(21rem, 100vw - 2 * var(--sv-space-8));
|
|
12
|
+
max-block-size: calc(100dvh - 2 * var(--sv-space-8));
|
|
13
|
+
padding: var(--sv-menu-pad);
|
|
14
|
+
overflow: auto;
|
|
15
|
+
overscroll-behavior: contain;
|
|
16
|
+
border: var(--sv-hairline) solid var(--sv-color-border-soft);
|
|
17
|
+
border-radius: var(--sv-radius-lg);
|
|
18
|
+
outline: none;
|
|
19
|
+
background-color: var(--sv-color-surface);
|
|
20
|
+
box-shadow: var(--sv-shadow);
|
|
21
|
+
color: var(--sv-color-text);
|
|
22
|
+
font-family: var(--sv-font-ui);
|
|
23
|
+
font-size: var(--sv-text-sm);
|
|
24
|
+
line-height: var(--sv-leading-snug);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* A row a small control tall, rounded to sit inside the menu's corner. */
|
|
28
|
+
.sv-menu__item {
|
|
29
|
+
display: flex;
|
|
30
|
+
gap: var(--sv-space-13);
|
|
31
|
+
align-items: center;
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
inline-size: 100%;
|
|
34
|
+
min-block-size: calc(var(--sv-control-sm) * var(--sv-density));
|
|
35
|
+
margin: 0;
|
|
36
|
+
padding: calc(var(--sv-space-5) * var(--sv-density)) var(--sv-space-8);
|
|
37
|
+
border: 0;
|
|
38
|
+
border-radius: max(0px, var(--sv-radius-lg) - var(--sv-menu-pad));
|
|
39
|
+
outline: none;
|
|
40
|
+
background: transparent;
|
|
41
|
+
color: inherit;
|
|
42
|
+
font: inherit;
|
|
43
|
+
text-align: start;
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
-webkit-tap-highlight-color: transparent;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* The pointer focuses the item under it, so one highlight serves both. */
|
|
49
|
+
.sv-menu__item:focus {
|
|
50
|
+
background-color: color-mix(in oklch, currentColor var(--sv-state-press), transparent);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.sv-menu__item:disabled {
|
|
54
|
+
opacity: 0.382;
|
|
55
|
+
cursor: not-allowed;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.sv-menu__item--danger {
|
|
59
|
+
color: var(--sv-color-error);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.sv-menu__item svg {
|
|
63
|
+
flex: none;
|
|
64
|
+
inline-size: 1.272em;
|
|
65
|
+
block-size: 1.272em;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.sv-menu__label {
|
|
69
|
+
flex: 1;
|
|
70
|
+
min-inline-size: 0;
|
|
71
|
+
overflow-wrap: anywhere;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.sv-menu__shortcut {
|
|
75
|
+
margin-inline-start: auto;
|
|
76
|
+
color: var(--sv-color-text-muted);
|
|
77
|
+
font: inherit;
|
|
78
|
+
font-size: var(--sv-text-xs);
|
|
79
|
+
font-variant-numeric: tabular-nums;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.sv-menu__item--danger .sv-menu__shortcut {
|
|
83
|
+
color: inherit;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.sv-menu__separator {
|
|
87
|
+
flex: none;
|
|
88
|
+
block-size: var(--sv-hairline);
|
|
89
|
+
margin-block: var(--sv-menu-pad);
|
|
90
|
+
margin-inline: calc(-1 * var(--sv-menu-pad));
|
|
91
|
+
background-color: var(--sv-color-border-soft);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* The theme's label voice, muted, above the items it names. */
|
|
95
|
+
.sv-menu__heading {
|
|
96
|
+
padding: var(--sv-space-8) var(--sv-space-8) var(--sv-space-3);
|
|
97
|
+
color: var(--sv-color-text-muted);
|
|
98
|
+
font-size: var(--sv-label-size);
|
|
99
|
+
font-weight: var(--sv-weight-medium);
|
|
100
|
+
letter-spacing: var(--sv-label-tracking);
|
|
101
|
+
text-transform: var(--sv-label-transform);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { CloseReason, TriggerAttributes } from '../../core/overlay/types.js';
|
|
4
|
+
import type { ElementProps } from '../../core/props/element.js';
|
|
5
|
+
import type { Density } from '../../core/props/types.js';
|
|
6
|
+
import type { Align, Side } from '../../primitives/anchored/anchored.js';
|
|
7
|
+
/** The Menu props that `DefaultsProvider` and `UIProvider` can set for a subtree. */
|
|
8
|
+
export interface MenuDefaults {
|
|
9
|
+
/**
|
|
10
|
+
* Multiplies the height and padding of the items, never the text.
|
|
11
|
+
* @default 'default'
|
|
12
|
+
*/
|
|
13
|
+
density: Density;
|
|
14
|
+
}
|
|
15
|
+
declare module '../../core/defaults/context.js' {
|
|
16
|
+
interface ComponentDefaults {
|
|
17
|
+
Menu: MenuDefaults;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** An item the user can choose. */
|
|
21
|
+
export interface MenuAction {
|
|
22
|
+
/** What the item says; typing its first letters moves to it. */
|
|
23
|
+
label: string;
|
|
24
|
+
/** An identifier for `onselect` to tell the items apart. */
|
|
25
|
+
value?: string;
|
|
26
|
+
/**
|
|
27
|
+
* An icon before the label: an SVG path on a 24×24 grid, as icon sets such as Material Design
|
|
28
|
+
* Icons export them (`mdiPencil`).
|
|
29
|
+
*/
|
|
30
|
+
icon?: string;
|
|
31
|
+
/** A keyboard shortcut shown at the end, such as `Ctrl+C`. Showing it does not bind it. */
|
|
32
|
+
shortcut?: string;
|
|
33
|
+
/** Shown but not chosen, and skipped by the arrow keys. */
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
/** In the error colour, for an action that deletes or cannot be undone. */
|
|
36
|
+
danger?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** A line between groups of items. */
|
|
39
|
+
export interface MenuSeparator {
|
|
40
|
+
separator: true;
|
|
41
|
+
}
|
|
42
|
+
/** A heading that names the items after it, up to the next heading or separator. */
|
|
43
|
+
export interface MenuHeading {
|
|
44
|
+
heading: string;
|
|
45
|
+
}
|
|
46
|
+
/** An entry of `items`: an action, a separator or a heading. */
|
|
47
|
+
export type MenuItem = MenuAction | MenuSeparator | MenuHeading;
|
|
48
|
+
/**
|
|
49
|
+
* A list of actions that opens from a control, as the APG menu button pattern describes. Arrow
|
|
50
|
+
* keys move through the items and wrap, Home and End jump to the ends, typing moves to the item
|
|
51
|
+
* that starts with it, Enter and Space choose, Escape closes and gives focus back, and Tab closes
|
|
52
|
+
* and moves on. `class`, `style`, `ref` and other attributes go to the menu.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* <Menu
|
|
56
|
+
* items={[
|
|
57
|
+
* { label: 'Rename', value: 'rename', shortcut: 'F2' },
|
|
58
|
+
* { separator: true },
|
|
59
|
+
* { label: 'Delete', value: 'delete', danger: true }
|
|
60
|
+
* ]}
|
|
61
|
+
* onselect={(item) => run(item.value)}
|
|
62
|
+
* >
|
|
63
|
+
* {#snippet trigger(attributes)}<Button {...attributes}>Actions</Button>{/snippet}
|
|
64
|
+
* </Menu>
|
|
65
|
+
*/
|
|
66
|
+
export type MenuProps = ElementProps<HTMLDivElement, Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'popover' | 'role'>, Partial<MenuDefaults> & {
|
|
67
|
+
/** The entries, in order. */
|
|
68
|
+
items: readonly MenuItem[];
|
|
69
|
+
/** Called with the item the user chooses, after the menu has closed. */
|
|
70
|
+
onselect?: (item: MenuAction) => void;
|
|
71
|
+
/** The control that opens it. Spread the attributes it receives onto it. */
|
|
72
|
+
trigger: Snippet<[TriggerAttributes]>;
|
|
73
|
+
/**
|
|
74
|
+
* Whether it is open. Bindable; it sets this to `false` when it closes itself.
|
|
75
|
+
* @default false
|
|
76
|
+
*/
|
|
77
|
+
open?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* The side of the control it opens on, moved to the opposite side when there is no room.
|
|
80
|
+
* @default 'bottom'
|
|
81
|
+
*/
|
|
82
|
+
side?: Side;
|
|
83
|
+
/**
|
|
84
|
+
* Its alignment along that side.
|
|
85
|
+
* @default 'start'
|
|
86
|
+
*/
|
|
87
|
+
align?: Align;
|
|
88
|
+
/** Called when it closes itself, with the reason; `'action'` after a choice. */
|
|
89
|
+
onclose?: (reason: CloseReason) => void;
|
|
90
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|