@sveltier/ui 0.0.0-stage → 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +82 -2
- package/dist/components/Alert/Alert.svelte +88 -0
- package/dist/components/Alert/Alert.svelte.d.ts +4 -0
- package/dist/components/Alert/alert.css +78 -0
- package/dist/components/Alert/index.d.ts +2 -0
- package/dist/components/Alert/index.js +1 -0
- package/dist/components/Alert/types.d.ts +67 -0
- package/dist/components/Alert/types.js +1 -0
- package/dist/components/Avatar/Avatar.svelte +47 -0
- package/dist/components/Avatar/Avatar.svelte.d.ts +4 -0
- package/dist/components/Avatar/avatar.css +34 -0
- package/dist/components/Avatar/index.d.ts +2 -0
- package/dist/components/Avatar/index.js +1 -0
- package/dist/components/Avatar/types.d.ts +47 -0
- package/dist/components/Avatar/types.js +1 -0
- package/dist/components/Badge/Badge.svelte +53 -0
- package/dist/components/Badge/Badge.svelte.d.ts +4 -0
- package/dist/components/Badge/badge.css +57 -0
- package/dist/components/Badge/index.d.ts +2 -0
- package/dist/components/Badge/index.js +1 -0
- package/dist/components/Badge/types.d.ts +43 -0
- package/dist/components/Badge/types.js +1 -0
- package/dist/components/Button/Button.svelte +85 -0
- package/dist/components/Button/Button.svelte.d.ts +4 -0
- package/dist/components/Button/button.css +132 -0
- package/dist/components/Button/index.d.ts +2 -0
- package/dist/components/Button/index.js +1 -0
- package/dist/components/Button/types.d.ts +76 -0
- package/dist/components/Button/types.js +1 -0
- package/dist/components/ButtonGroup/ButtonGroup.svelte +61 -0
- package/dist/components/ButtonGroup/ButtonGroup.svelte.d.ts +4 -0
- package/dist/components/ButtonGroup/button-group.css +39 -0
- package/dist/components/ButtonGroup/index.d.ts +2 -0
- package/dist/components/ButtonGroup/index.js +1 -0
- package/dist/components/ButtonGroup/types.d.ts +48 -0
- package/dist/components/ButtonGroup/types.js +1 -0
- package/dist/components/Card/Card.svelte +53 -0
- package/dist/components/Card/Card.svelte.d.ts +4 -0
- package/dist/components/Card/card.css +62 -0
- package/dist/components/Card/index.d.ts +2 -0
- package/dist/components/Card/index.js +1 -0
- package/dist/components/Card/types.d.ts +56 -0
- package/dist/components/Card/types.js +1 -0
- package/dist/components/Checkbox/Checkbox.svelte +105 -0
- package/dist/components/Checkbox/Checkbox.svelte.d.ts +4 -0
- package/dist/components/Checkbox/checkbox.css +138 -0
- package/dist/components/Checkbox/index.d.ts +2 -0
- package/dist/components/Checkbox/index.js +1 -0
- package/dist/components/Checkbox/types.d.ts +76 -0
- package/dist/components/Checkbox/types.js +1 -0
- package/dist/components/Chip/Chip.svelte +109 -0
- package/dist/components/Chip/Chip.svelte.d.ts +4 -0
- package/dist/components/Chip/chip.css +101 -0
- package/dist/components/Chip/index.d.ts +2 -0
- package/dist/components/Chip/index.js +1 -0
- package/dist/components/Chip/types.d.ts +69 -0
- package/dist/components/Chip/types.js +1 -0
- package/dist/components/Choice/Choice.svelte +60 -0
- package/dist/components/Choice/Choice.svelte.d.ts +4 -0
- package/dist/components/Choice/choice.css +75 -0
- package/dist/components/Choice/types.d.ts +43 -0
- package/dist/components/Choice/types.js +1 -0
- package/dist/components/Divider/Divider.svelte +19 -0
- package/dist/components/Divider/Divider.svelte.d.ts +4 -0
- package/dist/components/Divider/divider.css +23 -0
- package/dist/components/Divider/index.d.ts +2 -0
- package/dist/components/Divider/index.js +1 -0
- package/dist/components/Divider/types.d.ts +10 -0
- package/dist/components/Divider/types.js +1 -0
- package/dist/components/Field/Field.svelte +95 -0
- package/dist/components/Field/Field.svelte.d.ts +4 -0
- package/dist/components/Field/field.css +210 -0
- package/dist/components/Field/index.d.ts +3 -0
- package/dist/components/Field/index.js +2 -0
- package/dist/components/Field/types.d.ts +109 -0
- package/dist/components/Field/types.js +2 -0
- package/dist/components/Form/Form.svelte +93 -0
- package/dist/components/Form/Form.svelte.d.ts +8 -0
- package/dist/components/Form/form.css +16 -0
- package/dist/components/Form/index.d.ts +2 -0
- package/dist/components/Form/index.js +1 -0
- package/dist/components/Form/types.d.ts +38 -0
- package/dist/components/Form/types.js +1 -0
- package/dist/components/Icon/Icon.svelte +38 -0
- package/dist/components/Icon/Icon.svelte.d.ts +4 -0
- package/dist/components/Icon/icon.css +33 -0
- package/dist/components/Icon/index.d.ts +2 -0
- package/dist/components/Icon/index.js +1 -0
- package/dist/components/Icon/types.d.ts +34 -0
- package/dist/components/Icon/types.js +1 -0
- package/dist/components/List/List.svelte +39 -0
- package/dist/components/List/List.svelte.d.ts +4 -0
- package/dist/components/List/ListItem.svelte +57 -0
- package/dist/components/List/ListItem.svelte.d.ts +4 -0
- package/dist/components/List/index.d.ts +3 -0
- package/dist/components/List/index.js +2 -0
- package/dist/components/List/list.css +127 -0
- package/dist/components/List/types.d.ts +67 -0
- package/dist/components/List/types.js +1 -0
- package/dist/components/NativeSelect/NativeSelect.svelte +110 -0
- package/dist/components/NativeSelect/NativeSelect.svelte.d.ts +25 -0
- package/dist/components/NativeSelect/index.d.ts +2 -0
- package/dist/components/NativeSelect/index.js +1 -0
- package/dist/components/NativeSelect/native-select.css +45 -0
- package/dist/components/NativeSelect/types.d.ts +58 -0
- package/dist/components/NativeSelect/types.js +1 -0
- package/dist/components/NumberField/NumberField.svelte +246 -0
- package/dist/components/NumberField/NumberField.svelte.d.ts +4 -0
- package/dist/components/NumberField/index.d.ts +2 -0
- package/dist/components/NumberField/index.js +1 -0
- package/dist/components/NumberField/number-field.css +74 -0
- package/dist/components/NumberField/types.d.ts +73 -0
- package/dist/components/NumberField/types.js +1 -0
- package/dist/components/Progress/ProgressCircular.svelte +61 -0
- package/dist/components/Progress/ProgressCircular.svelte.d.ts +4 -0
- package/dist/components/Progress/ProgressLinear.svelte +44 -0
- package/dist/components/Progress/ProgressLinear.svelte.d.ts +4 -0
- package/dist/components/Progress/index.d.ts +3 -0
- package/dist/components/Progress/index.js +2 -0
- package/dist/components/Progress/progress-circular.css +61 -0
- package/dist/components/Progress/progress-linear.css +67 -0
- package/dist/components/Progress/types.d.ts +53 -0
- package/dist/components/Progress/types.js +1 -0
- package/dist/components/RadioGroup/RadioGroup.svelte +149 -0
- package/dist/components/RadioGroup/RadioGroup.svelte.d.ts +25 -0
- package/dist/components/RadioGroup/index.d.ts +2 -0
- package/dist/components/RadioGroup/index.js +1 -0
- package/dist/components/RadioGroup/radio-group.css +139 -0
- package/dist/components/RadioGroup/types.d.ts +93 -0
- package/dist/components/RadioGroup/types.js +1 -0
- package/dist/components/SegmentedControl/SegmentedControl.svelte +70 -0
- package/dist/components/SegmentedControl/SegmentedControl.svelte.d.ts +25 -0
- package/dist/components/SegmentedControl/index.d.ts +2 -0
- package/dist/components/SegmentedControl/index.js +1 -0
- package/dist/components/SegmentedControl/segmented-control.css +68 -0
- package/dist/components/SegmentedControl/types.d.ts +60 -0
- package/dist/components/SegmentedControl/types.js +1 -0
- package/dist/components/Skeleton/Skeleton.svelte +33 -0
- package/dist/components/Skeleton/Skeleton.svelte.d.ts +4 -0
- package/dist/components/Skeleton/index.d.ts +2 -0
- package/dist/components/Skeleton/index.js +1 -0
- package/dist/components/Skeleton/skeleton.css +70 -0
- package/dist/components/Skeleton/types.d.ts +25 -0
- package/dist/components/Skeleton/types.js +1 -0
- package/dist/components/Slider/Slider.svelte +153 -0
- package/dist/components/Slider/Slider.svelte.d.ts +4 -0
- package/dist/components/Slider/index.d.ts +2 -0
- package/dist/components/Slider/index.js +1 -0
- package/dist/components/Slider/slider.css +206 -0
- package/dist/components/Slider/types.d.ts +102 -0
- package/dist/components/Slider/types.js +1 -0
- package/dist/components/Switch/Switch.svelte +94 -0
- package/dist/components/Switch/Switch.svelte.d.ts +4 -0
- package/dist/components/Switch/index.d.ts +2 -0
- package/dist/components/Switch/index.js +1 -0
- package/dist/components/Switch/switch.css +118 -0
- package/dist/components/Switch/types.d.ts +67 -0
- package/dist/components/Switch/types.js +1 -0
- package/dist/components/Tabs/Tabs.svelte +93 -0
- package/dist/components/Tabs/Tabs.svelte.d.ts +25 -0
- package/dist/components/Tabs/index.d.ts +2 -0
- package/dist/components/Tabs/index.js +1 -0
- package/dist/components/Tabs/tabs.css +73 -0
- package/dist/components/Tabs/types.d.ts +59 -0
- package/dist/components/Tabs/types.js +1 -0
- package/dist/components/TextField/TextField.svelte +115 -0
- package/dist/components/TextField/TextField.svelte.d.ts +4 -0
- package/dist/components/TextField/index.d.ts +2 -0
- package/dist/components/TextField/index.js +1 -0
- package/dist/components/TextField/types.d.ts +57 -0
- package/dist/components/TextField/types.js +1 -0
- package/dist/components/Textarea/Textarea.svelte +116 -0
- package/dist/components/Textarea/Textarea.svelte.d.ts +4 -0
- package/dist/components/Textarea/index.d.ts +2 -0
- package/dist/components/Textarea/index.js +1 -0
- package/dist/components/Textarea/textarea.css +35 -0
- package/dist/components/Textarea/types.d.ts +60 -0
- package/dist/components/Textarea/types.js +1 -0
- package/dist/core/defaults/DefaultsProvider.svelte +30 -0
- package/dist/core/defaults/DefaultsProvider.svelte.d.ts +13 -0
- package/dist/core/defaults/context.d.ts +45 -0
- package/dist/core/defaults/context.js +38 -0
- package/dist/core/display/display.d.ts +39 -0
- package/dist/core/display/display.js +53 -0
- package/dist/core/errors/codes.d.ts +112 -0
- package/dist/core/errors/codes.js +96 -0
- package/dist/core/errors/errors.d.ts +8 -0
- package/dist/core/errors/errors.js +7 -0
- package/dist/core/form/constraints.d.ts +15 -0
- package/dist/core/form/constraints.js +42 -0
- package/dist/core/form/context.d.ts +19 -0
- package/dist/core/form/context.js +4 -0
- package/dist/core/form/field.svelte.d.ts +34 -0
- package/dist/core/form/field.svelte.js +66 -0
- package/dist/core/form/number.d.ts +24 -0
- package/dist/core/form/number.js +71 -0
- package/dist/core/form/types.d.ts +22 -0
- package/dist/core/form/types.js +1 -0
- package/dist/core/locale/locale.d.ts +55 -0
- package/dist/core/locale/locale.js +80 -0
- package/dist/core/props/classes.d.ts +21 -0
- package/dist/core/props/classes.js +44 -0
- package/dist/core/props/element.d.ts +18 -0
- package/dist/core/props/element.js +1 -0
- package/dist/core/props/types.d.ts +47 -0
- package/dist/core/props/types.js +16 -0
- package/dist/core/provider/UIProvider.svelte +60 -0
- package/dist/core/provider/UIProvider.svelte.d.ts +29 -0
- package/dist/core/theme/ThemeProvider.svelte +178 -0
- package/dist/core/theme/ThemeProvider.svelte.d.ts +25 -0
- package/dist/core/theme/color.d.ts +23 -0
- package/dist/core/theme/color.js +54 -0
- package/dist/core/theme/context.d.ts +25 -0
- package/dist/core/theme/context.js +14 -0
- package/dist/core/theme/css.d.ts +25 -0
- package/dist/core/theme/css.js +69 -0
- package/dist/core/theme/define.d.ts +11 -0
- package/dist/core/theme/define.js +21 -0
- package/dist/core/theme/resolve.d.ts +6 -0
- package/dist/core/theme/resolve.js +18 -0
- package/dist/core/theme/theme-provider.css +13 -0
- package/dist/core/theme/types.d.ts +35 -0
- package/dist/core/theme/types.js +61 -0
- package/dist/dev/off.d.ts +4 -0
- package/dist/dev/off.js +3 -0
- package/dist/dev/on.d.ts +8 -0
- package/dist/dev/on.js +33 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +72 -0
- package/dist/primitives/anchored/anchored.d.ts +39 -0
- package/dist/primitives/anchored/anchored.js +104 -0
- package/dist/primitives/anchored/fallback.d.ts +31 -0
- package/dist/primitives/anchored/fallback.js +73 -0
- package/dist/primitives/focus/focus-trap.d.ts +25 -0
- package/dist/primitives/focus/focus-trap.js +86 -0
- package/dist/primitives/focus/roving-focus.d.ts +22 -0
- package/dist/primitives/focus/roving-focus.js +86 -0
- package/dist/primitives/focus/tabbable.d.ts +9 -0
- package/dist/primitives/focus/tabbable.js +75 -0
- package/dist/primitives/inert/inert-others.d.ts +12 -0
- package/dist/primitives/inert/inert-others.js +44 -0
- package/dist/primitives/keyboard/keymap.d.ts +17 -0
- package/dist/primitives/keyboard/keymap.js +39 -0
- package/dist/primitives/keyboard/typeahead.d.ts +18 -0
- package/dist/primitives/keyboard/typeahead.js +50 -0
- package/dist/primitives/layer/dismissable.d.ts +26 -0
- package/dist/primitives/layer/dismissable.js +84 -0
- package/dist/primitives/motion/easing.d.ts +9 -0
- package/dist/primitives/motion/easing.js +46 -0
- package/dist/primitives/motion/motion.d.ts +59 -0
- package/dist/primitives/motion/motion.js +80 -0
- package/dist/primitives/portal/Portal.svelte +37 -0
- package/dist/primitives/portal/Portal.svelte.d.ts +9 -0
- package/dist/primitives/portal/PortalContent.svelte +15 -0
- package/dist/primitives/portal/PortalContent.svelte.d.ts +9 -0
- package/dist/primitives/portal/portal.css +8 -0
- package/dist/primitives/scroll-lock/scroll-lock.d.ts +10 -0
- package/dist/primitives/scroll-lock/scroll-lock.js +33 -0
- package/dist/primitives/virtual/virtual-list.svelte.d.ts +65 -0
- package/dist/primitives/virtual/virtual-list.svelte.js +291 -0
- package/dist/styles/base.css +44 -0
- package/dist/styles/props.css +137 -0
- package/dist/styles/tokens.css +84 -0
- package/dist/styles/visually-hidden.css +16 -0
- package/dist/themes/aurora.d.ts +13 -0
- package/dist/themes/aurora.js +103 -0
- package/dist/themes/heritage.d.ts +13 -0
- package/dist/themes/heritage.js +102 -0
- package/dist/themes/maison.d.ts +13 -0
- package/dist/themes/maison.js +99 -0
- package/dist/themes/milano.d.ts +13 -0
- package/dist/themes/milano.js +101 -0
- package/dist/themes/monolith.d.ts +13 -0
- package/dist/themes/monolith.js +101 -0
- package/package.json +87 -5
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const disabled = (item) => item.matches(':disabled') || item.getAttribute('aria-disabled') === 'true';
|
|
2
|
+
const label = (item) => (item.dataset.svLabel ?? item.getAttribute('aria-label') ?? item.textContent ?? '')
|
|
3
|
+
.trim()
|
|
4
|
+
.toLowerCase();
|
|
5
|
+
/**
|
|
6
|
+
* Typing moves focus to the next item whose label starts with what was typed, as menus,
|
|
7
|
+
* listboxes and trees do. Repeating one character cycles through the items starting with it.
|
|
8
|
+
* Labels come from `data-sv-label`, `aria-label`, then the text.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* <ul role="listbox" {@attach rovingFocus({ orientation: 'vertical' })} {@attach typeahead()}>
|
|
12
|
+
*/
|
|
13
|
+
export function typeahead({ items = '[data-sv-item]', timeout = 500, onMatch = (item) => item.focus() } = {}) {
|
|
14
|
+
return (container) => {
|
|
15
|
+
let search = '';
|
|
16
|
+
let timer;
|
|
17
|
+
function onkeydown(event) {
|
|
18
|
+
const key = event.key;
|
|
19
|
+
if (event.defaultPrevented ||
|
|
20
|
+
event.ctrlKey ||
|
|
21
|
+
event.metaKey ||
|
|
22
|
+
event.altKey ||
|
|
23
|
+
key.length !== 1 ||
|
|
24
|
+
(key === ' ' && search === '')) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
timer = setTimeout(() => (search = ''), timeout);
|
|
29
|
+
search += key.toLowerCase();
|
|
30
|
+
const list = Array.from(container.querySelectorAll(items)).filter((item) => !disabled(item));
|
|
31
|
+
// "aaa" cycles through the items starting with "a".
|
|
32
|
+
const repeated = [...search].every((character) => character === search[0]);
|
|
33
|
+
const term = repeated ? search[0] : search;
|
|
34
|
+
const current = list.indexOf(document.activeElement);
|
|
35
|
+
// A new or one-letter search starts after the focused item; a longer one includes it.
|
|
36
|
+
const from = term.length === 1 ? current + 1 : Math.max(current, 0);
|
|
37
|
+
const ordered = [...list.slice(from), ...list.slice(0, from)];
|
|
38
|
+
const match = ordered.find((item) => label(item).startsWith(term));
|
|
39
|
+
if (match) {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
onMatch(match);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
container.addEventListener('keydown', onkeydown);
|
|
45
|
+
return () => {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
container.removeEventListener('keydown', onkeydown);
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Attachment } from 'svelte/attachments';
|
|
2
|
+
export declare const DISMISS_REASONS: readonly ["escape", "outside", "focus"];
|
|
3
|
+
export type DismissReason = (typeof DISMISS_REASONS)[number];
|
|
4
|
+
type Elements = Array<Element | null | undefined>;
|
|
5
|
+
export interface DismissableOptions {
|
|
6
|
+
/** Called when the user dismisses the layer. The component decides whether to close. */
|
|
7
|
+
onDismiss: (reason: DismissReason) => void;
|
|
8
|
+
/** Dismiss on Escape, unless something inside handled it first. Defaults to `true`. */
|
|
9
|
+
escape?: boolean;
|
|
10
|
+
/** Dismiss on a pointer press outside. Defaults to `true`. */
|
|
11
|
+
outside?: boolean;
|
|
12
|
+
/** Dismiss when focus moves outside, as menus do on Tab. Defaults to `false`. */
|
|
13
|
+
focusOutside?: boolean;
|
|
14
|
+
/** Elements that count as inside, such as the trigger, which toggles instead. */
|
|
15
|
+
inside?: Elements | (() => Elements);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Registers the element as a layer: Escape, a press outside, or (opt-in) focus leaving it
|
|
19
|
+
* calls `onDismiss` with the reason. Layers stack, and only the topmost one is dismissed, so
|
|
20
|
+
* nested popovers, sheets and dialogs close one at a time.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* <div popover="manual" {@attach dismissable({ onDismiss: () => (open = false), inside: [trigger] })}>
|
|
24
|
+
*/
|
|
25
|
+
export declare function dismissable(options: DismissableOptions): Attachment<HTMLElement>;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export const DISMISS_REASONS = ['escape', 'outside', 'focus'];
|
|
2
|
+
// Active layers, topmost last. One set of document listeners routes each event to the
|
|
3
|
+
// topmost layer only: with a listener per layer, a layer closed by an event would leave the
|
|
4
|
+
// stack (Svelte flushes between listeners) and the next layer would react to the same event.
|
|
5
|
+
const layers = [];
|
|
6
|
+
let touch = false;
|
|
7
|
+
function isInside({ element, options }, target) {
|
|
8
|
+
if (!(target instanceof Node))
|
|
9
|
+
return false;
|
|
10
|
+
const inside = options.inside ?? [];
|
|
11
|
+
return [element, ...(typeof inside === 'function' ? inside() : inside)].some((node) => node?.contains(target));
|
|
12
|
+
}
|
|
13
|
+
// A press on the page's own scrollbar is not a press outside.
|
|
14
|
+
const onScrollbar = (event) => event.target === document.documentElement &&
|
|
15
|
+
(event.clientX >= document.documentElement.clientWidth ||
|
|
16
|
+
event.clientY >= document.documentElement.clientHeight);
|
|
17
|
+
function dismissOutside(event) {
|
|
18
|
+
const top = layers.at(-1);
|
|
19
|
+
if (top && top.options.outside !== false && !isInside(top, event.target) && !onScrollbar(event)) {
|
|
20
|
+
top.options.onDismiss('outside');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Escape listens in the bubble phase, so handlers inside run first and can prevent it.
|
|
24
|
+
function onkeydown(event) {
|
|
25
|
+
const top = layers.at(-1);
|
|
26
|
+
if (event.key === 'Escape' && !event.defaultPrevented && top && top.options.escape !== false) {
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
top.options.onDismiss('escape');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function onpointerdown(event) {
|
|
32
|
+
touch = event.pointerType === 'touch';
|
|
33
|
+
if (!touch && event.button === 0)
|
|
34
|
+
dismissOutside(event);
|
|
35
|
+
}
|
|
36
|
+
// Touch dismisses on the click that ends a tap, never on a press that starts a scroll.
|
|
37
|
+
function onclick(event) {
|
|
38
|
+
if (touch)
|
|
39
|
+
dismissOutside(event);
|
|
40
|
+
touch = false;
|
|
41
|
+
}
|
|
42
|
+
function onfocusin(event) {
|
|
43
|
+
const top = layers.at(-1);
|
|
44
|
+
if (top?.options.focusOutside && !isInside(top, event.target))
|
|
45
|
+
top.options.onDismiss('focus');
|
|
46
|
+
}
|
|
47
|
+
function listen() {
|
|
48
|
+
document.addEventListener('keydown', onkeydown);
|
|
49
|
+
document.addEventListener('pointerdown', onpointerdown, true);
|
|
50
|
+
document.addEventListener('click', onclick, true);
|
|
51
|
+
document.addEventListener('focusin', onfocusin, true);
|
|
52
|
+
}
|
|
53
|
+
function unlisten() {
|
|
54
|
+
document.removeEventListener('keydown', onkeydown);
|
|
55
|
+
document.removeEventListener('pointerdown', onpointerdown, true);
|
|
56
|
+
document.removeEventListener('click', onclick, true);
|
|
57
|
+
document.removeEventListener('focusin', onfocusin, true);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Registers the element as a layer: Escape, a press outside, or (opt-in) focus leaving it
|
|
61
|
+
* calls `onDismiss` with the reason. Layers stack, and only the topmost one is dismissed, so
|
|
62
|
+
* nested popovers, sheets and dialogs close one at a time.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* <div popover="manual" {@attach dismissable({ onDismiss: () => (open = false), inside: [trigger] })}>
|
|
66
|
+
*/
|
|
67
|
+
export function dismissable(options) {
|
|
68
|
+
return (element) => {
|
|
69
|
+
const layer = { element, options };
|
|
70
|
+
// A layer mounted around active layers (children attach first) goes beneath them.
|
|
71
|
+
const inner = layers.findIndex((other) => element.contains(other.element));
|
|
72
|
+
if (inner < 0)
|
|
73
|
+
layers.push(layer);
|
|
74
|
+
else
|
|
75
|
+
layers.splice(inner, 0, layer);
|
|
76
|
+
if (layers.length === 1)
|
|
77
|
+
listen();
|
|
78
|
+
return () => {
|
|
79
|
+
layers.splice(layers.indexOf(layer), 1);
|
|
80
|
+
if (layers.length === 0)
|
|
81
|
+
unlisten();
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An easing function from cubic-bezier control points, as CSS `cubic-bezier()` defines it.
|
|
3
|
+
* Use it where a function is needed (Svelte transitions, Tween); CSS and `element.animate()`
|
|
4
|
+
* take the string directly.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const standard = cubicBezier(0.236, 0, 0, 1);
|
|
8
|
+
*/
|
|
9
|
+
export declare function cubicBezier(x1: number, y1: number, x2: number, y2: number): (t: number) => number;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An easing function from cubic-bezier control points, as CSS `cubic-bezier()` defines it.
|
|
3
|
+
* Use it where a function is needed (Svelte transitions, Tween); CSS and `element.animate()`
|
|
4
|
+
* take the string directly.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const standard = cubicBezier(0.236, 0, 0, 1);
|
|
8
|
+
*/
|
|
9
|
+
export function cubicBezier(x1, y1, x2, y2) {
|
|
10
|
+
// Polynomial coefficients of x(s) and y(s) for the curve parameter s.
|
|
11
|
+
const cx = 3 * x1;
|
|
12
|
+
const bx = 3 * (x2 - x1) - cx;
|
|
13
|
+
const ax = 1 - cx - bx;
|
|
14
|
+
const cy = 3 * y1;
|
|
15
|
+
const by = 3 * (y2 - y1) - cy;
|
|
16
|
+
const ay = 1 - cy - by;
|
|
17
|
+
const x = (s) => ((ax * s + bx) * s + cx) * s;
|
|
18
|
+
const y = (s) => ((ay * s + by) * s + cy) * s;
|
|
19
|
+
const slope = (s) => (3 * ax * s + 2 * bx) * s + cx;
|
|
20
|
+
return (t) => {
|
|
21
|
+
if (t <= 0 || t >= 1)
|
|
22
|
+
return t <= 0 ? 0 : 1;
|
|
23
|
+
// Newton's method from s = t, then bisection if the slope is too flat to trust.
|
|
24
|
+
let s = t;
|
|
25
|
+
for (let i = 0; i < 8; i++) {
|
|
26
|
+
const error = x(s) - t;
|
|
27
|
+
if (Math.abs(error) < 1e-6)
|
|
28
|
+
return y(s);
|
|
29
|
+
const d = slope(s);
|
|
30
|
+
if (Math.abs(d) < 1e-6)
|
|
31
|
+
break;
|
|
32
|
+
s -= error / d;
|
|
33
|
+
}
|
|
34
|
+
let low = 0;
|
|
35
|
+
let high = 1;
|
|
36
|
+
s = t;
|
|
37
|
+
for (let i = 0; i < 32 && high - low > 1e-6; i++) {
|
|
38
|
+
if (x(s) < t)
|
|
39
|
+
low = s;
|
|
40
|
+
else
|
|
41
|
+
high = s;
|
|
42
|
+
s = (low + high) / 2;
|
|
43
|
+
}
|
|
44
|
+
return y(s);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Spring } from 'svelte/motion';
|
|
2
|
+
import type { TransitionConfig } from 'svelte/transition';
|
|
3
|
+
/** The theme's timing at an element: durations in ms and the easing as a CSS value. */
|
|
4
|
+
export interface Motion {
|
|
5
|
+
fast: number;
|
|
6
|
+
normal: number;
|
|
7
|
+
slow: number;
|
|
8
|
+
ease: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Reads the theme's timing where the element is: the theme in scope, and near-zero
|
|
12
|
+
* durations while the user prefers reduced motion. For `element.animate()` and CSS.
|
|
13
|
+
*/
|
|
14
|
+
export declare function motion(element: Element): Motion;
|
|
15
|
+
/**
|
|
16
|
+
* A CSS easing as a function, for Svelte transitions and `Tween`. Understands `cubic-bezier()`
|
|
17
|
+
* and the keywords; anything else (`steps()`, `linear()`) falls back to `ease`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function easing(ease: string): (t: number) => number;
|
|
20
|
+
export interface TransitionOptions {
|
|
21
|
+
/** In ms. Defaults to the theme's normal duration. */
|
|
22
|
+
duration?: number;
|
|
23
|
+
delay?: number;
|
|
24
|
+
}
|
|
25
|
+
/** Fades in and out with the theme's timing. */
|
|
26
|
+
export declare function fade(node: Element, options?: TransitionOptions): TransitionConfig;
|
|
27
|
+
/** Fades in while moving up `distance` px (8 by default), with the theme's timing. */
|
|
28
|
+
export declare function rise(node: Element, { distance, ...options }?: TransitionOptions & {
|
|
29
|
+
distance?: number;
|
|
30
|
+
}): TransitionConfig;
|
|
31
|
+
/** Fades in while growing from `scale` (94.4%, 1 − φ⁻⁶), with the theme's timing. */
|
|
32
|
+
export declare function pop(node: Element, { scale, ...options }?: TransitionOptions & {
|
|
33
|
+
scale?: number;
|
|
34
|
+
}): TransitionConfig;
|
|
35
|
+
/** Spring presets on the φ series: stiffness 1/φⁿ, damping 1 − 1/φⁿ. */
|
|
36
|
+
export declare const SPRINGS: {
|
|
37
|
+
readonly snappy: {
|
|
38
|
+
readonly stiffness: 0.236;
|
|
39
|
+
readonly damping: 0.764;
|
|
40
|
+
};
|
|
41
|
+
readonly standard: {
|
|
42
|
+
readonly stiffness: 0.146;
|
|
43
|
+
readonly damping: 0.854;
|
|
44
|
+
};
|
|
45
|
+
readonly gentle: {
|
|
46
|
+
readonly stiffness: 0.09;
|
|
47
|
+
readonly damping: 0.91;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
export type SpringPreset = keyof typeof SPRINGS;
|
|
51
|
+
/**
|
|
52
|
+
* A Svelte `Spring` with a φ preset that jumps straight to its target while the user prefers
|
|
53
|
+
* reduced motion. For motion that is essential, `set(value, { instant: false })` still animates.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* const balance = spring(0, 'gentle');
|
|
57
|
+
* balance.target = 248190.35;
|
|
58
|
+
*/
|
|
59
|
+
export declare function spring<T>(value: T, preset?: SpringPreset): Spring<T>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Spring, prefersReducedMotion } from 'svelte/motion';
|
|
2
|
+
import { cubicBezier } from './easing.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reads the theme's timing where the element is: the theme in scope, and near-zero
|
|
5
|
+
* durations while the user prefers reduced motion. For `element.animate()` and CSS.
|
|
6
|
+
*/
|
|
7
|
+
export function motion(element) {
|
|
8
|
+
const style = getComputedStyle(element);
|
|
9
|
+
const read = (name, fallback) => {
|
|
10
|
+
const value = style.getPropertyValue(`--sv-duration-${name}`).trim();
|
|
11
|
+
const ms = parseFloat(value) * (value.endsWith('ms') ? 1 : 1000);
|
|
12
|
+
return Number.isFinite(ms) ? ms : fallback;
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
fast: read('fast', 144),
|
|
16
|
+
normal: read('normal', 233),
|
|
17
|
+
slow: read('slow', 377),
|
|
18
|
+
ease: style.getPropertyValue('--sv-ease').trim() || 'ease'
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const KEYWORDS = {
|
|
22
|
+
linear: [0, 0, 1, 1],
|
|
23
|
+
ease: [0.25, 0.1, 0.25, 1],
|
|
24
|
+
'ease-in': [0.42, 0, 1, 1],
|
|
25
|
+
'ease-out': [0, 0, 0.58, 1],
|
|
26
|
+
'ease-in-out': [0.42, 0, 0.58, 1]
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* A CSS easing as a function, for Svelte transitions and `Tween`. Understands `cubic-bezier()`
|
|
30
|
+
* and the keywords; anything else (`steps()`, `linear()`) falls back to `ease`.
|
|
31
|
+
*/
|
|
32
|
+
export function easing(ease) {
|
|
33
|
+
const points = /cubic-bezier\(([^)]+)\)/.exec(ease)?.[1]?.split(',').map(Number);
|
|
34
|
+
const [x1, y1, x2, y2] = points?.length === 4 && points.every(Number.isFinite)
|
|
35
|
+
? points
|
|
36
|
+
: (KEYWORDS[ease.trim()] ?? KEYWORDS.ease);
|
|
37
|
+
return cubicBezier(x1, y1, x2, y2);
|
|
38
|
+
}
|
|
39
|
+
function themed(node, { duration, delay = 0 }, css) {
|
|
40
|
+
const timing = motion(node);
|
|
41
|
+
return { delay, duration: duration ?? timing.normal, easing: easing(timing.ease), css };
|
|
42
|
+
}
|
|
43
|
+
/** Fades in and out with the theme's timing. */
|
|
44
|
+
export function fade(node, options = {}) {
|
|
45
|
+
return themed(node, options, (t) => `opacity: ${t}`);
|
|
46
|
+
}
|
|
47
|
+
/** Fades in while moving up `distance` px (8 by default), with the theme's timing. */
|
|
48
|
+
export function rise(node, { distance = 8, ...options } = {}) {
|
|
49
|
+
return themed(node, options, (t, u) => `opacity: ${t}; transform: translateY(${u * distance}px)`);
|
|
50
|
+
}
|
|
51
|
+
/** Fades in while growing from `scale` (94.4%, 1 − φ⁻⁶), with the theme's timing. */
|
|
52
|
+
export function pop(node, { scale = 0.944, ...options } = {}) {
|
|
53
|
+
return themed(node, options, (t) => `opacity: ${t}; transform: scale(${scale + (1 - scale) * t})`);
|
|
54
|
+
}
|
|
55
|
+
/** Spring presets on the φ series: stiffness 1/φⁿ, damping 1 − 1/φⁿ. */
|
|
56
|
+
export const SPRINGS = {
|
|
57
|
+
snappy: { stiffness: 0.236, damping: 0.764 },
|
|
58
|
+
standard: { stiffness: 0.146, damping: 0.854 },
|
|
59
|
+
gentle: { stiffness: 0.09, damping: 0.91 }
|
|
60
|
+
};
|
|
61
|
+
// A Svelte Spring whose updates are instant while the user prefers reduced motion.
|
|
62
|
+
class ReducibleSpring extends Spring {
|
|
63
|
+
set(value, options) {
|
|
64
|
+
return super.set(value, {
|
|
65
|
+
...options,
|
|
66
|
+
instant: options?.instant ?? prefersReducedMotion.current
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A Svelte `Spring` with a φ preset that jumps straight to its target while the user prefers
|
|
72
|
+
* reduced motion. For motion that is essential, `set(value, { instant: false })` still animates.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* const balance = spring(0, 'gentle');
|
|
76
|
+
* balance.target = 248190.35;
|
|
77
|
+
*/
|
|
78
|
+
export function spring(value, preset = 'standard') {
|
|
79
|
+
return new ReducibleSpring(value, SPRINGS[preset]);
|
|
80
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
export interface PortalProps {
|
|
5
|
+
/** Where the content goes: an element or a selector. Defaults to `document.body`. */
|
|
6
|
+
target?: HTMLElement | string;
|
|
7
|
+
children: Snippet;
|
|
8
|
+
}
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
// Renders its children as a separate root in another part of the document, with the same
|
|
13
|
+
// contexts. Moving the element itself would break how Svelte removes it later.
|
|
14
|
+
import { getAllContexts, mount, unmount } from 'svelte';
|
|
15
|
+
import { sveltierError } from '../../core/errors/errors.js';
|
|
16
|
+
import { hasThemeContext, useTheme } from '../../core/theme/context.js';
|
|
17
|
+
import PortalContent from './PortalContent.svelte';
|
|
18
|
+
|
|
19
|
+
let { target = 'body', children }: PortalProps = $props();
|
|
20
|
+
|
|
21
|
+
const context = getAllContexts();
|
|
22
|
+
const theme = hasThemeContext() ? useTheme() : undefined;
|
|
23
|
+
|
|
24
|
+
$effect(() => {
|
|
25
|
+
const parent = typeof target === 'string' ? document.querySelector(target) : target;
|
|
26
|
+
if (!parent) {
|
|
27
|
+
throw sveltierError(
|
|
28
|
+
'SV3001',
|
|
29
|
+
`Portal: no element matches "${target}". Render the target first, or pass the element itself.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const root = mount(PortalContent, { target: parent, props: { children, theme }, context });
|
|
33
|
+
return () => {
|
|
34
|
+
void unmount(root);
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
</script>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export interface PortalProps {
|
|
3
|
+
/** Where the content goes: an element or a selector. Defaults to `document.body`. */
|
|
4
|
+
target?: HTMLElement | string;
|
|
5
|
+
children: Snippet;
|
|
6
|
+
}
|
|
7
|
+
declare const Portal: import("svelte").Component<PortalProps, {}, "">;
|
|
8
|
+
type Portal = ReturnType<typeof Portal>;
|
|
9
|
+
export default Portal;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The root mounted by Portal. It repeats the nearest theme scope, so portaled content
|
|
3
|
+
// looks like the place it came from.
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
5
|
+
import type { ThemeInstance } from '../../core/theme/context.js';
|
|
6
|
+
import './portal.css';
|
|
7
|
+
|
|
8
|
+
let { children, theme }: { children: Snippet; theme?: ThemeInstance } = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
{#if theme}
|
|
12
|
+
<div class="sv-portal" data-sv-theme={theme.name}>{@render children()}</div>
|
|
13
|
+
{:else}
|
|
14
|
+
{@render children()}
|
|
15
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ThemeInstance } from '../../core/theme/context.js';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
children: Snippet;
|
|
5
|
+
theme?: ThemeInstance;
|
|
6
|
+
};
|
|
7
|
+
declare const PortalContent: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type PortalContent = ReturnType<typeof PortalContent>;
|
|
9
|
+
export default PortalContent;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Attachment } from 'svelte/attachments';
|
|
2
|
+
/**
|
|
3
|
+
* Stops the page from scrolling while the element is attached, for modal surfaces. The
|
|
4
|
+
* scrollbar's width is kept as padding, so the layout does not shift. Locks are counted, so
|
|
5
|
+
* nested modals are safe: the page scrolls again when the last one ends.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <dialog {@attach open && scrollLock()}>…</dialog>
|
|
9
|
+
*/
|
|
10
|
+
export declare function scrollLock(): Attachment;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
let locks = 0;
|
|
2
|
+
let restore;
|
|
3
|
+
/**
|
|
4
|
+
* Stops the page from scrolling while the element is attached, for modal surfaces. The
|
|
5
|
+
* scrollbar's width is kept as padding, so the layout does not shift. Locks are counted, so
|
|
6
|
+
* nested modals are safe: the page scrolls again when the last one ends.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <dialog {@attach open && scrollLock()}>…</dialog>
|
|
10
|
+
*/
|
|
11
|
+
export function scrollLock() {
|
|
12
|
+
return () => {
|
|
13
|
+
if (locks++ === 0) {
|
|
14
|
+
const html = document.documentElement;
|
|
15
|
+
const { overflow, paddingInlineEnd } = html.style;
|
|
16
|
+
const scrollbar = window.innerWidth - html.clientWidth;
|
|
17
|
+
const gutter = getComputedStyle(html).scrollbarGutter.includes('stable');
|
|
18
|
+
if (scrollbar > 0 && !gutter) {
|
|
19
|
+
const padding = parseFloat(getComputedStyle(html).paddingInlineEnd) || 0;
|
|
20
|
+
html.style.paddingInlineEnd = `${padding + scrollbar}px`;
|
|
21
|
+
}
|
|
22
|
+
html.style.overflow = 'hidden';
|
|
23
|
+
restore = () => {
|
|
24
|
+
html.style.overflow = overflow;
|
|
25
|
+
html.style.paddingInlineEnd = paddingInlineEnd;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return () => {
|
|
29
|
+
if (--locks === 0)
|
|
30
|
+
restore?.();
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Attachment } from 'svelte/attachments';
|
|
2
|
+
export type VirtualKey = string | number;
|
|
3
|
+
export interface VirtualListOptions {
|
|
4
|
+
/** How many items the list has. */
|
|
5
|
+
count: number;
|
|
6
|
+
/**
|
|
7
|
+
* Item height in px: one number when every item is as tall, or an estimate per index.
|
|
8
|
+
* Items with the `measure` attachment are measured, and the measurement wins.
|
|
9
|
+
*/
|
|
10
|
+
itemSize: number | ((index: number) => number);
|
|
11
|
+
/** Items rendered beyond each edge of the viewport. Defaults to 3. */
|
|
12
|
+
overscan?: number;
|
|
13
|
+
/**
|
|
14
|
+
* A stable key per item, so measured heights follow items that move. Defaults to the index.
|
|
15
|
+
* Once items are measured it is read for every item when sizes change: keep large data in
|
|
16
|
+
* `$state.raw`, not deep `$state`.
|
|
17
|
+
*/
|
|
18
|
+
key?: (index: number) => VirtualKey;
|
|
19
|
+
/** Viewport height in px assumed until the viewport is measured, as on the server. Defaults to 0. */
|
|
20
|
+
ssrHeight?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface VirtualItem {
|
|
23
|
+
readonly index: number;
|
|
24
|
+
readonly key: VirtualKey;
|
|
25
|
+
/** Distance from the top of the list to the item, in px. */
|
|
26
|
+
readonly start: number;
|
|
27
|
+
readonly size: number;
|
|
28
|
+
/** `start + size`. */
|
|
29
|
+
readonly end: number;
|
|
30
|
+
}
|
|
31
|
+
export interface ScrollToIndexOptions {
|
|
32
|
+
/**
|
|
33
|
+
* Where the item lands. `auto` scrolls as little as possible, and not at all when the item
|
|
34
|
+
* is in view. Defaults to `'auto'`.
|
|
35
|
+
*/
|
|
36
|
+
align?: 'auto' | 'start' | 'center' | 'end';
|
|
37
|
+
}
|
|
38
|
+
/** A virtual list: which items to render for the scroll position, and where. Every field is reactive. */
|
|
39
|
+
export interface VirtualList {
|
|
40
|
+
/** The items to render: those in view, plus the overscan. */
|
|
41
|
+
readonly items: VirtualItem[];
|
|
42
|
+
/** Height of all the items together in px: the height of the element that holds them. */
|
|
43
|
+
readonly totalSize: number;
|
|
44
|
+
/** For the scroll container. */
|
|
45
|
+
readonly viewport: Attachment<HTMLElement>;
|
|
46
|
+
/** For item elements, with `data-index={item.index}`, to measure their real height. */
|
|
47
|
+
readonly measure: Attachment<HTMLElement>;
|
|
48
|
+
/**
|
|
49
|
+
* Scrolls the viewport, at once, so the item is in view, and renders it in the same update.
|
|
50
|
+
* Indexes outside the list are ignored.
|
|
51
|
+
*/
|
|
52
|
+
scrollToIndex(index: number, options?: ScrollToIndexOptions): void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Renders only the items in view out of thousands. Options are read through a function, so
|
|
56
|
+
* they stay reactive.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const list = virtualList(() => ({ count: rows.length, itemSize: 34 }));
|
|
60
|
+
* // <div class="viewport" {@attach list.viewport}>
|
|
61
|
+
* // <div style:height="{list.totalSize}px" style:position="relative">
|
|
62
|
+
* // {#each list.items as item (item.key)}
|
|
63
|
+
* // <div class="row" style:transform="translateY({item.start}px)">…</div>
|
|
64
|
+
*/
|
|
65
|
+
export declare function virtualList(options: () => VirtualListOptions): VirtualList;
|