@sveltier/ui 0.0.0-stage → 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +82 -2
- package/dist/components/Accordion/Accordion.svelte +74 -0
- package/dist/components/Accordion/Accordion.svelte.d.ts +25 -0
- package/dist/components/Accordion/accordion.css +88 -0
- package/dist/components/Accordion/index.d.ts +2 -0
- package/dist/components/Accordion/index.js +1 -0
- package/dist/components/Accordion/types.d.ts +64 -0
- package/dist/components/Accordion/types.js +1 -0
- package/dist/components/Alert/Alert.svelte +88 -0
- package/dist/components/Alert/Alert.svelte.d.ts +4 -0
- package/dist/components/Alert/alert.css +78 -0
- package/dist/components/Alert/index.d.ts +2 -0
- package/dist/components/Alert/index.js +1 -0
- package/dist/components/Alert/types.d.ts +67 -0
- package/dist/components/Alert/types.js +1 -0
- package/dist/components/Avatar/Avatar.svelte +47 -0
- package/dist/components/Avatar/Avatar.svelte.d.ts +4 -0
- package/dist/components/Avatar/avatar.css +34 -0
- package/dist/components/Avatar/index.d.ts +2 -0
- package/dist/components/Avatar/index.js +1 -0
- package/dist/components/Avatar/types.d.ts +47 -0
- package/dist/components/Avatar/types.js +1 -0
- package/dist/components/Badge/Badge.svelte +53 -0
- package/dist/components/Badge/Badge.svelte.d.ts +4 -0
- package/dist/components/Badge/badge.css +57 -0
- package/dist/components/Badge/index.d.ts +2 -0
- package/dist/components/Badge/index.js +1 -0
- package/dist/components/Badge/types.d.ts +43 -0
- package/dist/components/Badge/types.js +1 -0
- package/dist/components/Button/Button.svelte +85 -0
- package/dist/components/Button/Button.svelte.d.ts +4 -0
- package/dist/components/Button/button.css +132 -0
- package/dist/components/Button/index.d.ts +2 -0
- package/dist/components/Button/index.js +1 -0
- package/dist/components/Button/types.d.ts +76 -0
- package/dist/components/Button/types.js +1 -0
- package/dist/components/ButtonGroup/ButtonGroup.svelte +61 -0
- package/dist/components/ButtonGroup/ButtonGroup.svelte.d.ts +4 -0
- package/dist/components/ButtonGroup/button-group.css +39 -0
- package/dist/components/ButtonGroup/index.d.ts +2 -0
- package/dist/components/ButtonGroup/index.js +1 -0
- package/dist/components/ButtonGroup/types.d.ts +48 -0
- package/dist/components/ButtonGroup/types.js +1 -0
- package/dist/components/Card/Card.svelte +53 -0
- package/dist/components/Card/Card.svelte.d.ts +4 -0
- package/dist/components/Card/card.css +62 -0
- package/dist/components/Card/index.d.ts +2 -0
- package/dist/components/Card/index.js +1 -0
- package/dist/components/Card/types.d.ts +56 -0
- package/dist/components/Card/types.js +1 -0
- package/dist/components/Checkbox/Checkbox.svelte +105 -0
- package/dist/components/Checkbox/Checkbox.svelte.d.ts +4 -0
- package/dist/components/Checkbox/checkbox.css +138 -0
- package/dist/components/Checkbox/index.d.ts +2 -0
- package/dist/components/Checkbox/index.js +1 -0
- package/dist/components/Checkbox/types.d.ts +76 -0
- package/dist/components/Checkbox/types.js +1 -0
- package/dist/components/Chip/Chip.svelte +109 -0
- package/dist/components/Chip/Chip.svelte.d.ts +4 -0
- package/dist/components/Chip/chip.css +101 -0
- package/dist/components/Chip/index.d.ts +2 -0
- package/dist/components/Chip/index.js +1 -0
- package/dist/components/Chip/types.d.ts +69 -0
- package/dist/components/Chip/types.js +1 -0
- package/dist/components/Choice/Choice.svelte +60 -0
- package/dist/components/Choice/Choice.svelte.d.ts +4 -0
- package/dist/components/Choice/choice.css +75 -0
- package/dist/components/Choice/types.d.ts +43 -0
- package/dist/components/Choice/types.js +1 -0
- package/dist/components/Dialog/Dialog.svelte +36 -0
- package/dist/components/Dialog/Dialog.svelte.d.ts +4 -0
- package/dist/components/Dialog/index.d.ts +2 -0
- package/dist/components/Dialog/index.js +1 -0
- package/dist/components/Dialog/types.d.ts +50 -0
- package/dist/components/Dialog/types.js +1 -0
- package/dist/components/Divider/Divider.svelte +19 -0
- package/dist/components/Divider/Divider.svelte.d.ts +4 -0
- package/dist/components/Divider/divider.css +23 -0
- package/dist/components/Divider/index.d.ts +2 -0
- package/dist/components/Divider/index.js +1 -0
- package/dist/components/Divider/types.d.ts +10 -0
- package/dist/components/Divider/types.js +1 -0
- package/dist/components/Field/Field.svelte +95 -0
- package/dist/components/Field/Field.svelte.d.ts +4 -0
- package/dist/components/Field/field.css +210 -0
- package/dist/components/Field/index.d.ts +3 -0
- package/dist/components/Field/index.js +2 -0
- package/dist/components/Field/types.d.ts +109 -0
- package/dist/components/Field/types.js +2 -0
- package/dist/components/Form/Form.svelte +93 -0
- package/dist/components/Form/Form.svelte.d.ts +8 -0
- package/dist/components/Form/form.css +16 -0
- package/dist/components/Form/index.d.ts +2 -0
- package/dist/components/Form/index.js +1 -0
- package/dist/components/Form/types.d.ts +38 -0
- package/dist/components/Form/types.js +1 -0
- package/dist/components/Icon/Icon.svelte +38 -0
- package/dist/components/Icon/Icon.svelte.d.ts +4 -0
- package/dist/components/Icon/icon.css +33 -0
- package/dist/components/Icon/index.d.ts +2 -0
- package/dist/components/Icon/index.js +1 -0
- package/dist/components/Icon/types.d.ts +34 -0
- package/dist/components/Icon/types.js +1 -0
- package/dist/components/List/List.svelte +39 -0
- package/dist/components/List/List.svelte.d.ts +4 -0
- package/dist/components/List/ListItem.svelte +57 -0
- package/dist/components/List/ListItem.svelte.d.ts +4 -0
- package/dist/components/List/index.d.ts +3 -0
- package/dist/components/List/index.js +2 -0
- package/dist/components/List/list.css +127 -0
- package/dist/components/List/types.d.ts +67 -0
- package/dist/components/List/types.js +1 -0
- package/dist/components/Menu/Menu.svelte +195 -0
- package/dist/components/Menu/Menu.svelte.d.ts +4 -0
- package/dist/components/Menu/index.d.ts +2 -0
- package/dist/components/Menu/index.js +1 -0
- package/dist/components/Menu/menu.css +103 -0
- package/dist/components/Menu/types.d.ts +90 -0
- package/dist/components/Menu/types.js +1 -0
- package/dist/components/Modal/Modal.svelte +146 -0
- package/dist/components/Modal/Modal.svelte.d.ts +4 -0
- package/dist/components/Modal/modal.css +150 -0
- package/dist/components/Modal/types.d.ts +54 -0
- package/dist/components/Modal/types.js +1 -0
- package/dist/components/NativeSelect/NativeSelect.svelte +110 -0
- package/dist/components/NativeSelect/NativeSelect.svelte.d.ts +25 -0
- package/dist/components/NativeSelect/index.d.ts +2 -0
- package/dist/components/NativeSelect/index.js +1 -0
- package/dist/components/NativeSelect/native-select.css +45 -0
- package/dist/components/NativeSelect/types.d.ts +58 -0
- package/dist/components/NativeSelect/types.js +1 -0
- package/dist/components/NumberField/NumberField.svelte +246 -0
- package/dist/components/NumberField/NumberField.svelte.d.ts +4 -0
- package/dist/components/NumberField/index.d.ts +2 -0
- package/dist/components/NumberField/index.js +1 -0
- package/dist/components/NumberField/number-field.css +74 -0
- package/dist/components/NumberField/types.d.ts +73 -0
- package/dist/components/NumberField/types.js +1 -0
- package/dist/components/Popover/Popover.svelte +115 -0
- package/dist/components/Popover/Popover.svelte.d.ts +4 -0
- package/dist/components/Popover/index.d.ts +2 -0
- package/dist/components/Popover/index.js +1 -0
- package/dist/components/Popover/popover.css +67 -0
- package/dist/components/Popover/types.d.ts +83 -0
- package/dist/components/Popover/types.js +1 -0
- package/dist/components/Progress/ProgressCircular.svelte +61 -0
- package/dist/components/Progress/ProgressCircular.svelte.d.ts +4 -0
- package/dist/components/Progress/ProgressLinear.svelte +44 -0
- package/dist/components/Progress/ProgressLinear.svelte.d.ts +4 -0
- package/dist/components/Progress/index.d.ts +3 -0
- package/dist/components/Progress/index.js +2 -0
- package/dist/components/Progress/progress-circular.css +61 -0
- package/dist/components/Progress/progress-linear.css +67 -0
- package/dist/components/Progress/types.d.ts +53 -0
- package/dist/components/Progress/types.js +1 -0
- package/dist/components/RadioGroup/RadioGroup.svelte +149 -0
- package/dist/components/RadioGroup/RadioGroup.svelte.d.ts +25 -0
- package/dist/components/RadioGroup/index.d.ts +2 -0
- package/dist/components/RadioGroup/index.js +1 -0
- package/dist/components/RadioGroup/radio-group.css +139 -0
- package/dist/components/RadioGroup/types.d.ts +93 -0
- package/dist/components/RadioGroup/types.js +1 -0
- package/dist/components/SegmentedControl/SegmentedControl.svelte +70 -0
- package/dist/components/SegmentedControl/SegmentedControl.svelte.d.ts +25 -0
- package/dist/components/SegmentedControl/index.d.ts +2 -0
- package/dist/components/SegmentedControl/index.js +1 -0
- package/dist/components/SegmentedControl/segmented-control.css +68 -0
- package/dist/components/SegmentedControl/types.d.ts +60 -0
- package/dist/components/SegmentedControl/types.js +1 -0
- package/dist/components/Sheet/Sheet.svelte +56 -0
- package/dist/components/Sheet/Sheet.svelte.d.ts +4 -0
- package/dist/components/Sheet/index.d.ts +2 -0
- package/dist/components/Sheet/index.js +2 -0
- package/dist/components/Sheet/sheet.css +61 -0
- package/dist/components/Sheet/types.d.ts +51 -0
- package/dist/components/Sheet/types.js +1 -0
- package/dist/components/Skeleton/Skeleton.svelte +33 -0
- package/dist/components/Skeleton/Skeleton.svelte.d.ts +4 -0
- package/dist/components/Skeleton/index.d.ts +2 -0
- package/dist/components/Skeleton/index.js +1 -0
- package/dist/components/Skeleton/skeleton.css +70 -0
- package/dist/components/Skeleton/types.d.ts +25 -0
- package/dist/components/Skeleton/types.js +1 -0
- package/dist/components/Slider/Slider.svelte +153 -0
- package/dist/components/Slider/Slider.svelte.d.ts +4 -0
- package/dist/components/Slider/index.d.ts +2 -0
- package/dist/components/Slider/index.js +1 -0
- package/dist/components/Slider/slider.css +206 -0
- package/dist/components/Slider/types.d.ts +102 -0
- package/dist/components/Slider/types.js +1 -0
- package/dist/components/Switch/Switch.svelte +94 -0
- package/dist/components/Switch/Switch.svelte.d.ts +4 -0
- package/dist/components/Switch/index.d.ts +2 -0
- package/dist/components/Switch/index.js +1 -0
- package/dist/components/Switch/switch.css +118 -0
- package/dist/components/Switch/types.d.ts +67 -0
- package/dist/components/Switch/types.js +1 -0
- package/dist/components/Tabs/Tabs.svelte +93 -0
- package/dist/components/Tabs/Tabs.svelte.d.ts +25 -0
- package/dist/components/Tabs/index.d.ts +2 -0
- package/dist/components/Tabs/index.js +1 -0
- package/dist/components/Tabs/tabs.css +73 -0
- package/dist/components/Tabs/types.d.ts +59 -0
- package/dist/components/Tabs/types.js +1 -0
- package/dist/components/TextField/TextField.svelte +115 -0
- package/dist/components/TextField/TextField.svelte.d.ts +4 -0
- package/dist/components/TextField/index.d.ts +2 -0
- package/dist/components/TextField/index.js +1 -0
- package/dist/components/TextField/types.d.ts +57 -0
- package/dist/components/TextField/types.js +1 -0
- package/dist/components/Textarea/Textarea.svelte +116 -0
- package/dist/components/Textarea/Textarea.svelte.d.ts +4 -0
- package/dist/components/Textarea/index.d.ts +2 -0
- package/dist/components/Textarea/index.js +1 -0
- package/dist/components/Textarea/textarea.css +35 -0
- package/dist/components/Textarea/types.d.ts +60 -0
- package/dist/components/Textarea/types.js +1 -0
- package/dist/components/Tooltip/Tooltip.svelte +161 -0
- package/dist/components/Tooltip/Tooltip.svelte.d.ts +4 -0
- package/dist/components/Tooltip/index.d.ts +2 -0
- package/dist/components/Tooltip/index.js +1 -0
- package/dist/components/Tooltip/tooltip.css +44 -0
- package/dist/components/Tooltip/types.d.ts +55 -0
- package/dist/components/Tooltip/types.js +1 -0
- package/dist/core/defaults/DefaultsProvider.svelte +30 -0
- package/dist/core/defaults/DefaultsProvider.svelte.d.ts +13 -0
- package/dist/core/defaults/context.d.ts +45 -0
- package/dist/core/defaults/context.js +38 -0
- package/dist/core/display/display.d.ts +39 -0
- package/dist/core/display/display.js +53 -0
- package/dist/core/errors/codes.d.ts +124 -0
- package/dist/core/errors/codes.js +108 -0
- package/dist/core/errors/errors.d.ts +8 -0
- package/dist/core/errors/errors.js +7 -0
- package/dist/core/form/constraints.d.ts +15 -0
- package/dist/core/form/constraints.js +42 -0
- package/dist/core/form/context.d.ts +19 -0
- package/dist/core/form/context.js +4 -0
- package/dist/core/form/field.svelte.d.ts +34 -0
- package/dist/core/form/field.svelte.js +66 -0
- package/dist/core/form/number.d.ts +24 -0
- package/dist/core/form/number.js +71 -0
- package/dist/core/form/types.d.ts +22 -0
- package/dist/core/form/types.js +1 -0
- package/dist/core/locale/locale.d.ts +56 -0
- package/dist/core/locale/locale.js +82 -0
- package/dist/core/overlay/types.d.ts +33 -0
- package/dist/core/overlay/types.js +1 -0
- package/dist/core/props/classes.d.ts +21 -0
- package/dist/core/props/classes.js +44 -0
- package/dist/core/props/element.d.ts +18 -0
- package/dist/core/props/element.js +1 -0
- package/dist/core/props/types.d.ts +47 -0
- package/dist/core/props/types.js +16 -0
- package/dist/core/provider/UIProvider.svelte +63 -0
- package/dist/core/provider/UIProvider.svelte.d.ts +29 -0
- package/dist/core/theme/ThemeProvider.svelte +178 -0
- package/dist/core/theme/ThemeProvider.svelte.d.ts +25 -0
- package/dist/core/theme/color.d.ts +23 -0
- package/dist/core/theme/color.js +54 -0
- package/dist/core/theme/context.d.ts +25 -0
- package/dist/core/theme/context.js +14 -0
- package/dist/core/theme/css.d.ts +25 -0
- package/dist/core/theme/css.js +73 -0
- package/dist/core/theme/define.d.ts +11 -0
- package/dist/core/theme/define.js +21 -0
- package/dist/core/theme/resolve.d.ts +6 -0
- package/dist/core/theme/resolve.js +18 -0
- package/dist/core/theme/theme-provider.css +13 -0
- package/dist/core/theme/types.d.ts +35 -0
- package/dist/core/theme/types.js +61 -0
- package/dist/core/toast/ToastOutlet.svelte +192 -0
- package/dist/core/toast/ToastOutlet.svelte.d.ts +11 -0
- package/dist/core/toast/ToastProvider.svelte +39 -0
- package/dist/core/toast/ToastProvider.svelte.d.ts +19 -0
- package/dist/core/toast/toast.css +134 -0
- package/dist/core/toast/toast.svelte.d.ts +117 -0
- package/dist/core/toast/toast.svelte.js +133 -0
- package/dist/dev/off.d.ts +5 -0
- package/dist/dev/off.js +4 -0
- package/dist/dev/on.d.ts +20 -0
- package/dist/dev/on.js +49 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +83 -0
- package/dist/primitives/anchored/anchored.d.ts +39 -0
- package/dist/primitives/anchored/anchored.js +104 -0
- package/dist/primitives/anchored/fallback.d.ts +31 -0
- package/dist/primitives/anchored/fallback.js +73 -0
- package/dist/primitives/focus/focus-trap.d.ts +25 -0
- package/dist/primitives/focus/focus-trap.js +86 -0
- package/dist/primitives/focus/roving-focus.d.ts +22 -0
- package/dist/primitives/focus/roving-focus.js +86 -0
- package/dist/primitives/focus/tabbable.d.ts +9 -0
- package/dist/primitives/focus/tabbable.js +75 -0
- package/dist/primitives/inert/inert-others.d.ts +12 -0
- package/dist/primitives/inert/inert-others.js +44 -0
- package/dist/primitives/keyboard/keymap.d.ts +17 -0
- package/dist/primitives/keyboard/keymap.js +39 -0
- package/dist/primitives/keyboard/typeahead.d.ts +18 -0
- package/dist/primitives/keyboard/typeahead.js +50 -0
- package/dist/primitives/layer/dismissable.d.ts +32 -0
- package/dist/primitives/layer/dismissable.js +91 -0
- package/dist/primitives/motion/easing.d.ts +9 -0
- package/dist/primitives/motion/easing.js +46 -0
- package/dist/primitives/motion/motion.d.ts +59 -0
- package/dist/primitives/motion/motion.js +80 -0
- package/dist/primitives/portal/Portal.svelte +37 -0
- package/dist/primitives/portal/Portal.svelte.d.ts +9 -0
- package/dist/primitives/portal/PortalContent.svelte +15 -0
- package/dist/primitives/portal/PortalContent.svelte.d.ts +9 -0
- package/dist/primitives/portal/portal.css +8 -0
- package/dist/primitives/scroll-lock/scroll-lock.d.ts +10 -0
- package/dist/primitives/scroll-lock/scroll-lock.js +33 -0
- package/dist/primitives/virtual/virtual-list.svelte.d.ts +65 -0
- package/dist/primitives/virtual/virtual-list.svelte.js +291 -0
- package/dist/styles/base.css +44 -0
- package/dist/styles/props.css +140 -0
- package/dist/styles/tokens.css +84 -0
- package/dist/styles/visually-hidden.css +16 -0
- package/dist/themes/aurora.d.ts +13 -0
- package/dist/themes/aurora.js +103 -0
- package/dist/themes/heritage.d.ts +13 -0
- package/dist/themes/heritage.js +102 -0
- package/dist/themes/maison.d.ts +13 -0
- package/dist/themes/maison.js +99 -0
- package/dist/themes/milano.d.ts +13 -0
- package/dist/themes/milano.js +101 -0
- package/dist/themes/monolith.d.ts +13 -0
- package/dist/themes/monolith.js +101 -0
- package/package.json +87 -5
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { untrack } from 'svelte';
|
|
2
|
+
import { sveltierError } from '../../core/errors/errors.js';
|
|
3
|
+
class Virtualizer {
|
|
4
|
+
#options;
|
|
5
|
+
// The scroll position, and the viewport height once measured.
|
|
6
|
+
#offset = $state(0);
|
|
7
|
+
#height = $state();
|
|
8
|
+
// Measured heights by key, in a plain map: one signal for all of them, not one per item.
|
|
9
|
+
#sizes = new Map();
|
|
10
|
+
#version = $state(0);
|
|
11
|
+
#element;
|
|
12
|
+
#observer;
|
|
13
|
+
// Items are observed from the next frame on: an observation added while ResizeObserver is
|
|
14
|
+
// delivering (rows mounted because measured rows moved) is reported as a loop error.
|
|
15
|
+
#queued = new Set();
|
|
16
|
+
#observing = 0;
|
|
17
|
+
#jump;
|
|
18
|
+
#expiry = 0;
|
|
19
|
+
// The part of a scroll shift the browser rounded away, carried to the next one.
|
|
20
|
+
#remainder = 0;
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.#options = options;
|
|
23
|
+
}
|
|
24
|
+
#config = $derived.by(() => {
|
|
25
|
+
const { count, itemSize, overscan = 3, key, ssrHeight = 0 } = this.#options();
|
|
26
|
+
if (typeof itemSize === 'number' && !(itemSize > 0)) {
|
|
27
|
+
throw sveltierError('SV3002', `virtualList: itemSize must be a positive number of px; got ${itemSize}.`);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
count: Number.isFinite(count) ? Math.max(0, Math.trunc(count)) : 0,
|
|
31
|
+
itemSize,
|
|
32
|
+
overscan: Math.max(0, Math.trunc(overscan)) || 0,
|
|
33
|
+
key,
|
|
34
|
+
ssrHeight: Math.max(0, ssrHeight) || 0
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
// Where every item starts, plus the end of the last one. Not needed while all items are
|
|
38
|
+
// one known height: then a start is a multiplication.
|
|
39
|
+
#starts = $derived.by(() => {
|
|
40
|
+
const { count, itemSize, key } = this.#config;
|
|
41
|
+
const version = this.#version;
|
|
42
|
+
if (version === 0 && typeof itemSize === 'number')
|
|
43
|
+
return undefined;
|
|
44
|
+
// Until something is measured there is nothing to look up, and no key to work out.
|
|
45
|
+
const sizes = version > 0 ? this.#sizes : undefined;
|
|
46
|
+
const starts = new Float64Array(count + 1);
|
|
47
|
+
for (let i = 0; i < count; i++) {
|
|
48
|
+
starts[i + 1] = starts[i] + (sizes?.get(key ? key(i) : i) ?? this.#estimate(i));
|
|
49
|
+
}
|
|
50
|
+
return starts;
|
|
51
|
+
});
|
|
52
|
+
#estimate(index) {
|
|
53
|
+
const { itemSize } = this.#config;
|
|
54
|
+
if (typeof itemSize === 'number')
|
|
55
|
+
return itemSize;
|
|
56
|
+
const size = itemSize(index);
|
|
57
|
+
if (!(size > 0)) {
|
|
58
|
+
throw sveltierError('SV3002', `virtualList: itemSize(${index}) returned ${size}; it must return a positive number of px.`);
|
|
59
|
+
}
|
|
60
|
+
return size;
|
|
61
|
+
}
|
|
62
|
+
#start(index) {
|
|
63
|
+
return this.#starts?.[index] ?? index * this.#config.itemSize;
|
|
64
|
+
}
|
|
65
|
+
// The item at a distance from the top of the list, clamped to the list.
|
|
66
|
+
#indexAt(offset) {
|
|
67
|
+
const { count, itemSize } = this.#config;
|
|
68
|
+
const starts = this.#starts;
|
|
69
|
+
let index;
|
|
70
|
+
if (starts) {
|
|
71
|
+
let low = 0;
|
|
72
|
+
let high = count - 1;
|
|
73
|
+
while (low < high) {
|
|
74
|
+
const middle = (low + high + 1) >> 1;
|
|
75
|
+
if (starts[middle] <= offset)
|
|
76
|
+
low = middle;
|
|
77
|
+
else
|
|
78
|
+
high = middle - 1;
|
|
79
|
+
}
|
|
80
|
+
index = low;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
index = Math.floor(offset / itemSize);
|
|
84
|
+
}
|
|
85
|
+
return Math.max(0, Math.min(count - 1, index));
|
|
86
|
+
}
|
|
87
|
+
#totalSize = $derived(this.#start(this.#config.count));
|
|
88
|
+
#viewHeight = $derived(this.#height ?? this.#config.ssrHeight);
|
|
89
|
+
// The scroll position, kept within the content: when the content shrinks, the browser clamps
|
|
90
|
+
// the real position a frame before the scroll event reports it.
|
|
91
|
+
#top = $derived(Math.max(0, Math.min(this.#offset, this.#totalSize - this.#viewHeight)));
|
|
92
|
+
// First and last rendered index, as separate numbers: scrolling within the rendered range
|
|
93
|
+
// changes neither, so nothing re-renders.
|
|
94
|
+
#first = $derived(Math.max(0, this.#indexAt(this.#top) - this.#config.overscan));
|
|
95
|
+
#last = $derived.by(() => {
|
|
96
|
+
const { count, overscan } = this.#config;
|
|
97
|
+
// The item holding the last visible pixel; one starting right at the bottom is not in view.
|
|
98
|
+
const bottom = this.#top + this.#viewHeight;
|
|
99
|
+
return Math.min(count - 1, this.#indexAt(Math.max(bottom - 0.001, 0)) + overscan);
|
|
100
|
+
});
|
|
101
|
+
#items = $derived.by(() => {
|
|
102
|
+
const { count, key } = this.#config;
|
|
103
|
+
const items = [];
|
|
104
|
+
if (count === 0)
|
|
105
|
+
return items;
|
|
106
|
+
for (let index = this.#first; index <= this.#last; index++) {
|
|
107
|
+
const start = this.#start(index);
|
|
108
|
+
const end = this.#start(index + 1);
|
|
109
|
+
items.push({ index, key: key ? key(index) : index, start, size: end - start, end });
|
|
110
|
+
}
|
|
111
|
+
return items;
|
|
112
|
+
});
|
|
113
|
+
// Getters only: the rendered range is derived, never set.
|
|
114
|
+
get items() {
|
|
115
|
+
return this.#items;
|
|
116
|
+
}
|
|
117
|
+
get totalSize() {
|
|
118
|
+
return this.#totalSize;
|
|
119
|
+
}
|
|
120
|
+
viewport = (element) => {
|
|
121
|
+
this.#element = element;
|
|
122
|
+
// Item heights are kept steady by the list itself; the browser's scroll anchoring would
|
|
123
|
+
// correct for them a second time.
|
|
124
|
+
const anchoring = element.style.overflowAnchor;
|
|
125
|
+
element.style.overflowAnchor = 'none';
|
|
126
|
+
const scrolled = () => {
|
|
127
|
+
// A scroll that lands away from a pending jump is the user's: the jump gives way.
|
|
128
|
+
const top = this.#jump?.top;
|
|
129
|
+
if (top !== undefined && Math.abs(element.scrollTop - top) >= 1)
|
|
130
|
+
this.#jump = undefined;
|
|
131
|
+
this.#offset = element.scrollTop;
|
|
132
|
+
};
|
|
133
|
+
const resized = new ResizeObserver(() => {
|
|
134
|
+
this.#height = element.clientHeight;
|
|
135
|
+
if (element.clientHeight === 0)
|
|
136
|
+
return;
|
|
137
|
+
// Shown again: the browser restores the scroll position without a scroll event.
|
|
138
|
+
this.#offset = element.scrollTop;
|
|
139
|
+
this.#aim();
|
|
140
|
+
});
|
|
141
|
+
element.addEventListener('scroll', scrolled, { passive: true });
|
|
142
|
+
resized.observe(element);
|
|
143
|
+
this.#offset = element.scrollTop;
|
|
144
|
+
this.#height = element.clientHeight;
|
|
145
|
+
// A jump asked for before the viewport existed. Untracked: attachments re-run on what
|
|
146
|
+
// they read.
|
|
147
|
+
untrack(() => this.#aim());
|
|
148
|
+
return () => {
|
|
149
|
+
element.removeEventListener('scroll', scrolled);
|
|
150
|
+
resized.disconnect();
|
|
151
|
+
element.style.overflowAnchor = anchoring;
|
|
152
|
+
if (this.#element === element)
|
|
153
|
+
this.#element = undefined;
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
measure = (element) => {
|
|
157
|
+
if (element.dataset.index === undefined) {
|
|
158
|
+
throw sveltierError('SV3003', 'virtualList: a measured item has no data-index. Add data-index={item.index} next to {@attach list.measure}.');
|
|
159
|
+
}
|
|
160
|
+
this.#queued.add(element);
|
|
161
|
+
if (!this.#observing)
|
|
162
|
+
this.#observing = requestAnimationFrame(() => this.#observeQueued());
|
|
163
|
+
return () => {
|
|
164
|
+
this.#queued.delete(element);
|
|
165
|
+
this.#observer?.unobserve(element);
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
#observeQueued() {
|
|
169
|
+
this.#observing = 0;
|
|
170
|
+
// Border box: padding and borders count, and a change to them alone is reported.
|
|
171
|
+
this.#observer ??= new ResizeObserver((entries) => this.#measured(entries));
|
|
172
|
+
for (const element of this.#queued)
|
|
173
|
+
this.#observer.observe(element, { box: 'border-box' });
|
|
174
|
+
this.#queued.clear();
|
|
175
|
+
}
|
|
176
|
+
#measured(entries) {
|
|
177
|
+
const element = this.#element;
|
|
178
|
+
// A hidden viewport reports every row as 0px: keep what was measured while it showed.
|
|
179
|
+
if (element?.clientHeight === 0)
|
|
180
|
+
return;
|
|
181
|
+
const { count, key } = this.#config;
|
|
182
|
+
const top = this.#top;
|
|
183
|
+
let changed = false;
|
|
184
|
+
let shift = 0;
|
|
185
|
+
for (const entry of entries) {
|
|
186
|
+
const index = Number(entry.target.dataset.index);
|
|
187
|
+
if (!(index >= 0 && index < count))
|
|
188
|
+
continue;
|
|
189
|
+
const id = key ? key(index) : index;
|
|
190
|
+
const size = entry.borderBoxSize[0].blockSize;
|
|
191
|
+
const previous = this.#sizes.get(id) ?? this.#estimate(index);
|
|
192
|
+
this.#sizes.set(id, size);
|
|
193
|
+
if (size === previous)
|
|
194
|
+
continue;
|
|
195
|
+
changed = true;
|
|
196
|
+
// An item above the scroll position pushes the content in view: follow it.
|
|
197
|
+
if (this.#start(index) < top)
|
|
198
|
+
shift += size - previous;
|
|
199
|
+
}
|
|
200
|
+
if (!changed)
|
|
201
|
+
return;
|
|
202
|
+
this.#version++;
|
|
203
|
+
// A pending jump re-aims at its target, which accounts for every row that moved.
|
|
204
|
+
if (this.#jump)
|
|
205
|
+
this.#aim();
|
|
206
|
+
else if (shift && element)
|
|
207
|
+
this.#shift(element, shift);
|
|
208
|
+
}
|
|
209
|
+
#shift(element, by) {
|
|
210
|
+
const wanted = element.scrollTop + by + this.#remainder;
|
|
211
|
+
element.scrollTop = wanted;
|
|
212
|
+
const rest = wanted - element.scrollTop;
|
|
213
|
+
this.#remainder = Math.abs(rest) < 1 ? rest : 0;
|
|
214
|
+
this.#offset = element.scrollTop;
|
|
215
|
+
}
|
|
216
|
+
scrollToIndex(index, { align = 'auto' } = {}) {
|
|
217
|
+
// Untracked, so an effect that calls this does not re-run on every measurement.
|
|
218
|
+
untrack(() => {
|
|
219
|
+
const target = Math.trunc(index);
|
|
220
|
+
if (!(target >= 0 && target < this.#config.count))
|
|
221
|
+
return;
|
|
222
|
+
this.#jump = { index: target, align };
|
|
223
|
+
this.#aim();
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
// Scrolls to the pending jump, once there is a viewport and it is shown.
|
|
227
|
+
#aim() {
|
|
228
|
+
const jump = this.#jump;
|
|
229
|
+
const element = this.#element;
|
|
230
|
+
if (!jump || !element || element.clientHeight === 0)
|
|
231
|
+
return;
|
|
232
|
+
if (jump.index >= this.#config.count) {
|
|
233
|
+
this.#jump = undefined;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const view = element.clientHeight;
|
|
237
|
+
const start = this.#start(jump.index);
|
|
238
|
+
const end = this.#start(jump.index + 1);
|
|
239
|
+
if (jump.align === 'auto') {
|
|
240
|
+
const offset = element.scrollTop;
|
|
241
|
+
if (start >= offset && end <= offset + view) {
|
|
242
|
+
this.#jump = undefined;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// Settled once, so later passes keep the item at the same edge.
|
|
246
|
+
jump.align = start < offset ? 'start' : 'end';
|
|
247
|
+
}
|
|
248
|
+
const wanted = jump.align === 'start' ? start : jump.align === 'end' ? end - view : (start + end - view) / 2;
|
|
249
|
+
const top = Math.max(0, Math.min(wanted, this.#totalSize - view));
|
|
250
|
+
jump.top = top;
|
|
251
|
+
// Render the target now, not after the scroll event: it may be an active descendant.
|
|
252
|
+
this.#offset = top;
|
|
253
|
+
element.scrollTo({ top, behavior: 'instant' });
|
|
254
|
+
// The content may not have grown yet to allow that scroll: again once Svelte has rendered.
|
|
255
|
+
if (Math.abs(element.scrollTop - top) >= 1) {
|
|
256
|
+
queueMicrotask(() => {
|
|
257
|
+
if (this.#jump === jump)
|
|
258
|
+
element.scrollTo({ top: jump.top, behavior: 'instant' });
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
// Rows mounted around the target are measured within a frame; the jump ends a frame after
|
|
262
|
+
// the last measurement that moved it.
|
|
263
|
+
cancelAnimationFrame(this.#expiry);
|
|
264
|
+
this.#expiry = requestAnimationFrame(() => {
|
|
265
|
+
this.#expiry = requestAnimationFrame(() => {
|
|
266
|
+
if (this.#jump !== jump)
|
|
267
|
+
return;
|
|
268
|
+
this.#jump = undefined;
|
|
269
|
+
// In case the scroll never happened, as when nothing was tall enough to scroll. Not
|
|
270
|
+
// while hidden: a hidden element reports a scroll position of 0.
|
|
271
|
+
const current = this.#element;
|
|
272
|
+
if (current && current.clientHeight > 0)
|
|
273
|
+
this.#offset = current.scrollTop;
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Renders only the items in view out of thousands. Options are read through a function, so
|
|
280
|
+
* they stay reactive.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* const list = virtualList(() => ({ count: rows.length, itemSize: 34 }));
|
|
284
|
+
* // <div class="viewport" {@attach list.viewport}>
|
|
285
|
+
* // <div style:height="{list.totalSize}px" style:position="relative">
|
|
286
|
+
* // {#each list.items as item (item.key)}
|
|
287
|
+
* // <div class="row" style:transform="translateY({item.start}px)">…</div>
|
|
288
|
+
*/
|
|
289
|
+
export function virtualList(options) {
|
|
290
|
+
return new Virtualizer(options);
|
|
291
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* Minimal document defaults. Everything is in @layer sv.base, so any unlayered rule wins. */
|
|
2
|
+
@layer sv.tokens, sv.base, sv.components, sv.utilities;
|
|
3
|
+
|
|
4
|
+
@layer sv.base {
|
|
5
|
+
:root {
|
|
6
|
+
background-color: var(--sv-color-background);
|
|
7
|
+
-webkit-text-size-adjust: 100%;
|
|
8
|
+
text-size-adjust: 100%;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* Every theme scope re-applies its text colour and type, so nested themes restyle content. */
|
|
12
|
+
:root,
|
|
13
|
+
[data-sv-theme] {
|
|
14
|
+
color: var(--sv-color-text);
|
|
15
|
+
font-family: var(--sv-font-ui);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
body {
|
|
19
|
+
font-size: var(--sv-text-md);
|
|
20
|
+
line-height: var(--sv-leading-normal);
|
|
21
|
+
-webkit-font-smoothing: antialiased;
|
|
22
|
+
-moz-osx-font-smoothing: grayscale;
|
|
23
|
+
text-rendering: optimizeLegibility;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
:focus-visible {
|
|
27
|
+
outline: var(--sv-focus-width) solid var(--sv-color-primary);
|
|
28
|
+
outline-offset: var(--sv-focus-offset);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* Themes set their own durations in sv.tokens. This later layer wins over every theme, so
|
|
33
|
+
* reduced motion is honoured whatever theme is active.
|
|
34
|
+
*/
|
|
35
|
+
@media (prefers-reduced-motion: reduce) {
|
|
36
|
+
:root,
|
|
37
|
+
[data-sv-theme] {
|
|
38
|
+
--sv-duration-fast: 0.01ms;
|
|
39
|
+
--sv-duration-normal: 0.01ms;
|
|
40
|
+
--sv-duration-slow: 0.01ms;
|
|
41
|
+
--sv-press-offset: 0px;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Shared props. Each class sets custom properties that components read; none of them paints.
|
|
3
|
+
* They live in sv.base, so a component can refine a value for itself and any unlayered rule
|
|
4
|
+
* wins. The colour classes of the theme's intents and custom colours come from the theme
|
|
5
|
+
* stylesheet (themeCss), next to the colours they point at.
|
|
6
|
+
*/
|
|
7
|
+
@layer sv.tokens, sv.base, sv.components, sv.utilities;
|
|
8
|
+
|
|
9
|
+
@layer sv.base {
|
|
10
|
+
.sv-color-neutral {
|
|
11
|
+
--sv-color: var(--sv-color-text);
|
|
12
|
+
--sv-on-color: var(--sv-color-background);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* Variants: resting background, foreground, border and shadow. Hover and press mix the
|
|
17
|
+
* foreground over the background by --sv-state-hover and --sv-state-press. Every variant
|
|
18
|
+
* sets every variable, so nothing leaks in from a parent component.
|
|
19
|
+
*/
|
|
20
|
+
.sv-variant-elevated {
|
|
21
|
+
--sv-variant-bg: var(--sv-color-surface);
|
|
22
|
+
--sv-variant-fg: var(--sv-color);
|
|
23
|
+
--sv-variant-border: transparent;
|
|
24
|
+
--sv-variant-shadow: var(--sv-shadow);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.sv-variant-flat {
|
|
28
|
+
--sv-variant-bg: var(--sv-color);
|
|
29
|
+
--sv-variant-fg: var(--sv-on-color);
|
|
30
|
+
--sv-variant-border: transparent;
|
|
31
|
+
--sv-variant-shadow: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.sv-variant-tonal {
|
|
35
|
+
--sv-variant-bg: color-mix(in oklch, var(--sv-color) var(--sv-tonal-wash), transparent);
|
|
36
|
+
--sv-variant-fg: color-mix(
|
|
37
|
+
in oklch,
|
|
38
|
+
var(--sv-color) var(--sv-tonal-text-mix),
|
|
39
|
+
var(--sv-color-text)
|
|
40
|
+
);
|
|
41
|
+
--sv-variant-border: transparent;
|
|
42
|
+
--sv-variant-shadow: none;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.sv-variant-outlined {
|
|
46
|
+
--sv-variant-bg: transparent;
|
|
47
|
+
--sv-variant-fg: var(--sv-color);
|
|
48
|
+
--sv-variant-border: var(--sv-color);
|
|
49
|
+
--sv-variant-shadow: none;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sv-variant-text,
|
|
53
|
+
.sv-variant-plain {
|
|
54
|
+
--sv-variant-bg: transparent;
|
|
55
|
+
--sv-variant-fg: var(--sv-color);
|
|
56
|
+
--sv-variant-border: transparent;
|
|
57
|
+
--sv-variant-shadow: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
* Plain: no decoration in any state, on a control itself. The washes inherit, so a plain
|
|
62
|
+
* container (a Card, an Alert, a List) leaves them alone for the controls inside it.
|
|
63
|
+
*/
|
|
64
|
+
.sv-variant-plain:where(a, button, summary, [role='button'], [role='link']) {
|
|
65
|
+
--sv-state-hover: 0%;
|
|
66
|
+
--sv-state-press: 0%;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Sizes: a half step of φ in height and a quarter step in text per size. */
|
|
70
|
+
.sv-size-xs {
|
|
71
|
+
--sv-size-height: var(--sv-control-xs);
|
|
72
|
+
--sv-size-text: var(--sv-text-label);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.sv-size-sm {
|
|
76
|
+
--sv-size-height: var(--sv-control-sm);
|
|
77
|
+
--sv-size-text: var(--sv-text-xs);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.sv-size-md {
|
|
81
|
+
--sv-size-height: var(--sv-control-md);
|
|
82
|
+
--sv-size-text: var(--sv-text-sm);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.sv-size-lg {
|
|
86
|
+
--sv-size-height: var(--sv-control-lg);
|
|
87
|
+
--sv-size-text: var(--sv-text-md);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.sv-size-xl {
|
|
91
|
+
--sv-size-height: var(--sv-control-xl);
|
|
92
|
+
--sv-size-text: var(--sv-text-lg);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Density multiplies heights and paddings, never the text. */
|
|
96
|
+
.sv-density-compact {
|
|
97
|
+
--sv-density: var(--sv-density-compact);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.sv-density-default {
|
|
101
|
+
--sv-density: 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.sv-density-comfortable {
|
|
105
|
+
--sv-density: var(--sv-density-comfortable);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* `rounded` (true) clears any inherited value, so the component's own default applies:
|
|
110
|
+
* border-radius: var(--sv-rounded, <the component's radius>).
|
|
111
|
+
*/
|
|
112
|
+
.sv-rounded {
|
|
113
|
+
--sv-rounded: initial;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.sv-rounded-none {
|
|
117
|
+
--sv-rounded: 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.sv-rounded-sm {
|
|
121
|
+
--sv-rounded: var(--sv-radius-sm);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.sv-rounded-md {
|
|
125
|
+
--sv-rounded: var(--sv-radius-md);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.sv-rounded-lg {
|
|
129
|
+
--sv-rounded: var(--sv-radius-lg);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.sv-rounded-xl {
|
|
133
|
+
--sv-rounded: var(--sv-radius-xl);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* A pill in every theme, square themes included. */
|
|
137
|
+
.sv-rounded-full {
|
|
138
|
+
--sv-rounded: 9999px;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Design tokens shared by every theme. Colour, type families, shape, depth and motion come
|
|
3
|
+
* from the active theme (<ThemeProvider>) as --sv-color-*, --sv-on-* and the theme contract
|
|
4
|
+
* variables (--sv-font-*, --sv-radius-*, --sv-shadow, --sv-ease, --sv-duration-*, ...).
|
|
5
|
+
*
|
|
6
|
+
* Every scale here derives from the golden ratio φ = 1.6180339887: sizes are powers of φ,
|
|
7
|
+
* spacing follows the Fibonacci sequence (whose ratios converge to φ).
|
|
8
|
+
*/
|
|
9
|
+
@layer sv.tokens, sv.base, sv.components, sv.utilities;
|
|
10
|
+
|
|
11
|
+
@layer sv.tokens {
|
|
12
|
+
:root {
|
|
13
|
+
/* Type scale: 1rem × φ^(n/4), so user font-size preferences apply. */
|
|
14
|
+
--sv-text-label: 0.697rem; /* φ^-¾ ≈ 11.15px */
|
|
15
|
+
--sv-text-xs: 0.7862rem; /* φ^-½ ≈ 12.58px */
|
|
16
|
+
--sv-text-sm: 0.8867rem; /* φ^-¼ ≈ 14.19px, controls */
|
|
17
|
+
--sv-text-md: 1rem; /* 16px, body */
|
|
18
|
+
--sv-text-lg: 1.1278rem; /* φ^¼ ≈ 18.05px */
|
|
19
|
+
--sv-text-xl: 1.272rem; /* φ^½ ≈ 20.35px */
|
|
20
|
+
--sv-text-title: 1.618rem; /* φ ≈ 25.89px */
|
|
21
|
+
--sv-text-headline: clamp(1.8249rem, 1.6594rem + 0.7354vw, 2.3213rem); /* φ^1.25–φ^1.75 */
|
|
22
|
+
--sv-text-display: clamp(2.618rem, 2.0788rem + 2.397vw, 4.2361rem); /* φ²–φ³ */
|
|
23
|
+
|
|
24
|
+
--sv-weight-light: 300;
|
|
25
|
+
--sv-weight-regular: 400;
|
|
26
|
+
--sv-weight-medium: 500;
|
|
27
|
+
--sv-weight-semibold: 600;
|
|
28
|
+
|
|
29
|
+
--sv-leading-tight: 1.1278; /* φ^¼ */
|
|
30
|
+
--sv-leading-snug: 1.272; /* √φ */
|
|
31
|
+
--sv-leading-normal: 1.618; /* φ */
|
|
32
|
+
|
|
33
|
+
/* Spacing: Fibonacci, the suffix is the size in px at the default 16px root. */
|
|
34
|
+
--sv-space-2: 0.125rem;
|
|
35
|
+
--sv-space-3: 0.1875rem;
|
|
36
|
+
--sv-space-5: 0.3125rem;
|
|
37
|
+
--sv-space-8: 0.5rem;
|
|
38
|
+
--sv-space-13: 0.8125rem;
|
|
39
|
+
--sv-space-21: 1.3125rem;
|
|
40
|
+
--sv-space-34: 2.125rem;
|
|
41
|
+
--sv-space-55: 3.4375rem;
|
|
42
|
+
--sv-space-89: 5.5625rem;
|
|
43
|
+
--sv-space-144: 9rem;
|
|
44
|
+
|
|
45
|
+
/* Control heights: 1rem × φ^(k/2). md is φ² ≈ 41.9px. */
|
|
46
|
+
--sv-control-xs: 1.618rem; /* φ */
|
|
47
|
+
--sv-control-sm: 2.0582rem; /* φ^1.5 */
|
|
48
|
+
--sv-control-md: 2.618rem; /* φ² */
|
|
49
|
+
--sv-control-lg: 3.3302rem; /* φ^2.5 */
|
|
50
|
+
--sv-control-xl: 4.2361rem; /* φ³ */
|
|
51
|
+
|
|
52
|
+
/* Density moves a control one quarter step of φ. */
|
|
53
|
+
--sv-density-compact: 0.8867; /* φ^-¼ */
|
|
54
|
+
--sv-density-comfortable: 1.1278; /* φ^¼ */
|
|
55
|
+
|
|
56
|
+
/* The golden split, for two-column layouts: 61.8% / 38.2%. */
|
|
57
|
+
--sv-golden-major: 61.803%;
|
|
58
|
+
--sv-golden-minor: 38.197%;
|
|
59
|
+
|
|
60
|
+
/* Rules. */
|
|
61
|
+
--sv-hairline: 1px;
|
|
62
|
+
--sv-rule-total: 3px double;
|
|
63
|
+
|
|
64
|
+
/*
|
|
65
|
+
* Tonal fills (selected chips, badges, alerts): the intent colour as a light wash, with
|
|
66
|
+
* text mixed slightly toward the theme's text colour so it stays ≥ 4.5:1 on the wash:
|
|
67
|
+
* color-mix(in oklch, <intent> var(--sv-tonal-text-mix), var(--sv-color-text)).
|
|
68
|
+
* These follow the contrast tests rather than φ.
|
|
69
|
+
*/
|
|
70
|
+
--sv-tonal-wash: 12%;
|
|
71
|
+
--sv-tonal-text-mix: 80%;
|
|
72
|
+
|
|
73
|
+
/* Hover and press mix a control's foreground over its background by these amounts. */
|
|
74
|
+
--sv-state-hover: 8%;
|
|
75
|
+
--sv-state-press: 13%;
|
|
76
|
+
|
|
77
|
+
/* Focus ring. */
|
|
78
|
+
--sv-focus-width: 2px;
|
|
79
|
+
--sv-focus-offset: 2px;
|
|
80
|
+
|
|
81
|
+
/* Pressed controls move down by this much; never scale. */
|
|
82
|
+
--sv-press-offset: 1px;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Text for assistive technology only: out of sight, still read. Components that need it
|
|
3
|
+
* import this file, so it ships once and only with them.
|
|
4
|
+
*/
|
|
5
|
+
@layer sv.tokens, sv.base, sv.components, sv.utilities;
|
|
6
|
+
|
|
7
|
+
@layer sv.utilities {
|
|
8
|
+
.sv-visually-hidden {
|
|
9
|
+
position: absolute;
|
|
10
|
+
inline-size: 1px;
|
|
11
|
+
block-size: 1px;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
clip-path: inset(50%);
|
|
14
|
+
white-space: nowrap;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ThemeDefinition } from '../core/theme/types.js';
|
|
2
|
+
export declare const auroraLight: ThemeDefinition;
|
|
3
|
+
export declare const auroraDark: ThemeDefinition;
|
|
4
|
+
/**
|
|
5
|
+
* Aurora: Modern depth, dark first: frosted panels over an iris and mint glow. Manrope and JetBrains Mono.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <ThemeProvider themes={aurora}>…</ThemeProvider>
|
|
9
|
+
*/
|
|
10
|
+
export declare const aurora: {
|
|
11
|
+
'aurora-light': ThemeDefinition;
|
|
12
|
+
'aurora-dark': ThemeDefinition;
|
|
13
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Aurora. Modern depth, dark first: frosted panels over an iris and mint glow. Manrope and JetBrains Mono.
|
|
2
|
+
// Fonts are not bundled; see the theme docs for the Fontsource packages. `on-` colours are
|
|
3
|
+
// written out so these themes never pull in the colour maths.
|
|
4
|
+
const shared = {
|
|
5
|
+
'font-display': "'Manrope Variable', system-ui, sans-serif",
|
|
6
|
+
'font-ui': "'Manrope Variable', system-ui, sans-serif",
|
|
7
|
+
'font-data': "'JetBrains Mono Variable', ui-monospace, monospace",
|
|
8
|
+
'weight-display': 700,
|
|
9
|
+
'tracking-display': '-0.03em',
|
|
10
|
+
'radius-sm': '8px',
|
|
11
|
+
'radius-md': '13px',
|
|
12
|
+
'radius-lg': '21px',
|
|
13
|
+
'radius-xl': '34px',
|
|
14
|
+
'radius-full': '999px',
|
|
15
|
+
'label-size': '0.7862rem',
|
|
16
|
+
'label-transform': 'none',
|
|
17
|
+
'label-tracking': '0',
|
|
18
|
+
'button-transform': 'none',
|
|
19
|
+
'button-tracking': '0',
|
|
20
|
+
'surface-blur': '18px',
|
|
21
|
+
ornament: 'transparent',
|
|
22
|
+
ease: 'cubic-bezier(0.146, 1, 0.382, 1)',
|
|
23
|
+
'duration-fast': '144ms',
|
|
24
|
+
'duration-normal': '233ms',
|
|
25
|
+
'duration-slow': '377ms'
|
|
26
|
+
};
|
|
27
|
+
export const auroraLight = {
|
|
28
|
+
dark: false,
|
|
29
|
+
colors: {
|
|
30
|
+
background: 'oklch(0.975 0.01 285)',
|
|
31
|
+
surface: 'oklch(1 0 0)',
|
|
32
|
+
'surface-raised': 'oklch(0.955 0.014 285)',
|
|
33
|
+
border: 'oklch(0.90 0.015 285)',
|
|
34
|
+
'border-soft': 'oklch(0.93 0.012 285)',
|
|
35
|
+
'border-strong': 'oklch(0.60 0.03 285)',
|
|
36
|
+
text: 'oklch(0.22 0.04 282)',
|
|
37
|
+
'text-muted': 'oklch(0.42 0.04 282)',
|
|
38
|
+
'text-subtle': 'oklch(0.50 0.035 282)',
|
|
39
|
+
primary: 'oklch(0.50 0.20 285)',
|
|
40
|
+
success: 'oklch(0.50 0.12 165)',
|
|
41
|
+
warning: 'oklch(0.52 0.13 60)',
|
|
42
|
+
error: 'oklch(0.52 0.19 15)',
|
|
43
|
+
info: 'oklch(0.50 0.13 240)',
|
|
44
|
+
up: 'oklch(0.50 0.12 165)',
|
|
45
|
+
down: 'oklch(0.52 0.19 15)',
|
|
46
|
+
'on-primary': 'oklch(0.99 0.005 285)',
|
|
47
|
+
'on-success': 'oklch(0.985 0.005 165)',
|
|
48
|
+
'on-warning': 'oklch(0.985 0.005 60)',
|
|
49
|
+
'on-error': 'oklch(0.985 0.005 15)',
|
|
50
|
+
'on-info': 'oklch(0.985 0.005 240)',
|
|
51
|
+
'on-up': 'oklch(0.985 0.005 165)',
|
|
52
|
+
'on-down': 'oklch(0.985 0.005 15)'
|
|
53
|
+
},
|
|
54
|
+
variables: {
|
|
55
|
+
...shared,
|
|
56
|
+
'surface-alpha': '78%',
|
|
57
|
+
backdrop: 'radial-gradient(60% 50% at 10% 0%, oklch(0.75 0.14 287 / 0.28), transparent 70%), radial-gradient(50% 45% at 100% 15%, oklch(0.85 0.10 168 / 0.30), transparent 70%)',
|
|
58
|
+
bevel: 'oklch(1 0 0 / 0.7)',
|
|
59
|
+
shadow: '0 1px 2px oklch(0.3 0.08 285 / 0.08), 0 21px 55px -21px oklch(0.4 0.15 285 / 0.28)'
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
export const auroraDark = {
|
|
63
|
+
dark: true,
|
|
64
|
+
colors: {
|
|
65
|
+
background: 'oklch(0.165 0.035 282)',
|
|
66
|
+
surface: 'oklch(0.215 0.04 282)',
|
|
67
|
+
'surface-raised': 'oklch(0.27 0.045 282)',
|
|
68
|
+
border: 'oklch(0.34 0.04 282)',
|
|
69
|
+
'border-soft': 'oklch(0.28 0.04 282)',
|
|
70
|
+
'border-strong': 'oklch(0.58 0.05 282)',
|
|
71
|
+
text: 'oklch(0.97 0.008 282)',
|
|
72
|
+
'text-muted': 'oklch(0.80 0.02 282)',
|
|
73
|
+
'text-subtle': 'oklch(0.70 0.025 282)',
|
|
74
|
+
primary: 'oklch(0.74 0.15 287)',
|
|
75
|
+
success: 'oklch(0.80 0.13 168)',
|
|
76
|
+
warning: 'oklch(0.83 0.12 80)',
|
|
77
|
+
error: 'oklch(0.72 0.16 15)',
|
|
78
|
+
info: 'oklch(0.78 0.11 230)',
|
|
79
|
+
up: 'oklch(0.80 0.13 168)',
|
|
80
|
+
down: 'oklch(0.72 0.16 15)',
|
|
81
|
+
'on-primary': 'oklch(0.18 0.04 282)',
|
|
82
|
+
'on-success': 'oklch(0.2 0.02 168)',
|
|
83
|
+
'on-warning': 'oklch(0.2 0.02 80)',
|
|
84
|
+
'on-error': 'oklch(0.2 0.02 15)',
|
|
85
|
+
'on-info': 'oklch(0.2 0.02 230)',
|
|
86
|
+
'on-up': 'oklch(0.2 0.02 168)',
|
|
87
|
+
'on-down': 'oklch(0.2 0.02 15)'
|
|
88
|
+
},
|
|
89
|
+
variables: {
|
|
90
|
+
...shared,
|
|
91
|
+
'surface-alpha': '72%',
|
|
92
|
+
backdrop: 'radial-gradient(60% 50% at 10% 0%, oklch(0.55 0.2 287 / 0.38), transparent 70%), radial-gradient(50% 45% at 100% 15%, oklch(0.7 0.14 168 / 0.22), transparent 70%)',
|
|
93
|
+
bevel: 'oklch(1 0 0 / 0.08)',
|
|
94
|
+
shadow: 'inset 0 1px 0 oklch(1 0 0 / 0.06), 0 21px 55px -21px oklch(0.05 0.05 282 / 0.9)'
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Aurora: Modern depth, dark first: frosted panels over an iris and mint glow. Manrope and JetBrains Mono.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* <ThemeProvider themes={aurora}>…</ThemeProvider>
|
|
102
|
+
*/
|
|
103
|
+
export const aurora = { 'aurora-light': auroraLight, 'aurora-dark': auroraDark };
|