@loidolt/theme-svelte 0.5.0 → 0.7.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 +11 -0
- package/dist/announcer.svelte.d.ts +36 -0
- package/dist/announcer.svelte.js +73 -0
- package/dist/attachments.d.ts +83 -0
- package/dist/attachments.js +211 -0
- package/dist/components/ActiveFilterChips.svelte +80 -0
- package/dist/components/ActiveFilterChips.svelte.d.ts +41 -0
- package/dist/components/AspectRatio.svelte +32 -0
- package/dist/components/AspectRatio.svelte.d.ts +13 -0
- package/dist/components/AudioPlayer.svelte +94 -0
- package/dist/components/AudioPlayer.svelte.d.ts +24 -0
- package/dist/components/Avatar.svelte +85 -0
- package/dist/components/Avatar.svelte.d.ts +31 -0
- package/dist/components/Badge.svelte +28 -2
- package/dist/components/Badge.svelte.d.ts +13 -1
- package/dist/components/Combobox.svelte +180 -0
- package/dist/components/Combobox.svelte.d.ts +77 -0
- package/dist/components/DataTable.svelte +228 -0
- package/dist/components/DataTable.svelte.d.ts +62 -0
- package/dist/components/DataTableColumnVisibility.svelte +46 -0
- package/dist/components/DataTableColumnVisibility.svelte.d.ts +31 -0
- package/dist/components/DataTableFacetedFilter.svelte +87 -0
- package/dist/components/DataTableFacetedFilter.svelte.d.ts +40 -0
- package/dist/components/DataTablePagination.svelte +68 -0
- package/dist/components/DataTablePagination.svelte.d.ts +42 -0
- package/dist/components/DataTableSearch.svelte +67 -0
- package/dist/components/DataTableSearch.svelte.d.ts +37 -0
- package/dist/components/Dialog.svelte +32 -11
- package/dist/components/Dialog.svelte.d.ts +7 -0
- package/dist/components/DropdownMenu.svelte +95 -28
- package/dist/components/DropdownMenu.svelte.d.ts +12 -3
- package/dist/components/Field.svelte +2 -3
- package/dist/components/Fieldset.svelte +2 -3
- package/dist/components/Filmstrip.svelte +10 -3
- package/dist/components/FilterPanel.svelte +62 -0
- package/dist/components/FilterPanel.svelte.d.ts +22 -0
- package/dist/components/Lightbox.svelte +239 -0
- package/dist/components/Lightbox.svelte.d.ts +40 -0
- package/dist/components/LiveRegion.svelte +48 -0
- package/dist/components/LiveRegion.svelte.d.ts +20 -0
- package/dist/components/MediaCarousel.svelte +177 -0
- package/dist/components/MediaCarousel.svelte.d.ts +32 -0
- package/dist/components/MediaEmbed.svelte +116 -0
- package/dist/components/MediaEmbed.svelte.d.ts +34 -0
- package/dist/components/MediaGrid.svelte +91 -0
- package/dist/components/MediaGrid.svelte.d.ts +24 -0
- package/dist/components/MultiSelect.svelte +232 -0
- package/dist/components/MultiSelect.svelte.d.ts +67 -0
- package/dist/components/NumberField.svelte +53 -25
- package/dist/components/NumberField.svelte.d.ts +47 -18
- package/dist/components/OTPInput.svelte +94 -0
- package/dist/components/OTPInput.svelte.d.ts +26 -0
- package/dist/components/PasswordInput.svelte +93 -0
- package/dist/components/PasswordInput.svelte.d.ts +24 -0
- package/dist/components/Progress.svelte +57 -14
- package/dist/components/Progress.svelte.d.ts +13 -0
- package/dist/components/RadioGroup.svelte +19 -5
- package/dist/components/RadioGroup.svelte.d.ts +3 -2
- package/dist/components/Select.svelte +15 -5
- package/dist/components/Select.svelte.d.ts +3 -2
- package/dist/components/SignaturePad.svelte +317 -0
- package/dist/components/SignaturePad.svelte.d.ts +48 -0
- package/dist/components/Skeleton.svelte +40 -9
- package/dist/components/Skeleton.svelte.d.ts +8 -0
- package/dist/components/SkipLink.svelte +48 -0
- package/dist/components/SkipLink.svelte.d.ts +14 -0
- package/dist/components/Slider.svelte +148 -0
- package/dist/components/Slider.svelte.d.ts +55 -0
- package/dist/components/Spinner.svelte +11 -2
- package/dist/components/Spinner.svelte.d.ts +3 -0
- package/dist/components/Tabs.svelte.d.ts +1 -1
- package/dist/components/Thumbnail.svelte +52 -6
- package/dist/components/Thumbnail.svelte.d.ts +14 -2
- package/dist/components/Toast.svelte +24 -0
- package/dist/components/Toast.svelte.d.ts +10 -0
- package/dist/components/ToastViewport.svelte +9 -1
- package/dist/components/ToastViewport.svelte.d.ts +2 -0
- package/dist/components/Toolbar.svelte +49 -0
- package/dist/components/Toolbar.svelte.d.ts +23 -0
- package/dist/components/Tooltip.svelte +9 -1
- package/dist/components/Tooltip.svelte.d.ts +2 -0
- package/dist/components/VideoPlayer.svelte +136 -0
- package/dist/components/VideoPlayer.svelte.d.ts +38 -0
- package/dist/data-table-utils.d.ts +62 -0
- package/dist/data-table-utils.js +91 -0
- package/dist/data-table.svelte.d.ts +132 -0
- package/dist/data-table.svelte.js +292 -0
- package/dist/hls.d.ts +35 -0
- package/dist/hls.js +37 -0
- package/dist/hls.svelte.d.ts +22 -0
- package/dist/hls.svelte.js +66 -0
- package/dist/index.d.ts +49 -4
- package/dist/index.js +36 -2
- package/dist/internal/EditableCell.svelte +58 -0
- package/dist/internal/EditableCell.svelte.d.ts +8 -0
- package/dist/internal/ListboxEntries.svelte +49 -0
- package/dist/internal/ListboxEntries.svelte.d.ts +35 -0
- package/dist/internal/media/MediaControls.svelte +163 -0
- package/dist/internal/media/MediaControls.svelte.d.ts +12 -0
- package/dist/internal/media/MediaStage.svelte +49 -0
- package/dist/internal/media/MediaStage.svelte.d.ts +11 -0
- package/dist/internal/media/MediaTile.svelte +36 -0
- package/dist/internal/media/MediaTile.svelte.d.ts +11 -0
- package/dist/internal/media/keys.d.ts +7 -0
- package/dist/internal/media/keys.js +51 -0
- package/dist/internal/media/labels.d.ts +19 -0
- package/dist/internal/media/labels.js +15 -0
- package/dist/internal/options.d.ts +9 -0
- package/dist/internal/options.js +20 -0
- package/dist/media-player.svelte.d.ts +61 -0
- package/dist/media-player.svelte.js +246 -0
- package/dist/media-utils.d.ts +112 -0
- package/dist/media-utils.js +205 -0
- package/dist/media-zoom.svelte.d.ts +33 -0
- package/dist/media-zoom.svelte.js +126 -0
- package/dist/signature.d.ts +55 -0
- package/dist/signature.js +52 -0
- package/dist/toaster.svelte.d.ts +24 -0
- package/dist/toaster.svelte.js +23 -0
- package/dist/token-colors.svelte.d.ts +49 -0
- package/dist/token-colors.svelte.js +165 -0
- package/dist/types.d.ts +62 -3
- package/dist/utils.d.ts +9 -0
- package/dist/utils.js +11 -0
- package/package.json +10 -4
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import type { HlsLoader } from '../hls.js';
|
|
3
|
+
import { type MediaPlayerLabels } from '../internal/media/labels.js';
|
|
4
|
+
import { type MediaSourceEntry } from '../media-utils.js';
|
|
5
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> {
|
|
6
|
+
src: string | MediaSourceEntry[];
|
|
7
|
+
/** The track or recording title; also names the player. */
|
|
8
|
+
title: string;
|
|
9
|
+
artist?: string;
|
|
10
|
+
/** Cover art. Decorative: the title already names the recording. */
|
|
11
|
+
artwork?: string;
|
|
12
|
+
loop?: boolean;
|
|
13
|
+
preload?: 'none' | 'metadata' | 'auto';
|
|
14
|
+
hlsLoader?: HlsLoader;
|
|
15
|
+
labels?: Partial<MediaPlayerLabels>;
|
|
16
|
+
onEnded?: () => void;
|
|
17
|
+
onTimeUpdate?: (seconds: number) => void;
|
|
18
|
+
class?: string;
|
|
19
|
+
ref?: HTMLDivElement | null;
|
|
20
|
+
mediaRef?: HTMLAudioElement | null;
|
|
21
|
+
}
|
|
22
|
+
declare const AudioPlayer: import("svelte").Component<Props, {}, "ref" | "mediaRef">;
|
|
23
|
+
type AudioPlayer = ReturnType<typeof AudioPlayer>;
|
|
24
|
+
export default AudioPlayer;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Avatar as AvatarPrimitive, type AvatarRootProps } from 'bits-ui';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { ControlSize } from '../types.js';
|
|
5
|
+
import { cx } from '../utils.js';
|
|
6
|
+
|
|
7
|
+
type LoadingStatus = 'loading' | 'loaded' | 'error';
|
|
8
|
+
|
|
9
|
+
type Props = Omit<
|
|
10
|
+
AvatarRootProps,
|
|
11
|
+
'child' | 'children' | 'loadingStatus' | 'onLoadingStatusChange' | 'ref'
|
|
12
|
+
> & {
|
|
13
|
+
/** Image URL. Without one, or while it loads or after it fails, the fallback shows. */
|
|
14
|
+
src?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Who this is. Names the avatar for assistive tech and supplies the fallback initials. Leave
|
|
17
|
+
* both `name` and `alt` unset when adjacent text already names the person — the avatar is
|
|
18
|
+
* then decorative.
|
|
19
|
+
*/
|
|
20
|
+
name?: string;
|
|
21
|
+
/** Accessible name when it should differ from `name`. */
|
|
22
|
+
alt?: string;
|
|
23
|
+
/** Fallback text. Defaults to the first letters of the first and last word of `name`. */
|
|
24
|
+
initials?: string;
|
|
25
|
+
/** Custom fallback, e.g. a generic person glyph. Replaces the initials. */
|
|
26
|
+
fallback?: Snippet;
|
|
27
|
+
/** Matches the control heights, so an avatar sits level with buttons of the same size. */
|
|
28
|
+
size?: ControlSize;
|
|
29
|
+
/** Milliseconds before the fallback appears, so a fast image never flashes initials first. */
|
|
30
|
+
delayMs?: number;
|
|
31
|
+
loadingStatus?: LoadingStatus;
|
|
32
|
+
onLoadingStatusChange?: (status: LoadingStatus) => void;
|
|
33
|
+
class?: string;
|
|
34
|
+
ref?: HTMLElement | null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
let {
|
|
38
|
+
src,
|
|
39
|
+
name,
|
|
40
|
+
alt,
|
|
41
|
+
initials,
|
|
42
|
+
fallback,
|
|
43
|
+
size = 'md',
|
|
44
|
+
delayMs = 0,
|
|
45
|
+
loadingStatus = $bindable('loading'),
|
|
46
|
+
onLoadingStatusChange,
|
|
47
|
+
class: className,
|
|
48
|
+
ref = $bindable(null),
|
|
49
|
+
...rest
|
|
50
|
+
}: Props = $props();
|
|
51
|
+
|
|
52
|
+
const label = $derived(alt ?? name);
|
|
53
|
+
const letters = $derived(
|
|
54
|
+
initials ??
|
|
55
|
+
(name ?? '')
|
|
56
|
+
.trim()
|
|
57
|
+
.split(/\s+/)
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.filter((_, index, words) => index === 0 || index === words.length - 1)
|
|
60
|
+
.map((word) => word[0])
|
|
61
|
+
.join('')
|
|
62
|
+
.toLocaleUpperCase()
|
|
63
|
+
);
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<!--
|
|
67
|
+
One accessible name on the root, whichever of image or initials is showing: otherwise the
|
|
68
|
+
announcement would change when the image finished loading, or vanish when it failed.
|
|
69
|
+
-->
|
|
70
|
+
<AvatarPrimitive.Root
|
|
71
|
+
bind:ref
|
|
72
|
+
bind:loadingStatus
|
|
73
|
+
{delayMs}
|
|
74
|
+
{onLoadingStatusChange}
|
|
75
|
+
class={cx('ldt-avatar', size !== 'md' && `ldt-avatar--${size}`, className)}
|
|
76
|
+
role={label ? 'img' : undefined}
|
|
77
|
+
aria-label={label}
|
|
78
|
+
aria-hidden={label ? undefined : 'true'}
|
|
79
|
+
{...rest}
|
|
80
|
+
>
|
|
81
|
+
{#if src}<AvatarPrimitive.Image class="ldt-avatar__image" {src} alt="" />{/if}
|
|
82
|
+
<AvatarPrimitive.Fallback class="ldt-avatar__fallback" aria-hidden="true">
|
|
83
|
+
{#if fallback}{@render fallback()}{:else}{letters}{/if}
|
|
84
|
+
</AvatarPrimitive.Fallback>
|
|
85
|
+
</AvatarPrimitive.Root>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type AvatarRootProps } from 'bits-ui';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { ControlSize } from '../types.js';
|
|
4
|
+
type LoadingStatus = 'loading' | 'loaded' | 'error';
|
|
5
|
+
type Props = Omit<AvatarRootProps, 'child' | 'children' | 'loadingStatus' | 'onLoadingStatusChange' | 'ref'> & {
|
|
6
|
+
/** Image URL. Without one, or while it loads or after it fails, the fallback shows. */
|
|
7
|
+
src?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Who this is. Names the avatar for assistive tech and supplies the fallback initials. Leave
|
|
10
|
+
* both `name` and `alt` unset when adjacent text already names the person — the avatar is
|
|
11
|
+
* then decorative.
|
|
12
|
+
*/
|
|
13
|
+
name?: string;
|
|
14
|
+
/** Accessible name when it should differ from `name`. */
|
|
15
|
+
alt?: string;
|
|
16
|
+
/** Fallback text. Defaults to the first letters of the first and last word of `name`. */
|
|
17
|
+
initials?: string;
|
|
18
|
+
/** Custom fallback, e.g. a generic person glyph. Replaces the initials. */
|
|
19
|
+
fallback?: Snippet;
|
|
20
|
+
/** Matches the control heights, so an avatar sits level with buttons of the same size. */
|
|
21
|
+
size?: ControlSize;
|
|
22
|
+
/** Milliseconds before the fallback appears, so a fast image never flashes initials first. */
|
|
23
|
+
delayMs?: number;
|
|
24
|
+
loadingStatus?: LoadingStatus;
|
|
25
|
+
onLoadingStatusChange?: (status: LoadingStatus) => void;
|
|
26
|
+
class?: string;
|
|
27
|
+
ref?: HTMLElement | null;
|
|
28
|
+
};
|
|
29
|
+
declare const Avatar: import("svelte").Component<Props, {}, "ref" | "loadingStatus">;
|
|
30
|
+
type Avatar = ReturnType<typeof Avatar>;
|
|
31
|
+
export default Avatar;
|
|
@@ -7,19 +7,38 @@
|
|
|
7
7
|
interface Props extends HTMLAttributes<HTMLSpanElement> {
|
|
8
8
|
variant?: BadgeVariant;
|
|
9
9
|
size?: BadgeSize;
|
|
10
|
+
/** A leading dot in the badge's colour — a status marker that still reads in greyscale. */
|
|
11
|
+
dot?: boolean;
|
|
12
|
+
/** A number to show instead of `children`, e.g. unread items. Above `max` it reads `99+`. */
|
|
13
|
+
count?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Visually hidden context read after the visible text, so "3" is heard as "3 unread
|
|
17
|
+
* messages". Needed whenever the number or word alone does not say what it counts.
|
|
18
|
+
*/
|
|
19
|
+
label?: string;
|
|
20
|
+
/** Announce changes (`role="status"`) — for a count that updates while the user watches. */
|
|
21
|
+
live?: boolean;
|
|
10
22
|
class?: string;
|
|
11
|
-
children
|
|
23
|
+
children?: Snippet;
|
|
12
24
|
ref?: HTMLSpanElement | null;
|
|
13
25
|
}
|
|
14
26
|
|
|
15
27
|
let {
|
|
16
28
|
variant = 'default',
|
|
17
29
|
size = 'inline',
|
|
30
|
+
dot = false,
|
|
31
|
+
count,
|
|
32
|
+
max = 99,
|
|
33
|
+
label,
|
|
34
|
+
live = false,
|
|
18
35
|
class: className,
|
|
19
36
|
children,
|
|
20
37
|
ref = $bindable(null),
|
|
21
38
|
...rest
|
|
22
39
|
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
const shown = $derived(count === undefined ? undefined : count > max ? `${max}+` : String(count));
|
|
23
42
|
</script>
|
|
24
43
|
|
|
25
44
|
<span
|
|
@@ -30,5 +49,12 @@
|
|
|
30
49
|
size !== 'inline' && `ldt-badge--${size}`,
|
|
31
50
|
className
|
|
32
51
|
)}
|
|
33
|
-
{
|
|
52
|
+
role={live ? 'status' : undefined}
|
|
53
|
+
aria-live={live ? 'polite' : undefined}
|
|
54
|
+
aria-atomic={live ? 'true' : undefined}
|
|
55
|
+
{...rest}
|
|
56
|
+
>{#if dot}<span class="ldt-badge__dot" aria-hidden="true"
|
|
57
|
+
></span>{/if}{#if shown !== undefined}{shown}{:else if children}{@render children()}{/if}{#if label}<span
|
|
58
|
+
class="ldt-sr-only">{` ${label}`}</span
|
|
59
|
+
>{/if}</span
|
|
34
60
|
>
|
|
@@ -4,8 +4,20 @@ import type { BadgeSize, BadgeVariant } from '../types.js';
|
|
|
4
4
|
interface Props extends HTMLAttributes<HTMLSpanElement> {
|
|
5
5
|
variant?: BadgeVariant;
|
|
6
6
|
size?: BadgeSize;
|
|
7
|
+
/** A leading dot in the badge's colour — a status marker that still reads in greyscale. */
|
|
8
|
+
dot?: boolean;
|
|
9
|
+
/** A number to show instead of `children`, e.g. unread items. Above `max` it reads `99+`. */
|
|
10
|
+
count?: number;
|
|
11
|
+
max?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Visually hidden context read after the visible text, so "3" is heard as "3 unread
|
|
14
|
+
* messages". Needed whenever the number or word alone does not say what it counts.
|
|
15
|
+
*/
|
|
16
|
+
label?: string;
|
|
17
|
+
/** Announce changes (`role="status"`) — for a count that updates while the user watches. */
|
|
18
|
+
live?: boolean;
|
|
7
19
|
class?: string;
|
|
8
|
-
children
|
|
20
|
+
children?: Snippet;
|
|
9
21
|
ref?: HTMLSpanElement | null;
|
|
10
22
|
}
|
|
11
23
|
declare const Badge: import("svelte").Component<Props, {}, "ref">;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<script lang="ts" generics="T extends string = string">
|
|
2
|
+
import { Combobox as ComboboxPrimitive, type ComboboxInputProps } from 'bits-ui';
|
|
3
|
+
import { untrack, type Snippet } from 'svelte';
|
|
4
|
+
import ListboxEntries from '../internal/ListboxEntries.svelte';
|
|
5
|
+
import {
|
|
6
|
+
filterEntries,
|
|
7
|
+
flattenOptions,
|
|
8
|
+
matchesQuery,
|
|
9
|
+
type OptionEntry,
|
|
10
|
+
} from '../internal/options.js';
|
|
11
|
+
import type { Option } from '../types.js';
|
|
12
|
+
import { cx } from '../utils.js';
|
|
13
|
+
import LiveRegion from './LiveRegion.svelte';
|
|
14
|
+
|
|
15
|
+
type Props = Omit<
|
|
16
|
+
ComboboxInputProps,
|
|
17
|
+
'value' | 'child' | 'children' | 'defaultValue' | 'clearOnDeselect' | 'oninput' | 'ref'
|
|
18
|
+
> & {
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
/** Submits the selected value with a form. */
|
|
22
|
+
name?: string;
|
|
23
|
+
/** The selected value, or `''` for none. */
|
|
24
|
+
value?: T | '';
|
|
25
|
+
/** Options, optionally in labelled groups. */
|
|
26
|
+
options: OptionEntry<T>[];
|
|
27
|
+
/** Accessible name when the combobox is not wrapped in a `Field` or `<label>`. */
|
|
28
|
+
label?: string;
|
|
29
|
+
/**
|
|
30
|
+
* How typing narrows the list. Defaults to a case- and accent-insensitive match on the label.
|
|
31
|
+
* Pass `false` when the options already come back filtered — from a server search driven by
|
|
32
|
+
* `onQueryChange`.
|
|
33
|
+
*/
|
|
34
|
+
filter?: ((option: Option<T>, query: string) => boolean) | false;
|
|
35
|
+
/** The text typed so far. Bindable. */
|
|
36
|
+
query?: string;
|
|
37
|
+
/** Called with the typed text, after `queryDebounce` — the hook for a server-side search. */
|
|
38
|
+
onQueryChange?: (query: string) => void;
|
|
39
|
+
/** Milliseconds to wait after the last keystroke before calling `onQueryChange`. */
|
|
40
|
+
queryDebounce?: number;
|
|
41
|
+
/** Results are on their way; shown in place of the list and announced. */
|
|
42
|
+
loading?: boolean;
|
|
43
|
+
loadingText?: string;
|
|
44
|
+
emptyText?: string;
|
|
45
|
+
/** What screen readers hear as the list narrows. */
|
|
46
|
+
resultsText?: (count: number) => string;
|
|
47
|
+
open?: boolean;
|
|
48
|
+
onOpenChange?: (open: boolean) => void;
|
|
49
|
+
onValueChange?: (value: T | '') => void;
|
|
50
|
+
/** Choosing the selected option again clears it. */
|
|
51
|
+
allowDeselect?: boolean;
|
|
52
|
+
/** Draws the full box instead of the default underline. */
|
|
53
|
+
boxed?: boolean;
|
|
54
|
+
/** Accessible name of the button that opens the list. */
|
|
55
|
+
triggerLabel?: string;
|
|
56
|
+
/** Custom row content. The check mark for the selected row is still drawn. */
|
|
57
|
+
item?: Snippet<[Option<T>, { selected: boolean; highlighted: boolean }]>;
|
|
58
|
+
/** Lands on the wrapper; the rest props land on the text input. */
|
|
59
|
+
class?: string;
|
|
60
|
+
/** Class for the portaled list. */
|
|
61
|
+
contentClass?: string;
|
|
62
|
+
/** The text input. */
|
|
63
|
+
ref?: HTMLElement | null;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
let {
|
|
67
|
+
value = $bindable(''),
|
|
68
|
+
options,
|
|
69
|
+
label,
|
|
70
|
+
filter,
|
|
71
|
+
query = $bindable(''),
|
|
72
|
+
onQueryChange,
|
|
73
|
+
queryDebounce = 0,
|
|
74
|
+
loading = false,
|
|
75
|
+
loadingText = 'Loading…',
|
|
76
|
+
emptyText = 'No results',
|
|
77
|
+
resultsText = (count) => `${count} ${count === 1 ? 'result' : 'results'}`,
|
|
78
|
+
open = $bindable(false),
|
|
79
|
+
onOpenChange,
|
|
80
|
+
onValueChange,
|
|
81
|
+
allowDeselect = false,
|
|
82
|
+
boxed = false,
|
|
83
|
+
triggerLabel = 'Show options',
|
|
84
|
+
item,
|
|
85
|
+
disabled = false,
|
|
86
|
+
required = false,
|
|
87
|
+
name,
|
|
88
|
+
class: className,
|
|
89
|
+
contentClass,
|
|
90
|
+
ref = $bindable(null),
|
|
91
|
+
...rest
|
|
92
|
+
}: Props = $props();
|
|
93
|
+
|
|
94
|
+
const flat = $derived(flattenOptions(options));
|
|
95
|
+
const selected = $derived(flat.find((option) => option.value === value));
|
|
96
|
+
const visible = $derived(
|
|
97
|
+
filter === false || !query
|
|
98
|
+
? options
|
|
99
|
+
: filterEntries(options, (option) => (filter ?? matchesQuery)(option, query))
|
|
100
|
+
);
|
|
101
|
+
const count = $derived(flattenOptions(visible).length);
|
|
102
|
+
|
|
103
|
+
// What the input shows. Bits owns it while the user types; closing snaps it back to the
|
|
104
|
+
// selection, so a half-typed query never masquerades as the value.
|
|
105
|
+
// Seeded from the selection so the server renders it too; effects never run during SSR.
|
|
106
|
+
let text = $state(untrack(() => selected?.label ?? ''));
|
|
107
|
+
$effect.pre(() => {
|
|
108
|
+
if (!open) text = selected?.label ?? '';
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
112
|
+
$effect(() => () => clearTimeout(timer));
|
|
113
|
+
|
|
114
|
+
function handleInput(event: Event & { currentTarget: HTMLInputElement }) {
|
|
115
|
+
query = event.currentTarget.value;
|
|
116
|
+
text = query;
|
|
117
|
+
if (!onQueryChange) return;
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
const next = query;
|
|
120
|
+
if (queryDebounce > 0) timer = setTimeout(() => onQueryChange(next), queryDebounce);
|
|
121
|
+
else onQueryChange(next);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function handleOpenChange(next: boolean) {
|
|
125
|
+
if (!next) query = '';
|
|
126
|
+
onOpenChange?.(next);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function handleValueChange(next: string) {
|
|
130
|
+
const option = flat.find((one) => one.value === next);
|
|
131
|
+
text = option?.label ?? '';
|
|
132
|
+
onValueChange?.(next as T | '');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const announcement = $derived(
|
|
136
|
+
!open ? '' : loading ? loadingText : count === 0 ? emptyText : resultsText(count)
|
|
137
|
+
);
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<ComboboxPrimitive.Root
|
|
141
|
+
type="single"
|
|
142
|
+
bind:value={() => value, (next) => (value = next as T | '')}
|
|
143
|
+
bind:open
|
|
144
|
+
inputValue={text}
|
|
145
|
+
items={flat}
|
|
146
|
+
{disabled}
|
|
147
|
+
{required}
|
|
148
|
+
{name}
|
|
149
|
+
{allowDeselect}
|
|
150
|
+
onOpenChange={handleOpenChange}
|
|
151
|
+
onValueChange={handleValueChange}
|
|
152
|
+
>
|
|
153
|
+
<div class={cx('ldt-combobox', boxed && 'ldt-combobox--boxed', className)}>
|
|
154
|
+
<ComboboxPrimitive.Input
|
|
155
|
+
bind:ref
|
|
156
|
+
class="ldt-combobox__input"
|
|
157
|
+
aria-label={label}
|
|
158
|
+
autocomplete="off"
|
|
159
|
+
{...rest}
|
|
160
|
+
oninput={handleInput}
|
|
161
|
+
/>
|
|
162
|
+
<ComboboxPrimitive.Trigger class="ldt-combobox__trigger" aria-label={triggerLabel}>
|
|
163
|
+
<svg viewBox="0 0 12 12" width="12" height="12" aria-hidden="true" focusable="false"
|
|
164
|
+
><path d="M2 4.5 6 8.5l4-4" fill="none" stroke="currentColor" stroke-width="1.5" /></svg
|
|
165
|
+
>
|
|
166
|
+
</ComboboxPrimitive.Trigger>
|
|
167
|
+
</div>
|
|
168
|
+
<ComboboxPrimitive.Portal>
|
|
169
|
+
<ComboboxPrimitive.Content class={cx('ldt-listbox', contentClass)} sideOffset={4}>
|
|
170
|
+
{#if loading}
|
|
171
|
+
<div class="ldt-listbox__empty" aria-hidden="true">{loadingText}</div>
|
|
172
|
+
{:else if count === 0}
|
|
173
|
+
<div class="ldt-listbox__empty" aria-hidden="true">{emptyText}</div>
|
|
174
|
+
{:else}
|
|
175
|
+
<ListboxEntries entries={visible} {item} />
|
|
176
|
+
{/if}
|
|
177
|
+
</ComboboxPrimitive.Content>
|
|
178
|
+
</ComboboxPrimitive.Portal>
|
|
179
|
+
</ComboboxPrimitive.Root>
|
|
180
|
+
<LiveRegion message={announcement} />
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Combobox as ComboboxPrimitive } from 'bits-ui';
|
|
2
|
+
import { type Snippet } from 'svelte';
|
|
3
|
+
import { type OptionEntry } from '../internal/options.js';
|
|
4
|
+
import type { Option } from '../types.js';
|
|
5
|
+
declare function $$render<T extends string = string>(): {
|
|
6
|
+
props: Omit<ComboboxPrimitive.InputProps, "value" | "children" | "oninput" | "ref" | "child" | "defaultValue" | "clearOnDeselect"> & {
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
required?: boolean;
|
|
9
|
+
/** Submits the selected value with a form. */
|
|
10
|
+
name?: string;
|
|
11
|
+
/** The selected value, or `''` for none. */
|
|
12
|
+
value?: T | "";
|
|
13
|
+
/** Options, optionally in labelled groups. */
|
|
14
|
+
options: OptionEntry<T>[];
|
|
15
|
+
/** Accessible name when the combobox is not wrapped in a `Field` or `<label>`. */
|
|
16
|
+
label?: string;
|
|
17
|
+
/**
|
|
18
|
+
* How typing narrows the list. Defaults to a case- and accent-insensitive match on the label.
|
|
19
|
+
* Pass `false` when the options already come back filtered — from a server search driven by
|
|
20
|
+
* `onQueryChange`.
|
|
21
|
+
*/
|
|
22
|
+
filter?: ((option: Option<T>, query: string) => boolean) | false;
|
|
23
|
+
/** The text typed so far. Bindable. */
|
|
24
|
+
query?: string;
|
|
25
|
+
/** Called with the typed text, after `queryDebounce` — the hook for a server-side search. */
|
|
26
|
+
onQueryChange?: (query: string) => void;
|
|
27
|
+
/** Milliseconds to wait after the last keystroke before calling `onQueryChange`. */
|
|
28
|
+
queryDebounce?: number;
|
|
29
|
+
/** Results are on their way; shown in place of the list and announced. */
|
|
30
|
+
loading?: boolean;
|
|
31
|
+
loadingText?: string;
|
|
32
|
+
emptyText?: string;
|
|
33
|
+
/** What screen readers hear as the list narrows. */
|
|
34
|
+
resultsText?: (count: number) => string;
|
|
35
|
+
open?: boolean;
|
|
36
|
+
onOpenChange?: (open: boolean) => void;
|
|
37
|
+
onValueChange?: (value: T | "") => void;
|
|
38
|
+
/** Choosing the selected option again clears it. */
|
|
39
|
+
allowDeselect?: boolean;
|
|
40
|
+
/** Draws the full box instead of the default underline. */
|
|
41
|
+
boxed?: boolean;
|
|
42
|
+
/** Accessible name of the button that opens the list. */
|
|
43
|
+
triggerLabel?: string;
|
|
44
|
+
/** Custom row content. The check mark for the selected row is still drawn. */
|
|
45
|
+
item?: Snippet<[Option<T>, {
|
|
46
|
+
selected: boolean;
|
|
47
|
+
highlighted: boolean;
|
|
48
|
+
}]>;
|
|
49
|
+
/** Lands on the wrapper; the rest props land on the text input. */
|
|
50
|
+
class?: string;
|
|
51
|
+
/** Class for the portaled list. */
|
|
52
|
+
contentClass?: string;
|
|
53
|
+
/** The text input. */
|
|
54
|
+
ref?: HTMLElement | null;
|
|
55
|
+
};
|
|
56
|
+
exports: {};
|
|
57
|
+
bindings: "value" | "ref" | "open" | "query";
|
|
58
|
+
slots: {};
|
|
59
|
+
events: {};
|
|
60
|
+
};
|
|
61
|
+
declare class __sveltets_Render<T extends string = string> {
|
|
62
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
63
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
64
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
65
|
+
bindings(): "value" | "ref" | "open" | "query";
|
|
66
|
+
exports(): {};
|
|
67
|
+
}
|
|
68
|
+
interface $$IsomorphicComponent {
|
|
69
|
+
new <T extends string = string>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
70
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
71
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
72
|
+
<T extends string = string>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
73
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
74
|
+
}
|
|
75
|
+
declare const Combobox: $$IsomorphicComponent;
|
|
76
|
+
type Combobox<T extends string = string> = InstanceType<typeof Combobox<T>>;
|
|
77
|
+
export default Combobox;
|