@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,66 @@
|
|
|
1
|
+
import { PinInput } from 'bits-ui';
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
import { type FieldSize } from './field-types.ts';
|
|
4
|
+
interface Props extends Omit<HTMLInputAttributes, 'value' | 'size' | 'type' | 'maxlength' | 'pattern' | 'children' | 'inputmode'> {
|
|
5
|
+
/**
|
|
6
|
+
* The code typed so far. Bindable.
|
|
7
|
+
* @defaultValue `''`
|
|
8
|
+
*/
|
|
9
|
+
value?: string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Number of cells, and the input's `maxlength`.
|
|
12
|
+
* @defaultValue `6`
|
|
13
|
+
*/
|
|
14
|
+
length?: number | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Which characters are accepted; sets `inputmode` and the input's pattern.
|
|
17
|
+
* @defaultValue `'numeric'`
|
|
18
|
+
*/
|
|
19
|
+
charset?: 'numeric' | 'alphanumeric' | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Show `•` in the cells instead of the characters (a PIN rather than a code).
|
|
22
|
+
* @defaultValue `false`
|
|
23
|
+
*/
|
|
24
|
+
masked?: boolean | undefined;
|
|
25
|
+
/** Called once every cell is filled, with the full code — typically to submit. */
|
|
26
|
+
onComplete?: ((code: string) => void) | undefined;
|
|
27
|
+
/** Normalise pasted text before it is applied. Defaults to stripping whitespace and hyphens. */
|
|
28
|
+
pasteTransformer?: ((text: string) => string) | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The input's `autocomplete`.
|
|
31
|
+
* @defaultValue `'one-time-code'`
|
|
32
|
+
*/
|
|
33
|
+
autocomplete?: HTMLInputAttributes['autocomplete'];
|
|
34
|
+
/**
|
|
35
|
+
* Size step of the cells.
|
|
36
|
+
* @defaultValue `'md'`
|
|
37
|
+
*/
|
|
38
|
+
size?: FieldSize | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Disables the input.
|
|
41
|
+
* @defaultValue `false`
|
|
42
|
+
*/
|
|
43
|
+
disabled?: boolean | undefined;
|
|
44
|
+
/** `FieldControl`: the real input's `id`, which `FormField`'s label points at. */
|
|
45
|
+
id?: string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* `FieldControl`: space-separated ids for `aria-describedby`.
|
|
48
|
+
* @defaultValue `''`
|
|
49
|
+
*/
|
|
50
|
+
describedBy?: string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* `FieldControl`: the visible label's id. Not used — the real input is
|
|
53
|
+
* labelable, so the `FormField`'s `<label for>` already names it; taken
|
|
54
|
+
* here so a spread `FieldControl` does not leak it as an attribute.
|
|
55
|
+
*/
|
|
56
|
+
labelId?: string | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* `FieldControl`: the field is in error — sets `aria-invalid` and the error border.
|
|
59
|
+
* @defaultValue `false`
|
|
60
|
+
*/
|
|
61
|
+
invalid?: boolean | undefined;
|
|
62
|
+
}
|
|
63
|
+
/** A fixed-length code — a sign-in code, a TOTP, an invite code — typed one character per visible cell into one real input, on bits-ui's `PinInput`. */
|
|
64
|
+
declare const PinInput: import("svelte").Component<Props, {}, "value">;
|
|
65
|
+
type PinInput = ReturnType<typeof PinInput>;
|
|
66
|
+
export default PinInput;
|
|
@@ -17,6 +17,9 @@ One choice from a short, known list, on bits-ui’s `RadioGroup`.
|
|
|
17
17
|
question as a heading-ish `<span>` and points `aria-labelledby` at it. That
|
|
18
18
|
is why this is the one field group NOT built on `Fieldset`, and the hint and
|
|
19
19
|
errors hang off the radiogroup element itself through `aria-describedby`.
|
|
20
|
+
It is a whole frame (question, hint, errors), not a control a `FormField`
|
|
21
|
+
wraps, so it keeps its own `legend` rather than taking a `FieldControl`'s
|
|
22
|
+
`labelId`: inside a `FormField` it would render two labels.
|
|
20
23
|
|
|
21
24
|
The marker is drawn rather than delegated to daisyUI's `.radio`, because
|
|
22
25
|
that class styles an `<input type="radio">` through `:checked` and bits-ui's
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
An inline, always-visible month grid for picking a start–end date range, on bits-ui's `RangeCalendar`.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
RangeCalendar — `Calendar`'s range sibling: the first chosen day starts
|
|
6
|
+
the range, the second ends it, and the days between are drawn as one band.
|
|
7
|
+
`minDays` / `maxDays` bound the length; `excludeDisabled` decides whether a
|
|
8
|
+
range may run across disabled days. It shares everything else — props,
|
|
9
|
+
header, grids, day styling — with `Calendar` (`calendar-props.ts`,
|
|
10
|
+
`CalendarMonths.svelte`), so the ARIA contract is the same: see
|
|
11
|
+
`Calendar`'s header. bits-ui adds `data-range-start` / `-middle` / `-end`
|
|
12
|
+
to the days, which is all the band styling reads.
|
|
13
|
+
|
|
14
|
+
The value is `{ start, end }` of `DateValue`s, either side `undefined`
|
|
15
|
+
while the reader is still choosing.
|
|
16
|
+
-->
|
|
17
|
+
<script lang="ts">import { RangeCalendar } from "bits-ui";
|
|
18
|
+
import { getComponentsContext } from "../context.svelte.js";
|
|
19
|
+
import CalendarMonths from "./CalendarMonths.svelte";
|
|
20
|
+
import { calendarRoot, definedProps } from "./calendar-props.js";
|
|
21
|
+
let { value = $bindable(), month = $bindable(), minDays, maxDays, excludeDisabled = false, ...props } = $props();
|
|
22
|
+
const context = getComponentsContext();
|
|
23
|
+
const shared = $derived(calendarRoot(props, context.locale()));
|
|
24
|
+
const lengths = $derived(definedProps({
|
|
25
|
+
minDays,
|
|
26
|
+
maxDays
|
|
27
|
+
}));
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<RangeCalendar.Root
|
|
31
|
+
{...shared.root}
|
|
32
|
+
{...lengths}
|
|
33
|
+
{excludeDisabled}
|
|
34
|
+
bind:placeholder={
|
|
35
|
+
// Unset, bits-ui picks the value's month or today's.
|
|
36
|
+
() => month as DateValue,
|
|
37
|
+
(next: DateValue) => {
|
|
38
|
+
month = next;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
bind:value={
|
|
42
|
+
() => value ?? { start: undefined, end: undefined },
|
|
43
|
+
(next) => {
|
|
44
|
+
value = next;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
>
|
|
48
|
+
{#snippet children({ months, weekdays })}
|
|
49
|
+
<CalendarMonths parts={RangeCalendar} {...shared.months} {months} {weekdays} />
|
|
50
|
+
{/snippet}
|
|
51
|
+
</RangeCalendar.Root>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DateValue } from '@internationalized/date';
|
|
2
|
+
import { RangeCalendar } from 'bits-ui';
|
|
3
|
+
import { type CalendarBaseProps } from './calendar-props.ts';
|
|
4
|
+
interface Props extends CalendarBaseProps {
|
|
5
|
+
/** `{ start, end }`; either side `undefined` while choosing. Bindable. */
|
|
6
|
+
value?: {
|
|
7
|
+
start: DateValue | undefined;
|
|
8
|
+
end: DateValue | undefined;
|
|
9
|
+
} | undefined;
|
|
10
|
+
/** Shortest allowed range, in days. */
|
|
11
|
+
minDays?: number | undefined;
|
|
12
|
+
/** Longest allowed range, in days. */
|
|
13
|
+
maxDays?: number | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Refuse a range that would span a disabled day.
|
|
16
|
+
* @defaultValue `false`
|
|
17
|
+
*/
|
|
18
|
+
excludeDisabled?: boolean | undefined;
|
|
19
|
+
}
|
|
20
|
+
/** An inline, always-visible month grid for picking a start–end date range, on bits-ui's `RangeCalendar`. */
|
|
21
|
+
declare const RangeCalendar: import("svelte").Component<Props, {}, "value" | "month">;
|
|
22
|
+
type RangeCalendar = ReturnType<typeof RangeCalendar>;
|
|
23
|
+
export default RangeCalendar;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A score out of N as stars: an input on bits-ui's `RatingGroup` that binds a number (optionally in half steps) and posts under `name`, or, with `readonly`, a static display named "4.5 out of 5".
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Rating — one component, two branches.
|
|
6
|
+
|
|
7
|
+
Input (the default): bits-ui's `RatingGroup`. Its root is `role="slider"`
|
|
8
|
+
(bits-ui 2.20, `rating-group.svelte.js`) — one Tab stop, with the stars
|
|
9
|
+
`role="presentation"` — so there is no "radio button 4 of 5" with no
|
|
10
|
+
context. There is no APG rating pattern; bits-ui follows the slider one.
|
|
11
|
+
bits-ui supplies the role, `aria-valuenow` / `min` / `max`,
|
|
12
|
+
`aria-valuetext` (from `formatValue` here), `aria-readonly`,
|
|
13
|
+
`aria-disabled`, `aria-required`, the hidden input under `name`, the
|
|
14
|
+
arrow / Home / End / Page keys, digit keys (with `allowHalf`, "3" then
|
|
15
|
+
".5" gives 3.5), and half-step pointer handling. This file adds the
|
|
16
|
+
accessible name, `aria-describedby` and `aria-invalid`.
|
|
17
|
+
|
|
18
|
+
Naming: a `<label for>` cannot name a `div`, so inside a `FormField` the
|
|
19
|
+
root takes `aria-labelledby={labelId}` (`FieldControl.labelId`, #179) and
|
|
20
|
+
`FormField` forwards a label click to it. Outside one, `label` becomes the
|
|
21
|
+
root's `aria-label`.
|
|
22
|
+
|
|
23
|
+
Clearing: bits-ui clears the value to 0 when the first star is clicked
|
|
24
|
+
while the value already equals it (pointer only; no key clears below
|
|
25
|
+
`min`, which is 0). That is left as bits-ui ships it, not hand-patched.
|
|
26
|
+
|
|
27
|
+
Read-only display (`readonly`): plain markup with no bits-ui root mounted —
|
|
28
|
+
`role="img"` with `aria-label={formatValue(value, max)}`, every star
|
|
29
|
+
`aria-hidden`, nothing focusable, nothing posted. It rounds to the nearest
|
|
30
|
+
half star whatever `allowHalf` says, because a display usually shows an
|
|
31
|
+
average ("4.5 out of 5"). The label still says the exact value.
|
|
32
|
+
|
|
33
|
+
Styling: daisyUI's `rating-*` size classes set `--size`; each star is two
|
|
34
|
+
`mask mask-star-2` halves (`mask-half-1` / `mask-half-2`, which flip in
|
|
35
|
+
RTL), filled in the tone's background or left at `bg-base-content/20`.
|
|
36
|
+
daisyUI's `rating` class itself is not used: its `.rating *` rules style
|
|
37
|
+
every descendant, which fights bits-ui's item wrappers.
|
|
38
|
+
-->
|
|
39
|
+
<script lang="ts">import { RatingGroup } from "bits-ui";
|
|
40
|
+
import { backgroundTone } from "../ui/tones.js";
|
|
41
|
+
import { ratingSize } from "./field-types.js";
|
|
42
|
+
let { value = $bindable(0), max = 5, allowHalf = false, readonly = false, hoverPreview = true, formatValue = (score, outOf) => `${score} out of ${outOf}`, name, disabled = false, tone = "warning", size = "md", item, label = "Rating", id: idProp, labelId, describedBy = "", invalid = false, required = false, class: className, ...rest } = $props();
|
|
43
|
+
const uid = $props.id();
|
|
44
|
+
const id = $derived(idProp ?? uid);
|
|
45
|
+
const fill = $derived(backgroundTone[tone]);
|
|
46
|
+
// bits-ui types `name` as a bare optional, so it is omitted rather than passed as `undefined`.
|
|
47
|
+
const nameProp = $derived(name === undefined ? {} : { name });
|
|
48
|
+
/** The display's stars: the value rounded to the nearest half. */
|
|
49
|
+
const displayStates = $derived.by(() => {
|
|
50
|
+
const shown = Math.round(value * 2) / 2;
|
|
51
|
+
return Array.from({ length: max }, (_, index) => {
|
|
52
|
+
if (shown >= index + 1) return "active";
|
|
53
|
+
return shown >= index + .5 ? "partial" : "inactive";
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
{#snippet star(state: StarState)}
|
|
59
|
+
<span aria-hidden="true" class="flex">
|
|
60
|
+
{#if item}
|
|
61
|
+
{@render item({ state })}
|
|
62
|
+
{:else}
|
|
63
|
+
<span
|
|
64
|
+
class={[
|
|
65
|
+
'mask mask-star-2 mask-half-1 h-[calc(var(--size))] w-[calc(var(--size)*0.5)]',
|
|
66
|
+
state === 'inactive' ? 'bg-base-content/20' : fill,
|
|
67
|
+
]}
|
|
68
|
+
></span>
|
|
69
|
+
<span
|
|
70
|
+
class={[
|
|
71
|
+
'mask mask-star-2 mask-half-2 h-[calc(var(--size))] w-[calc(var(--size)*0.5)]',
|
|
72
|
+
state === 'active' ? fill : 'bg-base-content/20',
|
|
73
|
+
]}
|
|
74
|
+
></span>
|
|
75
|
+
{/if}
|
|
76
|
+
</span>
|
|
77
|
+
{/snippet}
|
|
78
|
+
|
|
79
|
+
{#if readonly}
|
|
80
|
+
<div
|
|
81
|
+
{...rest}
|
|
82
|
+
{id}
|
|
83
|
+
role="img"
|
|
84
|
+
aria-label={formatValue(value, max)}
|
|
85
|
+
class={['inline-flex align-middle', ratingSize[size], className]}
|
|
86
|
+
>
|
|
87
|
+
{#each displayStates as state, index (index)}
|
|
88
|
+
{@render star(state)}
|
|
89
|
+
{/each}
|
|
90
|
+
</div>
|
|
91
|
+
{:else}
|
|
92
|
+
<RatingGroup.Root
|
|
93
|
+
{...rest}
|
|
94
|
+
bind:value
|
|
95
|
+
{id}
|
|
96
|
+
{max}
|
|
97
|
+
{allowHalf}
|
|
98
|
+
{hoverPreview}
|
|
99
|
+
{...nameProp}
|
|
100
|
+
{disabled}
|
|
101
|
+
{required}
|
|
102
|
+
aria-valuetext={formatValue}
|
|
103
|
+
aria-label={labelId === undefined ? label : undefined}
|
|
104
|
+
aria-labelledby={labelId}
|
|
105
|
+
aria-describedby={describedBy === '' ? undefined : describedBy}
|
|
106
|
+
aria-invalid={invalid ? 'true' : undefined}
|
|
107
|
+
class={[
|
|
108
|
+
'inline-flex rounded-field align-middle focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary data-[disabled]:opacity-50',
|
|
109
|
+
invalid && 'outline-2 outline-offset-2 outline-error',
|
|
110
|
+
ratingSize[size],
|
|
111
|
+
className,
|
|
112
|
+
]}
|
|
113
|
+
>
|
|
114
|
+
{#snippet children({ items })}
|
|
115
|
+
{#each items as entry (entry.index)}
|
|
116
|
+
<RatingGroup.Item
|
|
117
|
+
index={entry.index}
|
|
118
|
+
class="cursor-pointer data-[disabled]:cursor-not-allowed"
|
|
119
|
+
>
|
|
120
|
+
{#snippet children({ state })}
|
|
121
|
+
{@render star(state)}
|
|
122
|
+
{/snippet}
|
|
123
|
+
</RatingGroup.Item>
|
|
124
|
+
{/each}
|
|
125
|
+
{/snippet}
|
|
126
|
+
</RatingGroup.Root>
|
|
127
|
+
{/if}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Size, type Tone } from '../ui/tones.ts';
|
|
4
|
+
/** How much of one star is filled. */
|
|
5
|
+
type StarState = 'active' | 'partial' | 'inactive';
|
|
6
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'dir'> {
|
|
7
|
+
/**
|
|
8
|
+
* The score. Bindable.
|
|
9
|
+
* @defaultValue `0`
|
|
10
|
+
*/
|
|
11
|
+
value?: number | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Number of stars.
|
|
14
|
+
* @defaultValue `5`
|
|
15
|
+
*/
|
|
16
|
+
max?: number | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Allow half-star values (bits-ui `allowHalf`).
|
|
19
|
+
* @defaultValue `false`
|
|
20
|
+
*/
|
|
21
|
+
allowHalf?: boolean | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Render a static, unfocusable display instead of an input.
|
|
24
|
+
* @defaultValue `false`
|
|
25
|
+
*/
|
|
26
|
+
readonly?: boolean | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Preview the hovered value before it is chosen (bits-ui `hoverPreview`).
|
|
29
|
+
* @defaultValue `true`
|
|
30
|
+
*/
|
|
31
|
+
hoverPreview?: boolean | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The spoken value (the input's `aria-valuetext`); also the display's accessible name.
|
|
34
|
+
* @defaultValue `(value, max) => \`${value} out of ${max}\``
|
|
35
|
+
*/
|
|
36
|
+
formatValue?: ((value: number, max: number) => string) | undefined;
|
|
37
|
+
/** Posts the value under this name (input only). */
|
|
38
|
+
name?: string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Blocks keys and clicks, and posts nothing.
|
|
41
|
+
* @defaultValue `false`
|
|
42
|
+
*/
|
|
43
|
+
disabled?: boolean | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Colour of the filled stars.
|
|
46
|
+
* @defaultValue `'warning'`
|
|
47
|
+
*/
|
|
48
|
+
tone?: Tone | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* daisyUI `rating` size step.
|
|
51
|
+
* @defaultValue `'md'`
|
|
52
|
+
*/
|
|
53
|
+
size?: Size | undefined;
|
|
54
|
+
/** Replace the star glyph (a heart, a leaf); receives how much of it is filled. It is rendered inside an `aria-hidden` wrapper. */
|
|
55
|
+
item?: Snippet<[{
|
|
56
|
+
state: StarState;
|
|
57
|
+
}]> | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Accessible name, for a rating outside a `FormField`; ignored when `labelId` is set.
|
|
60
|
+
* @defaultValue `'Rating'`
|
|
61
|
+
*/
|
|
62
|
+
label?: string | undefined;
|
|
63
|
+
/** `FieldControl`: the root's `id`. Minted when omitted. */
|
|
64
|
+
id?: string | undefined;
|
|
65
|
+
/** `FieldControl`: the visible label's id; the root is `aria-labelledby` it. */
|
|
66
|
+
labelId?: string | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* `FieldControl`: space-separated ids for `aria-describedby`.
|
|
69
|
+
* @defaultValue `''`
|
|
70
|
+
*/
|
|
71
|
+
describedBy?: string | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* `FieldControl`: the field is in error — sets `aria-invalid` and an error outline.
|
|
74
|
+
* @defaultValue `false`
|
|
75
|
+
*/
|
|
76
|
+
invalid?: boolean | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* `FieldControl`: sets `aria-required` and the hidden input's `required`.
|
|
79
|
+
* @defaultValue `false`
|
|
80
|
+
*/
|
|
81
|
+
required?: boolean | undefined;
|
|
82
|
+
}
|
|
83
|
+
/** A score out of N as stars: an input on bits-ui's `RatingGroup` that binds a number (optionally in half steps) and posts under `name`, or, with `readonly`, a static display named "4.5 out of 5". */
|
|
84
|
+
declare const Rating: import("svelte").Component<Props, {}, "value">;
|
|
85
|
+
type Rating = ReturnType<typeof Rating>;
|
|
86
|
+
export default Rating;
|
|
@@ -20,6 +20,7 @@ A `type="search"` field with a leading icon, a clear button while there is text,
|
|
|
20
20
|
uses) and `onSubmit` on Enter.
|
|
21
21
|
-->
|
|
22
22
|
<script lang="ts">import { Search, X } from "@lucide/svelte";
|
|
23
|
+
import Spinner from "../ui/Spinner.svelte";
|
|
23
24
|
import { inputSize } from "./field-types.js";
|
|
24
25
|
let { label, value = $bindable(""), onInput, onSubmit, onClear, pending = false, clearLabel = "Clear search", size = "md", name = "q", placeholder = "Search", class: className, ...rest } = $props();
|
|
25
26
|
const clear = () => {
|
|
@@ -47,7 +48,7 @@ const clear = () => {
|
|
|
47
48
|
}}
|
|
48
49
|
/>
|
|
49
50
|
{#if pending}
|
|
50
|
-
<
|
|
51
|
+
<Spinner decorative size="xs" />
|
|
51
52
|
{:else if value !== ''}
|
|
52
53
|
<button type="button" class="btn btn-ghost btn-xs btn-circle" onclick={clear}>
|
|
53
54
|
<X class="size-3.5" aria-hidden="true" />
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The segment group the bits-ui date and time controls share; internal, not exported.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
SegmentGroup — the `role="group"` of spinbutton segments inside a bits-ui
|
|
6
|
+
`DateField`, `TimeField`, `DatePicker` or `DateRangePicker` root, written
|
|
7
|
+
once for all four. `parts` is the bits-ui namespace in use; each exposes
|
|
8
|
+
`Input` and `Segment` (a range's `Input` also takes `type`).
|
|
9
|
+
|
|
10
|
+
`<label for>` cannot name a `role="group"`, so the group is
|
|
11
|
+
`aria-labelledby` the `FormField`'s label (`labelId`), and each segment is
|
|
12
|
+
labelled by its own part name ("day, ") followed by that label, so a
|
|
13
|
+
reader landing on one hears "day, Sown on". bits-ui would name them from
|
|
14
|
+
its own `Label`, which a `FormField` has no use for, so the segments are
|
|
15
|
+
rendered through their `child` snippet with our `aria-labelledby` last.
|
|
16
|
+
The literal separators ("/", ":") are `aria-hidden`: the spinbutton names
|
|
17
|
+
already say which part is which.
|
|
18
|
+
|
|
19
|
+
Two shapes. By default the group IS the field: the `input` shell, named
|
|
20
|
+
by the label, carrying `describedBy` / `invalid`, with an optional
|
|
21
|
+
`trailing` snippet (the picker's calendar button) inside it. With
|
|
22
|
+
`range`, it is one half of a `DateRangePicker`, whose root draws the
|
|
23
|
+
shell: a bare inline group named by `range.label` ("Start date"), whose
|
|
24
|
+
segments read "day, <label> Start date".
|
|
25
|
+
-->
|
|
26
|
+
<script lang="ts">import { inputSize } from "./field-types.js";
|
|
27
|
+
let { parts, group, range, trailing } = $props();
|
|
28
|
+
const { class: className, ...attributes } = $derived(group.attributes);
|
|
29
|
+
// SAFETY: the namespaces' `Segment`s differ only in the `part` union they
|
|
30
|
+
// accept, and every `part` rendered here comes from the same root's own
|
|
31
|
+
// `segments`, so a TimeField segment is only ever handed a time part.
|
|
32
|
+
const Segment = $derived(parts.Segment);
|
|
33
|
+
// SAFETY: every `Input` takes `id`, `name` and `child`; only the range's
|
|
34
|
+
// also takes `type`, which is passed only when `range` is set, and a range
|
|
35
|
+
// is only ever rendered with `DateRangePicker`'s parts.
|
|
36
|
+
const Input = $derived(parts.Input);
|
|
37
|
+
const inputProps = $derived({
|
|
38
|
+
...group.id === undefined || range !== undefined ? {} : { id: group.id },
|
|
39
|
+
name: (range === undefined ? group.name : range.name) ?? "",
|
|
40
|
+
...range === undefined ? {} : { type: range.type }
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
{#snippet segmentList(segments: readonly { part: string; value: string }[], names: string | undefined)}
|
|
45
|
+
{#each segments as { part, value }, index (index)}
|
|
46
|
+
<Segment part={part as 'literal'}>
|
|
47
|
+
{#snippet child({ props: segmentProps })}
|
|
48
|
+
{#if part === 'literal'}
|
|
49
|
+
<span {...segmentProps} aria-hidden="true" class="text-base-content/60 px-px">{value}</span>
|
|
50
|
+
{:else}
|
|
51
|
+
<span
|
|
52
|
+
{...segmentProps}
|
|
53
|
+
aria-labelledby={names === undefined ? undefined : `${String(segmentProps['id'])} ${names}`}
|
|
54
|
+
class="focus:bg-primary focus:text-primary-content rounded-sm px-0.5 outline-none aria-[valuetext=Empty]:text-base-content/50"
|
|
55
|
+
>{value}</span
|
|
56
|
+
>
|
|
57
|
+
{/if}
|
|
58
|
+
{/snippet}
|
|
59
|
+
</Segment>
|
|
60
|
+
{/each}
|
|
61
|
+
{/snippet}
|
|
62
|
+
|
|
63
|
+
<Input {...inputProps}>
|
|
64
|
+
{#snippet child({ props, segments })}
|
|
65
|
+
{#if range !== undefined}
|
|
66
|
+
{@const ownId = String(props['id'])}
|
|
67
|
+
<div {...props} role="group" aria-label={range.label} class="inline-flex items-center">
|
|
68
|
+
{@render segmentList(segments, [group.labelId, ownId].filter(Boolean).join(' '))}
|
|
69
|
+
</div>
|
|
70
|
+
{:else}
|
|
71
|
+
<div
|
|
72
|
+
{...attributes}
|
|
73
|
+
{...props}
|
|
74
|
+
aria-labelledby={group.labelId}
|
|
75
|
+
aria-describedby={group.describedBy === '' ? undefined : group.describedBy}
|
|
76
|
+
aria-invalid={group.invalid ? 'true' : undefined}
|
|
77
|
+
class={[
|
|
78
|
+
'input inline-flex w-auto items-center gap-0 tabular-nums',
|
|
79
|
+
inputSize[group.size],
|
|
80
|
+
group.invalid && 'input-error',
|
|
81
|
+
'data-invalid:input-error',
|
|
82
|
+
group.disabled && 'opacity-50',
|
|
83
|
+
className,
|
|
84
|
+
]}
|
|
85
|
+
>
|
|
86
|
+
{@render segmentList(segments, group.labelId)}
|
|
87
|
+
{@render trailing?.()}
|
|
88
|
+
</div>
|
|
89
|
+
{/if}
|
|
90
|
+
{/snippet}
|
|
91
|
+
</Input>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DateField, DatePicker, DateRangePicker, TimeField } from 'bits-ui';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { SegmentGroupOptions } from './segmented-field.ts';
|
|
4
|
+
interface Props {
|
|
5
|
+
/** The bits-ui namespace whose parts to render. */
|
|
6
|
+
parts: typeof DateField | typeof TimeField | typeof DatePicker | typeof DateRangePicker;
|
|
7
|
+
group: SegmentGroupOptions;
|
|
8
|
+
/** One half of a range: which half, its name, and its posted field name. */
|
|
9
|
+
range?: {
|
|
10
|
+
readonly type: 'start' | 'end';
|
|
11
|
+
readonly label: string;
|
|
12
|
+
readonly name?: string | undefined;
|
|
13
|
+
} | undefined;
|
|
14
|
+
/** Rendered inside the shell after the segments (a picker's trigger). */
|
|
15
|
+
trailing?: Snippet<[]> | undefined;
|
|
16
|
+
}
|
|
17
|
+
/** The segment group the bits-ui date and time controls share; internal, not exported. */
|
|
18
|
+
declare const SegmentGroup: import("svelte").Component<Props, {}, "">;
|
|
19
|
+
type SegmentGroup = ReturnType<typeof SegmentGroup>;
|
|
20
|
+
export default SegmentGroup;
|
|
@@ -16,7 +16,7 @@ A native `<select>` in daisyUI styling over a list of options, with an optional
|
|
|
16
16
|
attributes added.
|
|
17
17
|
-->
|
|
18
18
|
<script lang="ts">import { selectSize, toFieldOptions } from "./field-types.js";
|
|
19
|
-
let { options, value = $bindable(""), placeholder, size = "md", invalid = false, describedBy = "", class: className, ...rest } = $props();
|
|
19
|
+
let { options, value = $bindable(""), placeholder, size = "md", invalid = false, describedBy = "", labelId: _labelId, class: className, ...rest } = $props();
|
|
20
20
|
const entries = $derived(toFieldOptions(options));
|
|
21
21
|
</script>
|
|
22
22
|
|
|
@@ -22,6 +22,12 @@ interface Props extends Omit<HTMLSelectAttributes, 'value' | 'size'> {
|
|
|
22
22
|
* @defaultValue `''`
|
|
23
23
|
*/
|
|
24
24
|
describedBy?: string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* `FieldControl`: the visible label's id. Not used — this control is
|
|
27
|
+
* labelable, so the `FormField`'s `<label for>` already names it; taken
|
|
28
|
+
* here so a spread `FieldControl` does not leak it as an attribute.
|
|
29
|
+
*/
|
|
30
|
+
labelId?: string | undefined;
|
|
25
31
|
}
|
|
26
32
|
/** A native `<select>` in daisyUI styling over a list of options, with an optional placeholder and `FieldControl` wiring. */
|
|
27
33
|
declare const Select: import("svelte").Component<Props, {}, "value">;
|