@thomasfosterau/components 0.1.0 → 0.2.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/README.md +241 -56
- package/dist/canvas/VectorCanvas.svelte +89 -8
- package/dist/canvas/VectorCanvas.svelte.d.ts +19 -1
- package/dist/canvas/types.d.ts +1 -1
- package/dist/charts/types.d.ts +1 -1
- package/dist/citations/list-format.js +3 -1
- package/dist/data/ChatBubble.svelte +48 -0
- package/dist/data/ChatBubble.svelte.d.ts +33 -0
- package/dist/data/DescriptionList.svelte +9 -3
- package/dist/data/DescriptionList.svelte.d.ts +1 -1
- package/dist/data/DescriptionRow.svelte +6 -5
- package/dist/data/MessageThread.svelte +29 -0
- package/dist/data/MessageThread.svelte.d.ts +17 -0
- package/dist/data/Pagination.svelte +5 -5
- package/dist/data/Pagination.svelte.d.ts +8 -0
- package/dist/data/PaginationFooter.svelte +2 -1
- package/dist/data/PaginationFooter.svelte.d.ts +2 -0
- package/dist/data/Table.svelte +9 -3
- package/dist/data/description-context.d.ts +12 -6
- package/dist/data/description-context.js +6 -5
- package/dist/data/index.d.ts +2 -0
- package/dist/data/index.js +2 -0
- package/dist/data/table-types.d.ts +7 -0
- package/dist/feedback/Dialog.svelte +19 -49
- package/dist/feedback/Dialog.svelte.d.ts +0 -1
- package/dist/feedback/DialogFrame.svelte +64 -0
- package/dist/feedback/DialogFrame.svelte.d.ts +23 -0
- package/dist/feedback/Drawer.svelte +43 -0
- package/dist/feedback/Drawer.svelte.d.ts +43 -0
- package/dist/feedback/SegmentBar.svelte.d.ts +1 -0
- package/dist/feedback/StatTile.svelte.d.ts +1 -0
- package/dist/feedback/Steps.svelte +72 -0
- package/dist/feedback/Steps.svelte.d.ts +61 -0
- package/dist/feedback/Timeline.svelte.d.ts +3 -0
- package/dist/feedback/index.d.ts +3 -0
- package/dist/feedback/index.js +2 -0
- package/dist/feedback/sizes.d.ts +14 -0
- package/dist/feedback/sizes.js +28 -0
- package/dist/fields/Calendar.svelte +60 -0
- package/dist/fields/Calendar.svelte.d.ts +11 -0
- package/dist/fields/CalendarMonths.svelte +86 -0
- package/dist/fields/CalendarMonths.svelte.d.ts +30 -0
- package/dist/fields/Checkbox.svelte +1 -1
- package/dist/fields/Checkbox.svelte.d.ts +6 -0
- package/dist/fields/Combobox.svelte +61 -54
- package/dist/fields/Combobox.svelte.d.ts +38 -3
- package/dist/fields/DateField.svelte +37 -0
- package/dist/fields/DateField.svelte.d.ts +20 -0
- package/dist/fields/DateInput.svelte +1 -1
- package/dist/fields/DateInput.svelte.d.ts +6 -0
- package/dist/fields/DatePicker.svelte +47 -0
- package/dist/fields/DatePicker.svelte.d.ts +25 -0
- package/dist/fields/DateRangePicker.svelte +70 -0
- package/dist/fields/DateRangePicker.svelte.d.ts +43 -0
- package/dist/fields/Dropzone.svelte +232 -0
- package/dist/fields/Dropzone.svelte.d.ts +103 -0
- package/dist/fields/Fieldset.svelte +7 -2
- package/dist/fields/Fieldset.svelte.d.ts +3 -2
- package/dist/fields/FileInput.svelte +3 -2
- package/dist/fields/FileInput.svelte.d.ts +6 -0
- package/dist/fields/FormField.svelte +27 -3
- package/dist/fields/ImageInput.svelte.d.ts +6 -0
- package/dist/fields/MultiSelect.svelte +112 -0
- package/dist/fields/MultiSelect.svelte.d.ts +75 -0
- package/dist/fields/NumberField.svelte +190 -0
- package/dist/fields/NumberField.svelte.d.ts +76 -0
- package/dist/fields/PasswordInput.svelte +95 -0
- package/dist/fields/PasswordInput.svelte.d.ts +58 -0
- package/dist/fields/PickerPopover.svelte +47 -0
- package/dist/fields/PickerPopover.svelte.d.ts +16 -0
- package/dist/fields/PinInput.svelte +83 -0
- package/dist/fields/PinInput.svelte.d.ts +66 -0
- package/dist/fields/RadioGroup.svelte +3 -0
- package/dist/fields/RangeCalendar.svelte +51 -0
- package/dist/fields/RangeCalendar.svelte.d.ts +23 -0
- package/dist/fields/Rating.svelte +127 -0
- package/dist/fields/Rating.svelte.d.ts +86 -0
- package/dist/fields/SearchInput.svelte +2 -1
- package/dist/fields/SegmentGroup.svelte +91 -0
- package/dist/fields/SegmentGroup.svelte.d.ts +20 -0
- package/dist/fields/Select.svelte +1 -1
- package/dist/fields/Select.svelte.d.ts +6 -0
- package/dist/fields/Slider.svelte +194 -0
- package/dist/fields/Slider.svelte.d.ts +100 -0
- package/dist/fields/TagsInput.svelte +256 -0
- package/dist/fields/TagsInput.svelte.d.ts +102 -0
- package/dist/fields/TextInput.svelte +1 -1
- package/dist/fields/TextInput.svelte.d.ts +6 -0
- package/dist/fields/Textarea.svelte +1 -1
- package/dist/fields/Textarea.svelte.d.ts +6 -0
- package/dist/fields/TimeField.svelte +30 -0
- package/dist/fields/TimeField.svelte.d.ts +19 -0
- package/dist/fields/Toggle.svelte +1 -1
- package/dist/fields/Toggle.svelte.d.ts +6 -0
- package/dist/fields/VideoInput.svelte.d.ts +6 -0
- package/dist/fields/calendar-props.d.ts +119 -0
- package/dist/fields/calendar-props.js +51 -0
- package/dist/fields/dates.d.ts +28 -0
- package/dist/fields/dates.js +26 -0
- package/dist/fields/field-types.d.ts +29 -0
- package/dist/fields/field-types.js +32 -0
- package/dist/fields/file-validation.d.ts +30 -0
- package/dist/fields/file-validation.js +51 -0
- package/dist/fields/index.d.ts +9 -0
- package/dist/fields/index.js +8 -0
- package/dist/fields/latest-search.svelte.d.ts +38 -0
- package/dist/fields/latest-search.svelte.js +85 -0
- package/dist/fields/number-format.d.ts +52 -0
- package/dist/fields/number-format.js +89 -0
- package/dist/fields/segmented-field.d.ts +128 -0
- package/dist/fields/segmented-field.js +52 -0
- package/dist/fields/slider-keys.d.ts +21 -0
- package/dist/fields/slider-keys.js +37 -0
- package/dist/formatting/Countdown.svelte +110 -0
- package/dist/formatting/Countdown.svelte.d.ts +35 -0
- package/dist/formatting/Duration.svelte +18 -0
- package/dist/formatting/Duration.svelte.d.ts +20 -0
- package/dist/formatting/Highlight.svelte +4 -0
- package/dist/formatting/Highlight.svelte.d.ts +4 -0
- package/dist/formatting/duration.d.ts +44 -0
- package/dist/formatting/duration.js +125 -0
- package/dist/formatting/index.d.ts +3 -0
- package/dist/formatting/index.js +3 -0
- package/dist/forms/EntityForm.svelte +53 -45
- package/dist/forms/EntityForm.svelte.d.ts +12 -3
- package/dist/hotkeys/index.d.ts +5 -0
- package/dist/hotkeys/index.js +5 -0
- package/dist/hotkeys/match.js +12 -10
- package/dist/hotkeys/types.d.ts +4 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/infobox/Infobox.svelte +2 -2
- package/dist/infobox/Infobox.svelte.d.ts +7 -0
- package/dist/infobox/InfoboxHeader.svelte +15 -7
- package/dist/infobox/InfoboxHeader.svelte.d.ts +7 -0
- package/dist/infobox/types.d.ts +5 -5
- package/dist/layout/AppShell.svelte +50 -0
- package/dist/layout/AppShell.svelte.d.ts +28 -0
- package/dist/layout/Cluster.svelte +28 -0
- package/dist/layout/Cluster.svelte.d.ts +27 -0
- package/dist/layout/Fits.svelte +27 -0
- package/dist/layout/Fits.svelte.d.ts +13 -0
- package/dist/layout/Flow.svelte +46 -0
- package/dist/layout/Flow.svelte.d.ts +16 -0
- package/dist/layout/Grid.svelte +23 -0
- package/dist/layout/Grid.svelte.d.ts +26 -0
- package/dist/layout/HStack.svelte +12 -0
- package/dist/layout/HStack.svelte.d.ts +34 -0
- package/dist/layout/Hero.svelte +76 -0
- package/dist/layout/Hero.svelte.d.ts +36 -0
- package/dist/layout/Navbar.svelte +2 -2
- package/dist/layout/Sidebar.svelte +1 -1
- package/dist/layout/Spacer.svelte +12 -0
- package/dist/layout/Spacer.svelte.d.ts +6 -0
- package/dist/layout/VStack.svelte +12 -0
- package/dist/layout/VStack.svelte.d.ts +29 -0
- package/dist/layout/index.d.ts +9 -0
- package/dist/layout/index.js +9 -0
- package/dist/layout/space.d.ts +44 -0
- package/dist/layout/space.js +76 -0
- package/dist/search/types.d.ts +3 -0
- package/dist/seo/og-image.d.ts +4 -3
- package/dist/seo/og-image.js +4 -3
- package/dist/tips/tip-stack.d.ts +1 -0
- package/dist/tips/tip-store.svelte.d.ts +2 -1
- package/dist/ui/Accordion.svelte +75 -0
- package/dist/ui/Accordion.svelte.d.ts +50 -0
- package/dist/ui/AccordionItem.svelte +73 -0
- package/dist/ui/AccordionItem.svelte.d.ts +18 -0
- package/dist/ui/Button.svelte +3 -2
- package/dist/ui/ButtonGroup.svelte +24 -0
- package/dist/ui/ButtonGroup.svelte.d.ts +17 -0
- package/dist/ui/ContextMenu.svelte +75 -0
- package/dist/ui/ContextMenu.svelte.d.ts +25 -0
- package/dist/ui/Disclosure.svelte +7 -0
- package/dist/ui/Disclosure.svelte.d.ts +2 -2
- package/dist/ui/Divider.svelte +47 -0
- package/dist/ui/Divider.svelte.d.ts +28 -0
- package/dist/ui/DropdownMenu.svelte +44 -0
- package/dist/ui/DropdownMenu.svelte.d.ts +30 -0
- package/dist/ui/Menu.svelte +17 -47
- package/dist/ui/Menu.svelte.d.ts +3 -24
- package/dist/ui/MenuCheckboxItem.svelte +33 -0
- package/dist/ui/MenuCheckboxItem.svelte.d.ts +20 -0
- package/dist/ui/MenuItem.svelte +29 -31
- package/dist/ui/MenuItem.svelte.d.ts +2 -2
- package/dist/ui/MenuRadioGroup.svelte +25 -0
- package/dist/ui/MenuRadioGroup.svelte.d.ts +20 -0
- package/dist/ui/MenuRadioItem.svelte +20 -0
- package/dist/ui/MenuRadioItem.svelte.d.ts +15 -0
- package/dist/ui/MenuSeparator.svelte +12 -11
- package/dist/ui/MenuSub.svelte +24 -0
- package/dist/ui/MenuSub.svelte.d.ts +15 -0
- package/dist/ui/Spinner.svelte +41 -0
- package/dist/ui/Spinner.svelte.d.ts +35 -0
- package/dist/ui/Stack.svelte +36 -0
- package/dist/ui/Stack.svelte.d.ts +48 -0
- package/dist/ui/ToggleButton.svelte +58 -0
- package/dist/ui/ToggleButton.svelte.d.ts +45 -0
- package/dist/ui/ToggleGroup.svelte +142 -0
- package/dist/ui/ToggleGroup.svelte.d.ts +86 -0
- package/dist/ui/Toolbar.svelte +40 -0
- package/dist/ui/Toolbar.svelte.d.ts +20 -0
- package/dist/ui/ToolbarButton.svelte +29 -0
- package/dist/ui/ToolbarButton.svelte.d.ts +21 -0
- package/dist/ui/ToolbarLink.svelte +21 -0
- package/dist/ui/ToolbarLink.svelte.d.ts +12 -0
- package/dist/ui/ToolbarSeparator.svelte +22 -0
- package/dist/ui/ToolbarSeparator.svelte.d.ts +12 -0
- package/dist/ui/ToolbarToggleGroup.svelte +66 -0
- package/dist/ui/ToolbarToggleGroup.svelte.d.ts +52 -0
- package/dist/ui/accordion-context.d.ts +26 -0
- package/dist/ui/accordion-context.js +23 -0
- package/dist/ui/context-menu.d.ts +13 -0
- package/dist/ui/context-menu.js +13 -0
- package/dist/ui/index.d.ts +19 -0
- package/dist/ui/index.js +18 -0
- package/dist/ui/menu-context.d.ts +13 -15
- package/dist/ui/menu-context.js +14 -18
- package/dist/ui/menu-parts/CheckboxItemPart.svelte +26 -0
- package/dist/ui/menu-parts/CheckboxItemPart.svelte.d.ts +5 -0
- package/dist/ui/menu-parts/ItemPart.svelte +29 -0
- package/dist/ui/menu-parts/ItemPart.svelte.d.ts +5 -0
- package/dist/ui/menu-parts/RadioGroupPart.svelte +24 -0
- package/dist/ui/menu-parts/RadioGroupPart.svelte.d.ts +5 -0
- package/dist/ui/menu-parts/RadioItemPart.svelte +22 -0
- package/dist/ui/menu-parts/RadioItemPart.svelte.d.ts +5 -0
- package/dist/ui/menu-parts/SeparatorPart.svelte +16 -0
- package/dist/ui/menu-parts/SeparatorPart.svelte.d.ts +5 -0
- package/dist/ui/menu-parts/SubPart.svelte +28 -0
- package/dist/ui/menu-parts/SubPart.svelte.d.ts +5 -0
- package/dist/ui/menu-parts/menu-parts.d.ts +104 -0
- package/dist/ui/menu-parts/menu-parts.js +31 -0
- package/dist/ui/menu-parts/parts.d.ts +12 -0
- package/dist/ui/menu-parts/parts.js +27 -0
- package/dist/ui/toggle-classes.d.ts +39 -0
- package/dist/ui/toggle-classes.js +36 -0
- package/dist/ui/tones.d.ts +35 -0
- package/dist/ui/tones.js +90 -0
- package/package.json +34 -18
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The library's spacing rhythm and the layout primitives' vocabulary, as
|
|
3
|
+
* literal class tables.
|
|
4
|
+
*
|
|
5
|
+
* `VStack`, `HStack`, `Cluster`, `Grid` and `Fits` take tokens, not class
|
|
6
|
+
* names, so every app spaces things on the same scale. Tailwind only
|
|
7
|
+
* generates classes it can see verbatim in source, so each step is spelled
|
|
8
|
+
* out once here and looked up (the `ui/tones.ts` discipline).
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
/** Literal `gap-*` class per `Gap`; a lookup table so Tailwind's scanner sees every class. */
|
|
13
|
+
export const gapClass = {
|
|
14
|
+
none: 'gap-0',
|
|
15
|
+
xs: 'gap-1',
|
|
16
|
+
sm: 'gap-2',
|
|
17
|
+
md: 'gap-4',
|
|
18
|
+
lg: 'gap-8',
|
|
19
|
+
xl: 'gap-12',
|
|
20
|
+
};
|
|
21
|
+
/** Literal `items-*` class per `FlowAlign`; a lookup table so Tailwind's scanner sees every class. */
|
|
22
|
+
export const alignClass = {
|
|
23
|
+
start: 'items-start',
|
|
24
|
+
center: 'items-center',
|
|
25
|
+
end: 'items-end',
|
|
26
|
+
stretch: 'items-stretch',
|
|
27
|
+
baseline: 'items-baseline',
|
|
28
|
+
};
|
|
29
|
+
/** Literal `justify-*` class per `FlowJustify`; a lookup table so Tailwind's scanner sees every class. */
|
|
30
|
+
export const justifyClass = {
|
|
31
|
+
start: 'justify-start',
|
|
32
|
+
center: 'justify-center',
|
|
33
|
+
end: 'justify-end',
|
|
34
|
+
between: 'justify-between',
|
|
35
|
+
};
|
|
36
|
+
/** Literal auto-fill column template per `MinItem`; a lookup table so Tailwind's scanner sees every class. */
|
|
37
|
+
export const minItemClass = {
|
|
38
|
+
xs: 'grid-cols-[repeat(auto-fill,minmax(min(10rem,100%),1fr))]',
|
|
39
|
+
sm: 'grid-cols-[repeat(auto-fill,minmax(min(12rem,100%),1fr))]',
|
|
40
|
+
md: 'grid-cols-[repeat(auto-fill,minmax(min(16rem,100%),1fr))]',
|
|
41
|
+
lg: 'grid-cols-[repeat(auto-fill,minmax(min(20rem,100%),1fr))]',
|
|
42
|
+
};
|
|
43
|
+
/** Literal container-query class showing a `Fits` layout from this width up. */
|
|
44
|
+
export const fitsShowFrom = {
|
|
45
|
+
'20rem': '@min-[20rem]:block',
|
|
46
|
+
'28rem': '@min-[28rem]:block',
|
|
47
|
+
'36rem': '@min-[36rem]:block',
|
|
48
|
+
'48rem': '@min-[48rem]:block',
|
|
49
|
+
'64rem': '@min-[64rem]:block',
|
|
50
|
+
};
|
|
51
|
+
/** Literal container-query class hiding a `Fits` layout from this width up. */
|
|
52
|
+
export const fitsHideFrom = {
|
|
53
|
+
'20rem': '@min-[20rem]:hidden',
|
|
54
|
+
'28rem': '@min-[28rem]:hidden',
|
|
55
|
+
'36rem': '@min-[36rem]:hidden',
|
|
56
|
+
'48rem': '@min-[48rem]:hidden',
|
|
57
|
+
'64rem': '@min-[64rem]:hidden',
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* The classes of `Fits` layout `index` of `count`, given the widths at which
|
|
61
|
+
* each stops fitting (`breakpoints[i]` for layout `i`, one fewer than the
|
|
62
|
+
* layouts). The first shows from `breakpoints[0]` up, each next one between
|
|
63
|
+
* its own breakpoint and the previous one, and the last below the final
|
|
64
|
+
* breakpoint. Outside its band a layout is `display: none`, so it is out of
|
|
65
|
+
* the accessibility tree.
|
|
66
|
+
*/
|
|
67
|
+
export function fitsLayoutClass(index, count, breakpoints) {
|
|
68
|
+
const own = breakpoints[index];
|
|
69
|
+
const previous = index === 0 ? undefined : breakpoints[index - 1];
|
|
70
|
+
const isLast = index === count - 1;
|
|
71
|
+
return [
|
|
72
|
+
isLast ? 'block' : 'hidden',
|
|
73
|
+
!isLast && own !== undefined ? fitsShowFrom[own] : '',
|
|
74
|
+
previous === undefined ? '' : fitsHideFrom[previous],
|
|
75
|
+
].filter((name) => name !== '');
|
|
76
|
+
}
|
package/dist/search/types.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
|
+
/** One row of the command palette: an id, a label, the group it sits in, and what activating it does. */
|
|
12
13
|
export interface CommandItem {
|
|
13
14
|
/** Stable id. Unique within its group; it is what bits-ui ranks and selects on. */
|
|
14
15
|
id: string;
|
|
@@ -27,7 +28,9 @@ export interface CommandItem {
|
|
|
27
28
|
}
|
|
28
29
|
/** A palette section: an id rows point at, and the heading it renders. */
|
|
29
30
|
export interface CommandGroup {
|
|
31
|
+
/** What a `CommandItem`'s `group` names. */
|
|
30
32
|
id: string;
|
|
33
|
+
/** The section heading. */
|
|
31
34
|
label: string;
|
|
32
35
|
}
|
|
33
36
|
/**
|
package/dist/seo/og-image.d.ts
CHANGED
|
@@ -22,13 +22,14 @@ export declare const OG_SUBTITLE_PARAM = "d";
|
|
|
22
22
|
/** Query param carrying the cache-busting version token. */
|
|
23
23
|
export declare const OG_VERSION_PARAM = "v";
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
* should re-apply
|
|
25
|
+
* Longest title, in characters, carried in a share-card URL. A renderer
|
|
26
|
+
* should re-apply this cap (and `OG_SUBTITLE_MAX_LENGTH`) defensively; here
|
|
27
|
+
* they keep the emitted URL short.
|
|
27
28
|
*/
|
|
28
29
|
export declare const OG_TITLE_MAX_LENGTH = 120;
|
|
29
30
|
/** Longest description, in characters, carried in a share-card URL; see `OG_TITLE_MAX_LENGTH`. */
|
|
30
31
|
export declare const OG_SUBTITLE_MAX_LENGTH = 200;
|
|
31
|
-
/**
|
|
32
|
+
/** Share-card width in pixels — the 1.91:1 Open Graph standard, with `OG_IMAGE_HEIGHT`. */
|
|
32
33
|
export declare const OG_IMAGE_WIDTH = 1200;
|
|
33
34
|
/** Share-card height in pixels; see `OG_IMAGE_WIDTH`. */
|
|
34
35
|
export declare const OG_IMAGE_HEIGHT = 630;
|
package/dist/seo/og-image.js
CHANGED
|
@@ -22,13 +22,14 @@ export const OG_SUBTITLE_PARAM = 'd';
|
|
|
22
22
|
/** Query param carrying the cache-busting version token. */
|
|
23
23
|
export const OG_VERSION_PARAM = 'v';
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
* should re-apply
|
|
25
|
+
* Longest title, in characters, carried in a share-card URL. A renderer
|
|
26
|
+
* should re-apply this cap (and `OG_SUBTITLE_MAX_LENGTH`) defensively; here
|
|
27
|
+
* they keep the emitted URL short.
|
|
27
28
|
*/
|
|
28
29
|
export const OG_TITLE_MAX_LENGTH = 120;
|
|
29
30
|
/** Longest description, in characters, carried in a share-card URL; see `OG_TITLE_MAX_LENGTH`. */
|
|
30
31
|
export const OG_SUBTITLE_MAX_LENGTH = 200;
|
|
31
|
-
/**
|
|
32
|
+
/** Share-card width in pixels — the 1.91:1 Open Graph standard, with `OG_IMAGE_HEIGHT`. */
|
|
32
33
|
export const OG_IMAGE_WIDTH = 1200;
|
|
33
34
|
/** Share-card height in pixels; see `OG_IMAGE_WIDTH`. */
|
|
34
35
|
export const OG_IMAGE_HEIGHT = 630;
|
package/dist/tips/tip-stack.d.ts
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { TipConfig, TipState } from './types.ts';
|
|
18
18
|
/**
|
|
19
|
-
* The subset of `Storage`
|
|
19
|
+
* The subset of the DOM `Storage` interface a tip store reads and writes, so a
|
|
20
|
+
* test can pass a plain object.
|
|
20
21
|
*
|
|
21
22
|
* The signatures are the DOM `Storage` interface's own, `null` included: this
|
|
22
23
|
* type exists so `globalThis.localStorage` satisfies it structurally.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A stack of headed, collapsible sections on bits-ui `Accordion`, with single- or multi-open behaviour.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Accordion — a vertical stack of `AccordionItem`s, each a real heading whose
|
|
6
|
+
button expands its panel. `type="single"` keeps at most one open (an FAQ, a
|
|
7
|
+
care guide by season); `type="multiple"` lets any number open.
|
|
8
|
+
|
|
9
|
+
Why not a stack of `Disclosure`s: `<summary>` has an implicit button role
|
|
10
|
+
whose children are presentational, so a heading inside it drops out of the
|
|
11
|
+
heading tree, and native `<details name>` groups have no "which one is
|
|
12
|
+
open" value and no "keep one open" rule. A single `Disclosure` stays
|
|
13
|
+
native; this is for the heading-structured, coordinated case.
|
|
14
|
+
|
|
15
|
+
ARIA and keyboard contract (APG Accordion): bits-ui gives each trigger
|
|
16
|
+
`aria-expanded`, Enter / Space to toggle, ↑ / ↓ and Home / End between
|
|
17
|
+
headers. We render each header as a native `<h{headingLevel}>`, wire
|
|
18
|
+
`aria-controls` from the button to its panel, and make each panel a
|
|
19
|
+
`region` labelled by its button. With `required`, the open item's button
|
|
20
|
+
reports `aria-disabled="true"`, so a reader isn't offered an action that
|
|
21
|
+
does nothing. With `searchable` (the default) a closed panel keeps
|
|
22
|
+
`hidden="until-found"`, so find-in-page still reaches it and opens it, the
|
|
23
|
+
one advantage `<details>` had.
|
|
24
|
+
|
|
25
|
+
`headingLevel`, `variant`, `joined` and `searchable` reach the items
|
|
26
|
+
through context (`accordion-context.ts`), so they cannot drift per item.
|
|
27
|
+
-->
|
|
28
|
+
<script lang="ts">import { Accordion } from "bits-ui";
|
|
29
|
+
import { setAccordionContext } from "./accordion-context.js";
|
|
30
|
+
let { type, value = $bindable(), required = false, headingLevel = 3, variant = "arrow", joined = true, searchable = true, disabled = false, children, class: className, ...rest } = $props();
|
|
31
|
+
function openValues() {
|
|
32
|
+
if (value === undefined || value === "") return [];
|
|
33
|
+
return typeof value === "string" ? [value] : value;
|
|
34
|
+
}
|
|
35
|
+
setAccordionContext({
|
|
36
|
+
headingLevel: () => headingLevel,
|
|
37
|
+
variant: () => variant,
|
|
38
|
+
joined: () => joined,
|
|
39
|
+
searchable: () => searchable,
|
|
40
|
+
isOpen: (item) => openValues().includes(item),
|
|
41
|
+
isLocked: (item) => type === "single" && required && value === item
|
|
42
|
+
});
|
|
43
|
+
const rootClass = $derived([
|
|
44
|
+
"w-full",
|
|
45
|
+
joined && "join join-vertical",
|
|
46
|
+
className
|
|
47
|
+
]);
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
{#snippet root({ props }: { props: Record<string, unknown> })}
|
|
51
|
+
<div {...rest} {...props} class={rootClass}>{@render children()}</div>
|
|
52
|
+
{/snippet}
|
|
53
|
+
|
|
54
|
+
{#if type === 'single'}
|
|
55
|
+
<Accordion.Root
|
|
56
|
+
type="single"
|
|
57
|
+
{disabled}
|
|
58
|
+
bind:value={
|
|
59
|
+
() => (typeof value === 'string' ? value : ''),
|
|
60
|
+
(next) => {
|
|
61
|
+
// `required`: a change to '' is the open item closing; refuse it.
|
|
62
|
+
if (required && next === '') return;
|
|
63
|
+
value = next;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
child={root}
|
|
67
|
+
/>
|
|
68
|
+
{:else}
|
|
69
|
+
<Accordion.Root
|
|
70
|
+
type="multiple"
|
|
71
|
+
{disabled}
|
|
72
|
+
bind:value={() => [...openValues()], (next) => (value = next)}
|
|
73
|
+
child={root}
|
|
74
|
+
/>
|
|
75
|
+
{/if}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Accordion } from 'bits-ui';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import type { DisclosureVariant, HeadingLevel } from './tones.ts';
|
|
5
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'onchange'> & {
|
|
6
|
+
/**
|
|
7
|
+
* The level of each item's heading.
|
|
8
|
+
* @defaultValue `3`
|
|
9
|
+
*/
|
|
10
|
+
headingLevel?: HeadingLevel | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* The expand marker, shared with `Disclosure`.
|
|
13
|
+
* @defaultValue `'arrow'`
|
|
14
|
+
*/
|
|
15
|
+
variant?: DisclosureVariant | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Join the items into one bordered block (daisyUI `join join-vertical`).
|
|
18
|
+
* @defaultValue `true`
|
|
19
|
+
*/
|
|
20
|
+
joined?: boolean | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Keep closed panels reachable by find-in-page (`hidden="until-found"`).
|
|
23
|
+
* @defaultValue `true`
|
|
24
|
+
*/
|
|
25
|
+
searchable?: boolean | undefined;
|
|
26
|
+
/** Disable every item. */
|
|
27
|
+
disabled?: boolean | undefined;
|
|
28
|
+
/** The `AccordionItem`s. */
|
|
29
|
+
children: Snippet<[]>;
|
|
30
|
+
} & ({
|
|
31
|
+
/** At most one item open. */
|
|
32
|
+
type: 'single';
|
|
33
|
+
/** The open item's value, `''` for none. Bindable. */
|
|
34
|
+
value?: string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Refuse to close the open item, so exactly one stays open once one is.
|
|
37
|
+
* @defaultValue `false`
|
|
38
|
+
*/
|
|
39
|
+
required?: boolean | undefined;
|
|
40
|
+
} | {
|
|
41
|
+
/** Any number of items open. */
|
|
42
|
+
type: 'multiple';
|
|
43
|
+
/** The open items' values. Bindable. */
|
|
44
|
+
value?: readonly string[] | undefined;
|
|
45
|
+
required?: undefined;
|
|
46
|
+
});
|
|
47
|
+
/** A stack of headed, collapsible sections on bits-ui `Accordion`, with single- or multi-open behaviour. */
|
|
48
|
+
declare const Accordion: import("svelte").Component<Props, {}, "value">;
|
|
49
|
+
type Accordion = ReturnType<typeof Accordion>;
|
|
50
|
+
export default Accordion;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
One headed, collapsible section of an `Accordion`.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
AccordionItem — a heading whose button expands a panel. Must sit inside an
|
|
6
|
+
`Accordion`, which supplies the heading level, the marker and the open
|
|
7
|
+
state (see `accordion-context.ts`).
|
|
8
|
+
|
|
9
|
+
The header is a native `<h{level}>` rendered through bits-ui's `child`
|
|
10
|
+
snippet, so the redundant `role="heading"` / `aria-level` bits-ui would put
|
|
11
|
+
on a `<div>` are dropped. The daisyUI marker is the heading's `::after`,
|
|
12
|
+
outside the button, so it never joins the button's accessible name.
|
|
13
|
+
-->
|
|
14
|
+
<script lang="ts">import { Accordion } from "bits-ui";
|
|
15
|
+
import { getAccordionContext } from "./accordion-context.js";
|
|
16
|
+
import { collapseVariant } from "./tones.js";
|
|
17
|
+
const uid = $props.id();
|
|
18
|
+
let { value = uid, title, disabled, contentClass, children, class: className, ...rest } = $props();
|
|
19
|
+
const accordion = getAccordionContext();
|
|
20
|
+
const triggerId = `${uid}-trigger`;
|
|
21
|
+
const contentId = `${uid}-content`;
|
|
22
|
+
const open = $derived(accordion.isOpen(value));
|
|
23
|
+
const locked = $derived(accordion.isLocked(value));
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<Accordion.Item {value} {...disabled === undefined ? {} : { disabled }}>
|
|
27
|
+
{#snippet child({ props: itemProps })}
|
|
28
|
+
<div
|
|
29
|
+
{...rest}
|
|
30
|
+
{...itemProps}
|
|
31
|
+
class={[
|
|
32
|
+
'collapse border-base-300 bg-base-100 border',
|
|
33
|
+
open ? 'collapse-open' : 'collapse-close',
|
|
34
|
+
collapseVariant[accordion.variant()],
|
|
35
|
+
accordion.joined() && 'join-item',
|
|
36
|
+
className,
|
|
37
|
+
]}
|
|
38
|
+
>
|
|
39
|
+
<Accordion.Header level={accordion.headingLevel()}>
|
|
40
|
+
{#snippet child({ props })}
|
|
41
|
+
{@const { role: _role, 'aria-level': _level, ...headerProps } = props}
|
|
42
|
+
<svelte:element
|
|
43
|
+
this={`h${accordion.headingLevel()}`}
|
|
44
|
+
{...headerProps}
|
|
45
|
+
class="collapse-title p-0 text-base font-medium"
|
|
46
|
+
>
|
|
47
|
+
<Accordion.Trigger id={triggerId}>
|
|
48
|
+
{#snippet child({ props: triggerProps })}
|
|
49
|
+
<button
|
|
50
|
+
{...triggerProps}
|
|
51
|
+
aria-controls={contentId}
|
|
52
|
+
{...locked ? { 'aria-disabled': 'true' } : {}}
|
|
53
|
+
class="w-full cursor-pointer px-4 py-4 pe-12 text-start disabled:cursor-not-allowed disabled:opacity-60"
|
|
54
|
+
>
|
|
55
|
+
{#if typeof title === 'string'}{title}{:else}{@render title()}{/if}
|
|
56
|
+
</button>
|
|
57
|
+
{/snippet}
|
|
58
|
+
</Accordion.Trigger>
|
|
59
|
+
</svelte:element>
|
|
60
|
+
{/snippet}
|
|
61
|
+
</Accordion.Header>
|
|
62
|
+
<Accordion.Content
|
|
63
|
+
id={contentId}
|
|
64
|
+
role="region"
|
|
65
|
+
aria-labelledby={triggerId}
|
|
66
|
+
hiddenUntilFound={accordion.searchable()}
|
|
67
|
+
class={['collapse-content', contentClass]}
|
|
68
|
+
>
|
|
69
|
+
{@render children()}
|
|
70
|
+
</Accordion.Content>
|
|
71
|
+
</div>
|
|
72
|
+
{/snippet}
|
|
73
|
+
</Accordion.Item>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
|
|
4
|
+
/** Identifies the item in the `Accordion`'s `value`; defaults to a generated id. */
|
|
5
|
+
value?: string | undefined;
|
|
6
|
+
/** The header text, or a snippet for richer markup. */
|
|
7
|
+
title: string | Snippet<[]>;
|
|
8
|
+
/** Disable this item's button. */
|
|
9
|
+
disabled?: boolean | undefined;
|
|
10
|
+
/** Extra classes on the panel. */
|
|
11
|
+
contentClass?: string | undefined;
|
|
12
|
+
/** The panel's content. */
|
|
13
|
+
children: Snippet<[]>;
|
|
14
|
+
}
|
|
15
|
+
/** One headed, collapsible section of an `Accordion`. */
|
|
16
|
+
declare const AccordionItem: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type AccordionItem = ReturnType<typeof AccordionItem>;
|
|
18
|
+
export default AccordionItem;
|
package/dist/ui/Button.svelte
CHANGED
|
@@ -16,7 +16,8 @@ The daisyUI `btn` with tone, variant and size; `loading` disables it and sets `a
|
|
|
16
16
|
`type` defaults to `button`: a button inside a form that does not say what
|
|
17
17
|
it is submits it, which is almost never what a call site means.
|
|
18
18
|
-->
|
|
19
|
-
<script lang="ts">import
|
|
19
|
+
<script lang="ts">import Spinner from "./Spinner.svelte";
|
|
20
|
+
import { buttonSize, buttonTone, buttonVariant } from "./tones.js";
|
|
20
21
|
let { tone, variant = "solid", size = "md", loading = false, block = false, icon, children, type = "button", disabled = false, class: className, ...rest } = $props();
|
|
21
22
|
</script>
|
|
22
23
|
|
|
@@ -35,7 +36,7 @@ let { tone, variant = "solid", size = "md", loading = false, block = false, icon
|
|
|
35
36
|
]}
|
|
36
37
|
>
|
|
37
38
|
{#if loading}
|
|
38
|
-
<
|
|
39
|
+
<Spinner decorative size="sm" />
|
|
39
40
|
{:else if icon}
|
|
40
41
|
{@render icon()}
|
|
41
42
|
{/if}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A named cluster of independent buttons in a daisyUI `join`; each stays its own Tab stop.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
ButtonGroup — "Save | Save as… | Discard" on a form: presentational, a
|
|
6
|
+
`role="group"` with a name, no keyboard behaviour by design. For a row of
|
|
7
|
+
controls that should be ONE Tab stop with arrow keys, use `Toolbar`.
|
|
8
|
+
|
|
9
|
+
Children join the bar by taking `class="join-item"` themselves. The
|
|
10
|
+
group does not reach into them through a context, so `Button` stays
|
|
11
|
+
free of any group coupling.
|
|
12
|
+
-->
|
|
13
|
+
<script lang="ts">let { label, orientation = "horizontal", children, class: className, ...rest } = $props();
|
|
14
|
+
const orientationClass = {
|
|
15
|
+
horizontal: "",
|
|
16
|
+
vertical: "join-vertical",
|
|
17
|
+
responsive: "join-vertical sm:join-horizontal"
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<div {...rest} role="group" aria-label={label} class={['join', orientationClass[orientation], className]}>
|
|
23
|
+
{@render children()}
|
|
24
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
4
|
+
/** Accessible name of the group ("Save options"). */
|
|
5
|
+
label: string;
|
|
6
|
+
/**
|
|
7
|
+
* `responsive` stacks below `sm`.
|
|
8
|
+
* @defaultValue `'horizontal'`
|
|
9
|
+
*/
|
|
10
|
+
orientation?: 'horizontal' | 'vertical' | 'responsive' | undefined;
|
|
11
|
+
/** The buttons, each with `class="join-item"`. */
|
|
12
|
+
children: Snippet<[]>;
|
|
13
|
+
}
|
|
14
|
+
/** A named cluster of independent buttons in a daisyUI `join`; each stays its own Tab stop. */
|
|
15
|
+
declare const ButtonGroup: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type ButtonGroup = ReturnType<typeof ButtonGroup>;
|
|
17
|
+
export default ButtonGroup;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A menu that opens at the pointer on a right-click (or long-press) of a region, on bits-ui's `ContextMenu`.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
ContextMenu — the actions for one thing (a row in a list, a node on a
|
|
6
|
+
canvas, an image), opened where the reader right-clicks it, long-presses it
|
|
7
|
+
on touch, or presses Shift+F10 / the Menu key while it has focus. Built on
|
|
8
|
+
bits-ui's `ContextMenu`, which owns the menu at the pointer, roving focus,
|
|
9
|
+
typeahead, → / ← for submenus, Escape and outside click to close, and focus
|
|
10
|
+
return. Its children are the same `MenuItem`, `MenuSeparator`,
|
|
11
|
+
`MenuCheckboxItem`, `MenuRadioGroup` / `MenuRadioItem` and `MenuSub` that a
|
|
12
|
+
`DropdownMenu` takes; this component lends them the bits-ui parts through
|
|
13
|
+
context (`menu-parts/`).
|
|
14
|
+
|
|
15
|
+
Never the only route to an action. A context menu is invisible until it is
|
|
16
|
+
summoned, and touch screen-reader users (and many keyboard users) never
|
|
17
|
+
discover it. Put every action somewhere visible too: a row's "More"
|
|
18
|
+
`DropdownMenu`, a toolbar.
|
|
19
|
+
|
|
20
|
+
The region is the caller's own element: the `trigger` snippet receives
|
|
21
|
+
`props` to spread onto it. Those props make it focusable (`tabindex="0"`,
|
|
22
|
+
which a natively focusable element does not mind; pass a `tabindex` of your
|
|
23
|
+
own after the spread to override it), because browsers fire `contextmenu`
|
|
24
|
+
for the keyboard only on the focused element. Shift+F10 and the Menu key
|
|
25
|
+
are handled here rather than left to the browser, so they open the menu at
|
|
26
|
+
the region's corner the same way everywhere.
|
|
27
|
+
|
|
28
|
+
ARIA contract: the content is `role="menu"` named by `label`; the daisyUI
|
|
29
|
+
`menu` `<ul>` and its `<li>`s are `role="none"`, exactly as in
|
|
30
|
+
`DropdownMenu`, so `role="menu"` owns its items. `disabled` lets the
|
|
31
|
+
browser's own context menu show instead.
|
|
32
|
+
|
|
33
|
+
Not part of the root barrel: it is its own entry,
|
|
34
|
+
`@thomasfosterau/components/context-menu`, so the menu runtime loads only on
|
|
35
|
+
the pages that use it.
|
|
36
|
+
-->
|
|
37
|
+
<script lang="ts">import { ContextMenu } from "bits-ui";
|
|
38
|
+
import { menuParts } from "./menu-parts/parts.js";
|
|
39
|
+
import { setMenuParts } from "./menu-parts/menu-parts.js";
|
|
40
|
+
let { label, open = $bindable(false), disabled = false, class: className, trigger, children } = $props();
|
|
41
|
+
setMenuParts(menuParts("context"));
|
|
42
|
+
// Shift+F10 and the Menu key: open at the region's bottom-left corner by
|
|
43
|
+
// dispatching the `contextmenu` event bits-ui's trigger listens for.
|
|
44
|
+
// `preventDefault` stops the browser from generating its own as well.
|
|
45
|
+
const openFromKeyboard = (event) => {
|
|
46
|
+
if (disabled) return;
|
|
47
|
+
const isMenuKey = event.key === "ContextMenu" || event.shiftKey && event.key === "F10";
|
|
48
|
+
if (!isMenuKey) return;
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
const region = event.currentTarget;
|
|
51
|
+
if (!(region instanceof HTMLElement)) return;
|
|
52
|
+
const rect = region.getBoundingClientRect();
|
|
53
|
+
region.dispatchEvent(new MouseEvent("contextmenu", {
|
|
54
|
+
bubbles: true,
|
|
55
|
+
cancelable: true,
|
|
56
|
+
clientX: rect.left,
|
|
57
|
+
clientY: rect.bottom
|
|
58
|
+
}));
|
|
59
|
+
};
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<ContextMenu.Root bind:open>
|
|
63
|
+
<ContextMenu.Trigger {disabled} tabindex={0} onkeydown={openFromKeyboard}>
|
|
64
|
+
{#snippet child({ props })}
|
|
65
|
+
{@render trigger({ props })}
|
|
66
|
+
{/snippet}
|
|
67
|
+
</ContextMenu.Trigger>
|
|
68
|
+
<ContextMenu.Portal>
|
|
69
|
+
<ContextMenu.Content aria-label={label} class="z-50">
|
|
70
|
+
<ul role="none" class={['menu bg-base-100 rounded-box w-56 p-2 shadow-md', className]}>
|
|
71
|
+
{@render children()}
|
|
72
|
+
</ul>
|
|
73
|
+
</ContextMenu.Content>
|
|
74
|
+
</ContextMenu.Portal>
|
|
75
|
+
</ContextMenu.Root>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ContextMenu } from 'bits-ui';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Accessible name of the open menu ("Actions for Rosa canina"). */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Whether the menu is open. Bindable. */
|
|
7
|
+
open?: boolean | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Turn the menu off; the browser's own context menu shows instead.
|
|
10
|
+
* @defaultValue `false`
|
|
11
|
+
*/
|
|
12
|
+
disabled?: boolean | undefined;
|
|
13
|
+
/** Extra classes on the `<ul class="menu">`. */
|
|
14
|
+
class?: string | undefined;
|
|
15
|
+
/** The region the menu belongs to — the caller's own element, spreading `props`. */
|
|
16
|
+
trigger: Snippet<[{
|
|
17
|
+
props: Record<string, unknown>;
|
|
18
|
+
}]>;
|
|
19
|
+
/** The menu's items. */
|
|
20
|
+
children: Snippet<[]>;
|
|
21
|
+
}
|
|
22
|
+
/** A menu that opens at the pointer on a right-click (or long-press) of a region, on bits-ui's `ContextMenu`. */
|
|
23
|
+
declare const ContextMenu: import("svelte").Component<Props, {}, "open">;
|
|
24
|
+
type ContextMenu = ReturnType<typeof ContextMenu>;
|
|
25
|
+
export default ContextMenu;
|
|
@@ -16,6 +16,13 @@ A native `<details>` wearing daisyUI’s `collapse`.
|
|
|
16
16
|
a second copy can only be wrong). `open` is bindable, so a caller can drive
|
|
17
17
|
the state, and the four apps' "open this section because its form failed"
|
|
18
18
|
case is a one-line binding.
|
|
19
|
+
|
|
20
|
+
Exclusive groups are native too: give several `Disclosure`s the same
|
|
21
|
+
`name` (spread onto `<details>`) and the browser keeps at most one open.
|
|
22
|
+
What a `Disclosure` cannot carry is a heading, because `<summary>`'s
|
|
23
|
+
children are presentational, so a heading inside it drops out of the
|
|
24
|
+
heading tree. For a stack of headed sections, or a bindable "which one is
|
|
25
|
+
open", use `Accordion`.
|
|
19
26
|
-->
|
|
20
27
|
<script lang="ts">import { collapseVariant } from "./tones.js";
|
|
21
28
|
let { open = $bindable(false), title, variant = "arrow", contentClass, children, class: className, ...rest } = $props();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
import type {
|
|
2
|
+
import type { HTMLDetailsAttributes } from 'svelte/elements';
|
|
3
3
|
import { type DisclosureVariant } from './tones.ts';
|
|
4
|
-
interface Props extends Omit<
|
|
4
|
+
interface Props extends Omit<HTMLDetailsAttributes, 'title' | 'open'> {
|
|
5
5
|
/** Whether the section is expanded. Bindable. */
|
|
6
6
|
open?: boolean | undefined;
|
|
7
7
|
/** The summary — its own text, or a snippet for richer markup. */
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A horizontal or vertical rule between content, optionally labelled ("or"), in daisyUI's `divider` styles.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Divider — a rule between sections or side-by-side items, optionally with a
|
|
6
|
+
short label on it ("or" between two sign-in methods).
|
|
7
|
+
|
|
8
|
+
Hand-rolled, not on bits-ui's `Separator` (AGENTS.md asks for the reason):
|
|
9
|
+
that primitive's whole contribution is `role="separator"` +
|
|
10
|
+
`aria-orientation`, or `role="none"` when decorative — two attributes,
|
|
11
|
+
written here directly, with no bits-ui on the always-loaded path.
|
|
12
|
+
|
|
13
|
+
ARIA contract:
|
|
14
|
+
- Unlabelled: `role="separator"`; vertical adds
|
|
15
|
+
`aria-orientation="vertical"` (horizontal is the implicit default). It
|
|
16
|
+
is a `<div>`, not a native `<hr>`, because daisyUI draws the rule with
|
|
17
|
+
`::before` / `::after`, which a void `<hr>` cannot render.
|
|
18
|
+
- Labelled: NOT a separator. A non-focusable separator's children are
|
|
19
|
+
presentational, so the label would be lost to assistive technology. The
|
|
20
|
+
wrapper is `role="none"` and the text reads in flow — "Sign in with a
|
|
21
|
+
passkey / or / Continue with email" (WAI-ARIA 1.2, separator).
|
|
22
|
+
- `decorative`: `aria-hidden="true"`, nothing announced.
|
|
23
|
+
|
|
24
|
+
Not `MenuSeparator`, which must be an `<li>` inside a menu and defers to
|
|
25
|
+
bits-ui's menu separator for roving focus; use that one inside `Menu`.
|
|
26
|
+
-->
|
|
27
|
+
<script lang="ts">import { dividerAlign, dividerOrientation, dividerTone } from "./tones.js";
|
|
28
|
+
let { orientation = "horizontal", decorative = false, align = "center", tone, children, class: className, ...rest } = $props();
|
|
29
|
+
const classes = $derived([
|
|
30
|
+
"divider",
|
|
31
|
+
dividerOrientation[orientation],
|
|
32
|
+
dividerAlign[align],
|
|
33
|
+
tone !== undefined && dividerTone[tone],
|
|
34
|
+
className
|
|
35
|
+
]);
|
|
36
|
+
const role = $derived(decorative || children !== undefined ? "none" : "separator");
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<div
|
|
40
|
+
{...rest}
|
|
41
|
+
{role}
|
|
42
|
+
aria-orientation={role === 'separator' && orientation === 'vertical' ? 'vertical' : undefined}
|
|
43
|
+
aria-hidden={decorative ? 'true' : undefined}
|
|
44
|
+
class={classes}
|
|
45
|
+
>
|
|
46
|
+
{#if children}<span>{@render children()}</span>{/if}
|
|
47
|
+
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type DividerAlign, type Tone } from './tones.ts';
|
|
4
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
5
|
+
/**
|
|
6
|
+
* Which way the rule runs: `vertical` sits between side-by-side items.
|
|
7
|
+
* @defaultValue `'horizontal'`
|
|
8
|
+
*/
|
|
9
|
+
orientation?: 'horizontal' | 'vertical' | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* The rule marks no real boundary: hide it from assistive technology.
|
|
12
|
+
* @defaultValue `false`
|
|
13
|
+
*/
|
|
14
|
+
decorative?: boolean | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Where a label sits along the rule.
|
|
17
|
+
* @defaultValue `'center'`
|
|
18
|
+
*/
|
|
19
|
+
align?: DividerAlign | undefined;
|
|
20
|
+
/** Semantic colour of the rule. */
|
|
21
|
+
tone?: Tone | undefined;
|
|
22
|
+
/** Short text on the rule ("or"). */
|
|
23
|
+
children?: Snippet<[]> | undefined;
|
|
24
|
+
}
|
|
25
|
+
/** A horizontal or vertical rule between content, optionally labelled ("or"), in daisyUI's `divider` styles. */
|
|
26
|
+
declare const Divider: import("svelte").Component<Props, {}, "">;
|
|
27
|
+
type Divider = ReturnType<typeof Divider>;
|
|
28
|
+
export default Divider;
|