@dorsk/tsumikit 0.52.0 → 0.54.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 +25 -2
- package/dist/components/atoms/Badge.svelte +7 -6
- package/dist/components/layouts/Toolbar.svelte +83 -2
- package/dist/components/layouts/Toolbar.svelte.d.ts +4 -0
- package/dist/components/layouts/toolbar-roving.d.ts +53 -0
- package/dist/components/layouts/toolbar-roving.js +104 -0
- package/dist/components/molecules/FontScalePicker.svelte +37 -2
- package/dist/components/molecules/FontScalePicker.svelte.d.ts +5 -1
- package/dist/components/molecules/Menu.svelte +3 -0
- package/dist/components/molecules/Menu.svelte.d.ts +1 -1
- package/dist/components/molecules/Popover.svelte +49 -37
- package/dist/components/molecules/Popover.svelte.d.ts +9 -4
- package/dist/components/molecules/ThemePicker.svelte +36 -2
- package/dist/components/molecules/ThemePicker.svelte.d.ts +5 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -190,7 +190,7 @@ sanctioned escape hatch for everything else.
|
|
|
190
190
|
| component | published properties |
|
|
191
191
|
| --- | --- |
|
|
192
192
|
| `Button` | `--btn-bg`, `--btn-fg`, `--btn-border`, `--btn-size`, `--btn-radius`, `--btn-tone`, `--btn-on`, `--btn-box` |
|
|
193
|
-
| `Badge` | `--badge-bg`, `--badge-fg`, `--badge-border`, `--badge-radius`, `--badge-tone`, `--badge-max-width` |
|
|
193
|
+
| `Badge` | `--badge-bg`, `--badge-fg`, `--badge-border`, `--badge-radius`, `--badge-tone`, `--badge-max-width`, `--badge-fs`, `--badge-fw`, `--badge-min-size` |
|
|
194
194
|
| `Text` | `--txt-fg`, `--txt-size` |
|
|
195
195
|
| `Select` | `--select-bg`, `--select-fg`, `--select-border`, `--select-size`, `--select-radius` |
|
|
196
196
|
| `Input` | `--input-bg`, `--input-fg`, `--input-border`, `--input-size`, `--input-radius` |
|
|
@@ -204,10 +204,24 @@ sanctioned escape hatch for everything else.
|
|
|
204
204
|
| `Fieldset` | `--fieldset-pad`, `--fieldset-border` |
|
|
205
205
|
| `GitRef` | `--git-ref-tone`, `--git-ref-max-width` |
|
|
206
206
|
| `EmptyState` | `--empty-tone` |
|
|
207
|
+
| `Popover` | `--pop-trigger-bg`, `--pop-trigger-fg`, `--pop-trigger-border`, `--pop-trigger-radius`, `--pop-trigger-size`, `--pop-trigger-pad`, `--pop-box` |
|
|
207
208
|
|
|
208
209
|
`tests/css-custom-property-contract.test.js` reads this table and fails if a
|
|
209
210
|
listed property is not actually read by its component, so the docs cannot drift.
|
|
210
211
|
|
|
212
|
+
`Popover`'s hooks paint the **trigger**; hover states keep their own feedback
|
|
213
|
+
colours. `--pop-box: auto` releases the square floor, so `pill` plus
|
|
214
|
+
`--pop-trigger-pad` reshapes the default trigger into a chip of any height. `--pop-trigger-border` needs the chromed trigger — `bare` means no ring
|
|
215
|
+
by definition. `Badge`'s `--badge-min-size` pairs with `numeric` (already centred
|
|
216
|
+
and tabular) to turn a count badge into a square/circular counter.
|
|
217
|
+
|
|
218
|
+
A `triggerClass` on `Popover` (or `class` on the pickers that wrap it) is a hook
|
|
219
|
+
for a **globally loaded** stylesheet only. Your component's *scoped* CSS cannot
|
|
220
|
+
reach it: Svelte's scoping attribute is added to elements your own template
|
|
221
|
+
renders, and the trigger belongs to `Popover`, so `.toolbar .my-trigger` compiles
|
|
222
|
+
to `.toolbar.svelte-x .my-trigger.svelte-x` and matches nothing. Use `style` and
|
|
223
|
+
the properties above instead.
|
|
224
|
+
|
|
211
225
|
## Components
|
|
212
226
|
|
|
213
227
|
**Atoms:** Text, Heading, Button, Input (`icon` inset leading glyph,
|
|
@@ -509,6 +523,14 @@ triggers, and `SegmentedControl`. Each renders an exact
|
|
|
509
523
|
original ghost icon-button default. Use `control` on `Button` or `Popover` when
|
|
510
524
|
the roomier shared `--control-height` composer contract is required.
|
|
511
525
|
|
|
526
|
+
`Toolbar` implements the keyboard contract its `role="toolbar"` announces: the
|
|
527
|
+
bar is a **single tab stop**, and `←`/`→` (plus `Home`/`End`) move between the
|
|
528
|
+
controls that are actually visible — collapsed `data-overflow` children drop out
|
|
529
|
+
of the ring and the `…` trigger is its last stop. Controls that own the arrow
|
|
530
|
+
keys themselves (text inputs, `select`, sliders) keep them. Pass
|
|
531
|
+
`roving={false}` to opt out; that drops the role along with the promise, leaving
|
|
532
|
+
every child an ordinary tab stop.
|
|
533
|
+
|
|
512
534
|
Button and Popover share the same semantic tones. For a confirmed positive
|
|
513
535
|
action, `tone="success"` gives neutral controls a success tint; combine it with
|
|
514
536
|
`variant="primary"` for a filled success action without consumer CSS.
|
|
@@ -596,7 +618,8 @@ label, copy, line numbers, wrap, scroll) and takes code three ways: plain
|
|
|
596
618
|
`disabled` and native events pass through.
|
|
597
619
|
- Visible `:focus-visible` rings; ARIA patterns implemented for switch, menu
|
|
598
620
|
(`role=menu` + roving focus), tabs (`tablist` + arrow keys), radiogroup,
|
|
599
|
-
dialog; polite live region for
|
|
621
|
+
toolbar (`role=toolbar` + roving tabindex), dialog; polite live region for
|
|
622
|
+
toasts; `.sr-only`.
|
|
600
623
|
- Mobile-first: one `min-width: 640px` breakpoint, bottom-sheet→centered-modal,
|
|
601
624
|
safe-area insets, 16px-min inputs (no iOS zoom).
|
|
602
625
|
- A verified color-blind-safe theme (Okabe-Ito); meaning never relies on hue
|
|
@@ -167,8 +167,8 @@
|
|
|
167
167
|
gap: var(--sp-1);
|
|
168
168
|
padding: 0.15rem var(--sp-2);
|
|
169
169
|
border-radius: var(--badge-radius, var(--r-pill));
|
|
170
|
-
font-size: var(--fs-xs);
|
|
171
|
-
font-weight: var(--fw-medium);
|
|
170
|
+
font-size: var(--badge-fs, var(--fs-xs));
|
|
171
|
+
font-weight: var(--badge-fw, var(--fw-medium));
|
|
172
172
|
line-height: 1.4;
|
|
173
173
|
background: var(--badge-bg, var(--bg-elevated-2));
|
|
174
174
|
color: var(--badge-fg, var(--text-muted));
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
}
|
|
179
179
|
.badge-sm,
|
|
180
180
|
.badge-xs {
|
|
181
|
-
font-size: calc(var(--fs-xs) * 0.92);
|
|
181
|
+
font-size: var(--badge-fs, calc(var(--fs-xs) * 0.92));
|
|
182
182
|
gap: 0.15rem;
|
|
183
183
|
}
|
|
184
184
|
.badge-sm {
|
|
@@ -205,16 +205,17 @@
|
|
|
205
205
|
}
|
|
206
206
|
.mono {
|
|
207
207
|
font-family: var(--font-mono);
|
|
208
|
-
font-weight: var(--fw-normal);
|
|
208
|
+
font-weight: var(--badge-fw, var(--fw-normal));
|
|
209
209
|
}
|
|
210
210
|
.uppercase {
|
|
211
211
|
text-transform: uppercase;
|
|
212
212
|
letter-spacing: 0.04em;
|
|
213
|
-
font-weight: var(--fw-semibold);
|
|
213
|
+
font-weight: var(--badge-fw, var(--fw-semibold));
|
|
214
214
|
}
|
|
215
215
|
.numeric {
|
|
216
216
|
font-variant-numeric: tabular-nums;
|
|
217
|
-
min-width: 1.5em;
|
|
217
|
+
min-width: var(--badge-min-size, 1.5em);
|
|
218
|
+
min-height: var(--badge-min-size, auto);
|
|
218
219
|
justify-content: center;
|
|
219
220
|
}
|
|
220
221
|
.truncate {
|
|
@@ -3,10 +3,22 @@
|
|
|
3
3
|
// every child marked `data-overflow` is hidden and a `…` button opens a Menu
|
|
4
4
|
// with `items` (or the `overflow` snippet in a Popover) that stands in for
|
|
5
5
|
// them. `sticky` pins the bar under `stickyOffset`.
|
|
6
|
+
//
|
|
7
|
+
// `role="toolbar"` is a promise of keyboard behaviour, so the bar keeps it:
|
|
8
|
+
// roving tabindex makes it one tab stop and ←/→/Home/End move between the
|
|
9
|
+
// controls actually visible, the `…` trigger included. Children are arbitrary
|
|
10
|
+
// snippet content, so the ring is read from the DOM rather than a list prop.
|
|
6
11
|
import type { Snippet } from 'svelte';
|
|
7
12
|
import IconButton from '../molecules/IconButton.svelte';
|
|
8
13
|
import Menu, { type MenuItem } from '../molecules/Menu.svelte';
|
|
9
14
|
import Popover from '../molecules/Popover.svelte';
|
|
15
|
+
import {
|
|
16
|
+
consumesArrowKeys,
|
|
17
|
+
isToolbarNavKey,
|
|
18
|
+
nextToolbarStop,
|
|
19
|
+
TOOLBAR_STOP_ATTR,
|
|
20
|
+
toolbarStops
|
|
21
|
+
} from './toolbar-roving.js';
|
|
10
22
|
|
|
11
23
|
let {
|
|
12
24
|
children,
|
|
@@ -18,6 +30,7 @@
|
|
|
18
30
|
density = 'default',
|
|
19
31
|
overflowLabel = 'More',
|
|
20
32
|
label,
|
|
33
|
+
roving = true,
|
|
21
34
|
class: klass = '',
|
|
22
35
|
style: styleProp = ''
|
|
23
36
|
}: {
|
|
@@ -34,6 +47,10 @@
|
|
|
34
47
|
overflowLabel?: string;
|
|
35
48
|
/** Accessible name of the bar (`role="toolbar"`). */
|
|
36
49
|
label?: string;
|
|
50
|
+
/** Roving tabindex: the bar is one tab stop and ←/→ (plus Home/End) move
|
|
51
|
+
* between its controls. `false` drops `role="toolbar"` along with the
|
|
52
|
+
* keyboard contract it promises, leaving children as ordinary tab stops. */
|
|
53
|
+
roving?: boolean;
|
|
37
54
|
class?: string;
|
|
38
55
|
style?: string;
|
|
39
56
|
} = $props();
|
|
@@ -64,13 +81,77 @@
|
|
|
64
81
|
});
|
|
65
82
|
|
|
66
83
|
const hasOverflow = $derived(!!items?.length || !!overflow);
|
|
84
|
+
|
|
85
|
+
let activeStop: HTMLElement | null = null;
|
|
86
|
+
|
|
87
|
+
// Collapsed `data-overflow` children are `display: none`, so they have no box —
|
|
88
|
+
// that, not a class check, is what takes them out of the ring.
|
|
89
|
+
const stops = () => toolbarStops(el, (node) => node.getClientRects().length === 0);
|
|
90
|
+
|
|
91
|
+
function rove(list: HTMLElement[], index: number) {
|
|
92
|
+
activeStop = list[index] ?? null;
|
|
93
|
+
list.forEach((node, i) => {
|
|
94
|
+
node.setAttribute(TOOLBAR_STOP_ATTR, '');
|
|
95
|
+
node.tabIndex = i === index ? 0 : -1;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function sync() {
|
|
100
|
+
const list = stops();
|
|
101
|
+
if (!list.length) return;
|
|
102
|
+
const current = activeStop ? list.indexOf(activeStop) : -1;
|
|
103
|
+
rove(list, current < 0 ? 0 : current);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
$effect(() => {
|
|
107
|
+
if (!roving || !el) return;
|
|
108
|
+
const node = el;
|
|
109
|
+
sync();
|
|
110
|
+
// Only attributes the caller owns are observed: writing tabindex and
|
|
111
|
+
// TOOLBAR_STOP_ATTR back must not re-enter this.
|
|
112
|
+
const mo = new MutationObserver(sync);
|
|
113
|
+
mo.observe(node, {
|
|
114
|
+
childList: true,
|
|
115
|
+
subtree: true,
|
|
116
|
+
attributes: true,
|
|
117
|
+
attributeFilter: ['disabled', 'hidden', 'aria-hidden', 'class', 'style']
|
|
118
|
+
});
|
|
119
|
+
return () => mo.disconnect();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
function onKeydown(event: KeyboardEvent) {
|
|
123
|
+
if (!roving || event.defaultPrevented) return;
|
|
124
|
+
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
125
|
+
if (!isToolbarNavKey(event.key)) return;
|
|
126
|
+
const target = event.target as HTMLElement | null;
|
|
127
|
+
if (consumesArrowKeys(target)) return;
|
|
128
|
+
const list = stops();
|
|
129
|
+
const next = nextToolbarStop(
|
|
130
|
+
list.length,
|
|
131
|
+
list.findIndex((node) => node.contains(target)),
|
|
132
|
+
event.key
|
|
133
|
+
);
|
|
134
|
+
if (next === undefined) return;
|
|
135
|
+
event.preventDefault();
|
|
136
|
+
rove(list, next);
|
|
137
|
+
list[next].focus();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function onFocusin(event: FocusEvent) {
|
|
141
|
+
if (!roving) return;
|
|
142
|
+
const list = stops();
|
|
143
|
+
const index = list.findIndex((node) => node.contains(event.target as Node));
|
|
144
|
+
if (index >= 0 && list[index] !== activeStop) rove(list, index);
|
|
145
|
+
}
|
|
67
146
|
</script>
|
|
68
147
|
|
|
69
148
|
<div
|
|
70
149
|
bind:this={el}
|
|
71
150
|
data-tsu="Toolbar"
|
|
72
|
-
role=
|
|
73
|
-
aria-label={label}
|
|
151
|
+
role={roving ? 'toolbar' : undefined}
|
|
152
|
+
aria-label={roving ? label : undefined}
|
|
153
|
+
onkeydown={onKeydown}
|
|
154
|
+
onfocusin={onFocusin}
|
|
74
155
|
class="toolbar density-{density} {klass}"
|
|
75
156
|
class:sticky
|
|
76
157
|
class:collapsed
|
|
@@ -14,6 +14,10 @@ type $$ComponentProps = {
|
|
|
14
14
|
overflowLabel?: string;
|
|
15
15
|
/** Accessible name of the bar (`role="toolbar"`). */
|
|
16
16
|
label?: string;
|
|
17
|
+
/** Roving tabindex: the bar is one tab stop and ←/→ (plus Home/End) move
|
|
18
|
+
* between its controls. `false` drops `role="toolbar"` along with the
|
|
19
|
+
* keyboard contract it promises, leaving children as ordinary tab stops. */
|
|
20
|
+
roving?: boolean;
|
|
17
21
|
class?: string;
|
|
18
22
|
style?: string;
|
|
19
23
|
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** @param {string} key */
|
|
2
|
+
export function isToolbarNavKey(key: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Controls that own the arrow keys themselves (caret, spinner, native listbox):
|
|
5
|
+
* the toolbar must not steal them.
|
|
6
|
+
*
|
|
7
|
+
* @param {{ tagName?: string; type?: string; isContentEditable?: boolean } | null | undefined} el
|
|
8
|
+
*/
|
|
9
|
+
export function consumesArrowKeys(el: {
|
|
10
|
+
tagName?: string;
|
|
11
|
+
type?: string;
|
|
12
|
+
isContentEditable?: boolean;
|
|
13
|
+
} | null | undefined): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* @param {{
|
|
16
|
+
* disabled?: boolean;
|
|
17
|
+
* hidden?: boolean;
|
|
18
|
+
* ariaHidden?: boolean;
|
|
19
|
+
* inPopover?: boolean;
|
|
20
|
+
* tabindex?: string | null;
|
|
21
|
+
* claimed?: boolean;
|
|
22
|
+
* }} el
|
|
23
|
+
*/
|
|
24
|
+
export function acceptsRovingStop(el: {
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
hidden?: boolean;
|
|
27
|
+
ariaHidden?: boolean;
|
|
28
|
+
inPopover?: boolean;
|
|
29
|
+
tabindex?: string | null;
|
|
30
|
+
claimed?: boolean;
|
|
31
|
+
}): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* The ring, in DOM order. `isHidden` is injected because "has no box" is the only
|
|
34
|
+
* honest test for a collapsed child and it cannot be computed without layout.
|
|
35
|
+
*
|
|
36
|
+
* @param {Element | null} container
|
|
37
|
+
* @param {(node: HTMLElement) => boolean} isHidden
|
|
38
|
+
* @returns {HTMLElement[]}
|
|
39
|
+
*/
|
|
40
|
+
export function toolbarStops(container: Element | null, isHidden: (node: HTMLElement) => boolean): HTMLElement[];
|
|
41
|
+
/**
|
|
42
|
+
* Arrows wrap; Home/End jump to the ends. `undefined` means "not ours" — the
|
|
43
|
+
* caller must leave the event alone.
|
|
44
|
+
*
|
|
45
|
+
* @param {number} count
|
|
46
|
+
* @param {number} current
|
|
47
|
+
* @param {string} key
|
|
48
|
+
* @returns {number | undefined}
|
|
49
|
+
*/
|
|
50
|
+
export function nextToolbarStop(count: number, current: number, key: string): number | undefined;
|
|
51
|
+
export const TOOLBAR_STOP_SELECTOR: "button, a[href], input, select, textarea, summary, [tabindex]";
|
|
52
|
+
/** Marks a control the toolbar has taken over, so its `-1` reads as ours, not the author's. */
|
|
53
|
+
export const TOOLBAR_STOP_ATTR: "data-toolbar-stop";
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export const TOOLBAR_STOP_SELECTOR =
|
|
2
|
+
'button, a[href], input, select, textarea, summary, [tabindex]';
|
|
3
|
+
|
|
4
|
+
/** Marks a control the toolbar has taken over, so its `-1` reads as ours, not the author's. */
|
|
5
|
+
export const TOOLBAR_STOP_ATTR = 'data-toolbar-stop';
|
|
6
|
+
|
|
7
|
+
const NAV_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End']);
|
|
8
|
+
|
|
9
|
+
const ARROW_CONSUMING_INPUTS = new Set([
|
|
10
|
+
'text',
|
|
11
|
+
'search',
|
|
12
|
+
'email',
|
|
13
|
+
'url',
|
|
14
|
+
'tel',
|
|
15
|
+
'password',
|
|
16
|
+
'number',
|
|
17
|
+
'date',
|
|
18
|
+
'datetime-local',
|
|
19
|
+
'month',
|
|
20
|
+
'week',
|
|
21
|
+
'time',
|
|
22
|
+
'range',
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
/** @param {string} key */
|
|
26
|
+
export function isToolbarNavKey(key) {
|
|
27
|
+
return NAV_KEYS.has(key);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Controls that own the arrow keys themselves (caret, spinner, native listbox):
|
|
32
|
+
* the toolbar must not steal them.
|
|
33
|
+
*
|
|
34
|
+
* @param {{ tagName?: string; type?: string; isContentEditable?: boolean } | null | undefined} el
|
|
35
|
+
*/
|
|
36
|
+
export function consumesArrowKeys(el) {
|
|
37
|
+
if (!el) return false;
|
|
38
|
+
if (el.isContentEditable) return true;
|
|
39
|
+
const tag = (el.tagName ?? '').toLowerCase();
|
|
40
|
+
if (tag === 'textarea' || tag === 'select') return true;
|
|
41
|
+
if (tag !== 'input') return false;
|
|
42
|
+
return ARROW_CONSUMING_INPUTS.has((el.type ?? 'text').toLowerCase());
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {{
|
|
47
|
+
* disabled?: boolean;
|
|
48
|
+
* hidden?: boolean;
|
|
49
|
+
* ariaHidden?: boolean;
|
|
50
|
+
* inPopover?: boolean;
|
|
51
|
+
* tabindex?: string | null;
|
|
52
|
+
* claimed?: boolean;
|
|
53
|
+
* }} el
|
|
54
|
+
*/
|
|
55
|
+
export function acceptsRovingStop(el) {
|
|
56
|
+
if (el.disabled || el.hidden || el.ariaHidden || el.inPopover) return false;
|
|
57
|
+
return el.claimed === true || el.tabindex !== '-1';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The ring, in DOM order. `isHidden` is injected because "has no box" is the only
|
|
62
|
+
* honest test for a collapsed child and it cannot be computed without layout.
|
|
63
|
+
*
|
|
64
|
+
* @param {Element | null} container
|
|
65
|
+
* @param {(node: HTMLElement) => boolean} isHidden
|
|
66
|
+
* @returns {HTMLElement[]}
|
|
67
|
+
*/
|
|
68
|
+
export function toolbarStops(container, isHidden) {
|
|
69
|
+
if (!container) return [];
|
|
70
|
+
return [
|
|
71
|
+
.../** @type {NodeListOf<HTMLElement>} */ (container.querySelectorAll(TOOLBAR_STOP_SELECTOR)),
|
|
72
|
+
].filter((node) =>
|
|
73
|
+
acceptsRovingStop({
|
|
74
|
+
disabled: /** @type {HTMLButtonElement} */ (node).disabled,
|
|
75
|
+
hidden: node.hidden || isHidden(node),
|
|
76
|
+
ariaHidden: node.getAttribute('aria-hidden') === 'true',
|
|
77
|
+
inPopover: node.closest('[popover]') !== null,
|
|
78
|
+
tabindex: node.getAttribute('tabindex'),
|
|
79
|
+
claimed: node.hasAttribute(TOOLBAR_STOP_ATTR),
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Arrows wrap; Home/End jump to the ends. `undefined` means "not ours" — the
|
|
86
|
+
* caller must leave the event alone.
|
|
87
|
+
*
|
|
88
|
+
* @param {number} count
|
|
89
|
+
* @param {number} current
|
|
90
|
+
* @param {string} key
|
|
91
|
+
* @returns {number | undefined}
|
|
92
|
+
*/
|
|
93
|
+
export function nextToolbarStop(count, current, key) {
|
|
94
|
+
if (count <= 0 || current < 0 || current >= count) return undefined;
|
|
95
|
+
if (key === 'Home') return 0;
|
|
96
|
+
if (key === 'End') return count - 1;
|
|
97
|
+
|
|
98
|
+
let direction;
|
|
99
|
+
if (key === 'ArrowRight' || key === 'ArrowDown') direction = 1;
|
|
100
|
+
else if (key === 'ArrowLeft' || key === 'ArrowUp') direction = -1;
|
|
101
|
+
else return undefined;
|
|
102
|
+
|
|
103
|
+
return (((current + direction) % count) + count) % count;
|
|
104
|
+
}
|
|
@@ -4,18 +4,53 @@
|
|
|
4
4
|
// Everything inside the popover is sized in plain rem, never --fs-*: if the
|
|
5
5
|
// panel rescaled with the page the slider would move under the pointer
|
|
6
6
|
// mid-drag and oscillate between steps.
|
|
7
|
+
import type { ComponentProps } from 'svelte';
|
|
7
8
|
import Popover from './Popover.svelte';
|
|
8
9
|
import Slider from '../atoms/Slider.svelte';
|
|
9
10
|
import { fontScale, SCALE_LEVELS } from '../../stores/fontscale.svelte';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
type TriggerChrome = Pick<
|
|
13
|
+
ComponentProps<typeof Popover>,
|
|
14
|
+
'variant' | 'tone' | 'size' | 'box' | 'pill' | 'control' | 'block' | 'bare' | 'hitArea' | 'placement' | 'disabled'
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
box = 'md',
|
|
19
|
+
placement = 'bottom-end',
|
|
20
|
+
variant,
|
|
21
|
+
tone,
|
|
22
|
+
size,
|
|
23
|
+
pill,
|
|
24
|
+
control,
|
|
25
|
+
block,
|
|
26
|
+
bare,
|
|
27
|
+
hitArea,
|
|
28
|
+
disabled,
|
|
29
|
+
class: klass = '',
|
|
30
|
+
style: styleProp = '',
|
|
31
|
+
}: TriggerChrome & { class?: string; style?: string } = $props();
|
|
12
32
|
|
|
13
33
|
const index = $derived(Math.max(0, SCALE_LEVELS.findIndex((l) => l.id === fontScale.levelId)));
|
|
14
34
|
const level = $derived(SCALE_LEVELS[index]);
|
|
15
35
|
const set = (i: number) => fontScale.set(SCALE_LEVELS[Math.max(0, Math.min(SCALE_LEVELS.length - 1, i))].id);
|
|
16
36
|
</script>
|
|
17
37
|
|
|
18
|
-
<Popover
|
|
38
|
+
<Popover
|
|
39
|
+
label="Text size"
|
|
40
|
+
{placement}
|
|
41
|
+
{box}
|
|
42
|
+
{variant}
|
|
43
|
+
{tone}
|
|
44
|
+
{size}
|
|
45
|
+
{pill}
|
|
46
|
+
{control}
|
|
47
|
+
{block}
|
|
48
|
+
{bare}
|
|
49
|
+
{hitArea}
|
|
50
|
+
{disabled}
|
|
51
|
+
triggerClass={klass}
|
|
52
|
+
style={styleProp}
|
|
53
|
+
>
|
|
19
54
|
{#snippet trigger()}<span class="glyph" data-tsu="FontScalePicker" title="Text size: {level.label}">A</span>{/snippet}
|
|
20
55
|
<div class="panel">
|
|
21
56
|
<div class="row">
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
type
|
|
1
|
+
import type { ComponentProps } from 'svelte';
|
|
2
|
+
import Popover from './Popover.svelte';
|
|
3
|
+
type TriggerChrome = Pick<ComponentProps<typeof Popover>, 'variant' | 'tone' | 'size' | 'box' | 'pill' | 'control' | 'block' | 'bare' | 'hitArea' | 'placement' | 'disabled'>;
|
|
4
|
+
type $$ComponentProps = TriggerChrome & {
|
|
2
5
|
class?: string;
|
|
6
|
+
style?: string;
|
|
3
7
|
};
|
|
4
8
|
declare const FontScalePicker: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
5
9
|
type FontScalePicker = ReturnType<typeof FontScalePicker>;
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
| 'tone'
|
|
35
35
|
| 'size'
|
|
36
36
|
| 'box'
|
|
37
|
+
| 'pill'
|
|
37
38
|
| 'control'
|
|
38
39
|
| 'block'
|
|
39
40
|
| 'triggerClass'
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
tone,
|
|
56
57
|
size,
|
|
57
58
|
box,
|
|
59
|
+
pill,
|
|
58
60
|
control,
|
|
59
61
|
block,
|
|
60
62
|
triggerClass,
|
|
@@ -135,6 +137,7 @@
|
|
|
135
137
|
{tone}
|
|
136
138
|
{size}
|
|
137
139
|
{box}
|
|
140
|
+
{pill}
|
|
138
141
|
{control}
|
|
139
142
|
{block}
|
|
140
143
|
{triggerClass}
|
|
@@ -15,7 +15,7 @@ export interface MenuItem {
|
|
|
15
15
|
import type { ComponentProps, Snippet } from 'svelte';
|
|
16
16
|
import Popover from './Popover.svelte';
|
|
17
17
|
type PopoverProps = ComponentProps<typeof Popover>;
|
|
18
|
-
type TriggerChrome = Pick<PopoverProps, 'variant' | 'tone' | 'size' | 'box' | 'control' | 'block' | 'triggerClass' | 'bare' | 'hitArea' | 'disabled' | 'gap' | 'onopen' | 'onclose'>;
|
|
18
|
+
type TriggerChrome = Pick<PopoverProps, 'variant' | 'tone' | 'size' | 'box' | 'pill' | 'control' | 'block' | 'triggerClass' | 'bare' | 'hitArea' | 'disabled' | 'gap' | 'onopen' | 'onclose'>;
|
|
19
19
|
type $$ComponentProps = {
|
|
20
20
|
label: string;
|
|
21
21
|
items: MenuItem[];
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
tone = 'none',
|
|
33
33
|
size,
|
|
34
34
|
box,
|
|
35
|
+
pill = false,
|
|
35
36
|
control = false,
|
|
36
37
|
block = false,
|
|
37
38
|
hitArea = 'auto',
|
|
@@ -58,8 +59,11 @@
|
|
|
58
59
|
/** Panel content. Receives `{ close }` to dismiss the panel from inside;
|
|
59
60
|
* zero-argument snippets keep working. */
|
|
60
61
|
children: Snippet<[{ close: () => void }]>;
|
|
61
|
-
/** Extra class on the trigger button
|
|
62
|
-
*
|
|
62
|
+
/** Extra class on the trigger button, for a stylesheet you already load
|
|
63
|
+
* globally. Your *scoped* CSS cannot reach it: the element is rendered by
|
|
64
|
+
* `Popover`, so it carries `Popover`'s scope hash, not yours, and a
|
|
65
|
+
* selector like `.toolbar .my-trigger` matches nothing. Use `style` and
|
|
66
|
+
* the published `--pop-trigger-*` / `--pop-box` properties instead. */
|
|
63
67
|
triggerClass?: string;
|
|
64
68
|
/** Drop the default ghost-icon chrome so the trigger is an unstyled button
|
|
65
69
|
* you fully own (pair with `triggerClass`). */
|
|
@@ -71,9 +75,11 @@
|
|
|
71
75
|
size?: TriggerSize;
|
|
72
76
|
/** Shared square box scale (`--box-xs/sm/md/lg`) for an icon-only trigger,
|
|
73
77
|
* pinning the square exactly. The default trigger floors at the `sm` box
|
|
74
|
-
* and grows with its content; override that floor
|
|
75
|
-
*
|
|
78
|
+
* and grows with its content; override that floor with
|
|
79
|
+
* `style="--pop-box: …"` for any square the four tiers do not cover. */
|
|
76
80
|
box?: 'xs' | 'sm' | 'md' | 'lg';
|
|
81
|
+
/** Fully rounded trigger (`--r-pill`), as on `Button`. */
|
|
82
|
+
pill?: boolean;
|
|
77
83
|
/** Use the shared `--control-height` toolbar/composer contract. */
|
|
78
84
|
control?: boolean;
|
|
79
85
|
block?: boolean;
|
|
@@ -226,6 +232,7 @@
|
|
|
226
232
|
class:trigger-control={control}
|
|
227
233
|
class:trigger-block={block}
|
|
228
234
|
class:trigger-box={box !== undefined}
|
|
235
|
+
class:trigger-pill={pill}
|
|
229
236
|
class:hit-compact={hitArea === 'compact'}
|
|
230
237
|
style:--pop-box={box ? `var(--box-${box})` : undefined}
|
|
231
238
|
class:trigger-tone-accent={tone === 'accent'}
|
|
@@ -271,19 +278,20 @@
|
|
|
271
278
|
(triggerClass) overrides it; `:not(.bare)` keeps it off bare triggers such
|
|
272
279
|
as the Timestamp <time>, which stay plain inline text. */
|
|
273
280
|
/* The square is one knob, `--pop-box`: the `box` prop sets it inline, and a
|
|
274
|
-
consumer
|
|
275
|
-
|
|
281
|
+
consumer reaches it with `style="--pop-box: var(--box-xs)"` — a plain
|
|
282
|
+
width/height would not have beaten a `min-*` floor. */
|
|
276
283
|
:where(.pop-trigger:not(.bare)) {
|
|
277
284
|
display: inline-flex;
|
|
278
285
|
align-items: center;
|
|
279
286
|
justify-content: center;
|
|
280
287
|
min-height: var(--pop-box, var(--box-sm));
|
|
281
288
|
min-width: var(--pop-box, var(--box-sm));
|
|
282
|
-
padding: var(--sp-1);
|
|
283
|
-
border: 1px solid transparent;
|
|
284
|
-
border-radius: var(--r-md);
|
|
285
|
-
background: transparent;
|
|
286
|
-
color: var(--text);
|
|
289
|
+
padding: var(--pop-trigger-pad, var(--sp-1));
|
|
290
|
+
border: 1px solid var(--pop-trigger-border, transparent);
|
|
291
|
+
border-radius: var(--pop-trigger-radius, var(--r-md));
|
|
292
|
+
background: var(--pop-trigger-bg, transparent);
|
|
293
|
+
color: var(--pop-trigger-fg, var(--text));
|
|
294
|
+
font-size: var(--pop-trigger-size, inherit);
|
|
287
295
|
transition:
|
|
288
296
|
background 0.12s var(--ease),
|
|
289
297
|
border-color 0.12s var(--ease);
|
|
@@ -296,11 +304,11 @@
|
|
|
296
304
|
.pop-trigger.canonical {
|
|
297
305
|
min-width: 0;
|
|
298
306
|
min-height: var(--control-height-default);
|
|
299
|
-
padding: var(--sp-2) var(--sp-4);
|
|
300
|
-
border-color: var(--border-strong);
|
|
301
|
-
background: var(--surface);
|
|
307
|
+
padding: var(--pop-trigger-pad, var(--sp-2) var(--sp-4));
|
|
308
|
+
border-color: var(--pop-trigger-border, var(--border-strong));
|
|
309
|
+
background: var(--pop-trigger-bg, var(--surface));
|
|
302
310
|
font-weight: var(--fw-medium);
|
|
303
|
-
font-size: var(--fs-sm);
|
|
311
|
+
font-size: var(--pop-trigger-size, var(--fs-sm));
|
|
304
312
|
line-height: 1;
|
|
305
313
|
user-select: none;
|
|
306
314
|
white-space: nowrap;
|
|
@@ -310,9 +318,9 @@
|
|
|
310
318
|
background: var(--surface);
|
|
311
319
|
}
|
|
312
320
|
.pop-trigger.trigger-primary {
|
|
313
|
-
background: var(--accent);
|
|
314
|
-
border-color: var(--accent);
|
|
315
|
-
color: var(--text-on-accent);
|
|
321
|
+
background: var(--pop-trigger-bg, var(--accent));
|
|
322
|
+
border-color: var(--pop-trigger-border, var(--accent));
|
|
323
|
+
color: var(--pop-trigger-fg, var(--text-on-accent));
|
|
316
324
|
font-weight: var(--fw-semibold);
|
|
317
325
|
}
|
|
318
326
|
.pop-trigger.trigger-primary:hover:not(:disabled) {
|
|
@@ -321,16 +329,16 @@
|
|
|
321
329
|
filter: brightness(1.08);
|
|
322
330
|
}
|
|
323
331
|
.pop-trigger.trigger-ghost {
|
|
324
|
-
background: transparent;
|
|
325
|
-
border-color: transparent;
|
|
332
|
+
background: var(--pop-trigger-bg, transparent);
|
|
333
|
+
border-color: var(--pop-trigger-border, transparent);
|
|
326
334
|
}
|
|
327
335
|
.pop-trigger.trigger-ghost:hover:not(:disabled) {
|
|
328
336
|
background: var(--bg-elevated-2);
|
|
329
337
|
border-color: transparent;
|
|
330
338
|
}
|
|
331
339
|
.pop-trigger.trigger-danger {
|
|
332
|
-
color: var(--danger);
|
|
333
|
-
border-color: color-mix(in srgb, var(--danger) 50%, var(--border));
|
|
340
|
+
color: var(--pop-trigger-fg, var(--danger));
|
|
341
|
+
border-color: var(--pop-trigger-border, color-mix(in srgb, var(--danger) 50%, var(--border)));
|
|
334
342
|
}
|
|
335
343
|
.pop-trigger.trigger-danger:hover:not(:disabled) {
|
|
336
344
|
background: color-mix(in srgb, var(--danger) 14%, transparent);
|
|
@@ -339,28 +347,31 @@
|
|
|
339
347
|
.pop-trigger.trigger-sm {
|
|
340
348
|
height: var(--control-height-compact);
|
|
341
349
|
min-height: var(--control-height-compact);
|
|
342
|
-
padding: var(--sp-1) var(--sp-3);
|
|
343
|
-
font-size: var(--fs-xs);
|
|
350
|
+
padding: var(--pop-trigger-pad, var(--sp-1) var(--sp-3));
|
|
351
|
+
font-size: var(--pop-trigger-size, var(--fs-xs));
|
|
344
352
|
}
|
|
345
353
|
.pop-trigger.trigger-lg {
|
|
346
354
|
min-height: var(--control-height-large);
|
|
347
|
-
padding: var(--sp-3) var(--sp-5);
|
|
348
|
-
font-size: var(--fs-base);
|
|
355
|
+
padding: var(--pop-trigger-pad, var(--sp-3) var(--sp-5));
|
|
356
|
+
font-size: var(--pop-trigger-size, var(--fs-base));
|
|
349
357
|
}
|
|
350
358
|
.pop-trigger.trigger-control {
|
|
351
359
|
height: var(--control-height);
|
|
352
360
|
min-height: var(--control-height);
|
|
353
|
-
padding: 0 var(--sp-3);
|
|
361
|
+
padding: var(--pop-trigger-pad, 0 var(--sp-3));
|
|
354
362
|
}
|
|
355
363
|
.pop-trigger.trigger-block {
|
|
356
364
|
width: 100%;
|
|
357
365
|
}
|
|
366
|
+
.pop-trigger.trigger-pill {
|
|
367
|
+
border-radius: var(--pop-trigger-radius, var(--r-pill));
|
|
368
|
+
}
|
|
358
369
|
.pop-trigger.trigger-box {
|
|
359
370
|
width: var(--pop-box);
|
|
360
371
|
min-width: var(--pop-box);
|
|
361
372
|
height: var(--pop-box);
|
|
362
373
|
min-height: var(--pop-box);
|
|
363
|
-
padding: 0;
|
|
374
|
+
padding: var(--pop-trigger-pad, 0);
|
|
364
375
|
flex: none;
|
|
365
376
|
}
|
|
366
377
|
/* Coarse pointers: icon-only triggers extend their hit area to --touch-target
|
|
@@ -395,8 +406,8 @@
|
|
|
395
406
|
.pop-trigger.trigger-tone-info,
|
|
396
407
|
.pop-trigger.trigger-tone-warn,
|
|
397
408
|
.pop-trigger.trigger-tone-danger {
|
|
398
|
-
color: var(--pop-trigger-tone);
|
|
399
|
-
border-color: color-mix(in srgb, var(--pop-trigger-tone) 50%, var(--border));
|
|
409
|
+
color: var(--pop-trigger-fg, var(--pop-trigger-tone));
|
|
410
|
+
border-color: var(--pop-trigger-border, color-mix(in srgb, var(--pop-trigger-tone) 50%, var(--border)));
|
|
400
411
|
}
|
|
401
412
|
.pop-trigger.trigger-tone-accent:hover:not(:disabled),
|
|
402
413
|
.pop-trigger.trigger-tone-success:hover:not(:disabled),
|
|
@@ -407,9 +418,9 @@
|
|
|
407
418
|
border-color: var(--pop-trigger-tone);
|
|
408
419
|
}
|
|
409
420
|
.pop-trigger.trigger-primary.trigger-tone-success {
|
|
410
|
-
background: var(--ok);
|
|
411
|
-
border-color: var(--ok);
|
|
412
|
-
color: var(--text-on-success);
|
|
421
|
+
background: var(--pop-trigger-bg, var(--ok));
|
|
422
|
+
border-color: var(--pop-trigger-border, var(--ok));
|
|
423
|
+
color: var(--pop-trigger-fg, var(--text-on-success));
|
|
413
424
|
}
|
|
414
425
|
.pop-trigger.trigger-primary.trigger-tone-success:hover:not(:disabled) {
|
|
415
426
|
background: var(--ok);
|
|
@@ -431,12 +442,13 @@
|
|
|
431
442
|
/* `bare`: strip the chrome down to a plain button the consumer styles. */
|
|
432
443
|
:where(.pop-trigger.bare) {
|
|
433
444
|
display: inline;
|
|
434
|
-
padding: 0;
|
|
445
|
+
padding: var(--pop-trigger-pad, 0);
|
|
435
446
|
border: 0;
|
|
436
|
-
border-radius: 0;
|
|
437
|
-
background: none;
|
|
438
|
-
color: inherit;
|
|
447
|
+
border-radius: var(--pop-trigger-radius, 0);
|
|
448
|
+
background: var(--pop-trigger-bg, none);
|
|
449
|
+
color: var(--pop-trigger-fg, inherit);
|
|
439
450
|
font: inherit;
|
|
451
|
+
font-size: var(--pop-trigger-size, inherit);
|
|
440
452
|
line-height: inherit;
|
|
441
453
|
vertical-align: baseline;
|
|
442
454
|
cursor: pointer;
|
|
@@ -17,8 +17,11 @@ type $$ComponentProps = {
|
|
|
17
17
|
children: Snippet<[{
|
|
18
18
|
close: () => void;
|
|
19
19
|
}]>;
|
|
20
|
-
/** Extra class on the trigger button
|
|
21
|
-
*
|
|
20
|
+
/** Extra class on the trigger button, for a stylesheet you already load
|
|
21
|
+
* globally. Your *scoped* CSS cannot reach it: the element is rendered by
|
|
22
|
+
* `Popover`, so it carries `Popover`'s scope hash, not yours, and a
|
|
23
|
+
* selector like `.toolbar .my-trigger` matches nothing. Use `style` and
|
|
24
|
+
* the published `--pop-trigger-*` / `--pop-box` properties instead. */
|
|
22
25
|
triggerClass?: string;
|
|
23
26
|
/** Drop the default ghost-icon chrome so the trigger is an unstyled button
|
|
24
27
|
* you fully own (pair with `triggerClass`). */
|
|
@@ -30,9 +33,11 @@ type $$ComponentProps = {
|
|
|
30
33
|
size?: TriggerSize;
|
|
31
34
|
/** Shared square box scale (`--box-xs/sm/md/lg`) for an icon-only trigger,
|
|
32
35
|
* pinning the square exactly. The default trigger floors at the `sm` box
|
|
33
|
-
* and grows with its content; override that floor
|
|
34
|
-
*
|
|
36
|
+
* and grows with its content; override that floor with
|
|
37
|
+
* `style="--pop-box: …"` for any square the four tiers do not cover. */
|
|
35
38
|
box?: 'xs' | 'sm' | 'md' | 'lg';
|
|
39
|
+
/** Fully rounded trigger (`--r-pill`), as on `Button`. */
|
|
40
|
+
pill?: boolean;
|
|
36
41
|
/** Use the shared `--control-height` toolbar/composer contract. */
|
|
37
42
|
control?: boolean;
|
|
38
43
|
block?: boolean;
|
|
@@ -5,18 +5,36 @@
|
|
|
5
5
|
// (--c-bg, --c-surface, --c-text, --c-accent; the derived --bg etc. are
|
|
6
6
|
// resolved once at :root and would not re-scope). The root default theme
|
|
7
7
|
// has no [data-theme] block, so its swatch carries the :root values.
|
|
8
|
+
import type { ComponentProps } from 'svelte';
|
|
8
9
|
import Popover from './Popover.svelte';
|
|
9
10
|
import { type ThemeDef, theme } from '../../stores/theme.svelte';
|
|
10
11
|
import { AUTO_THEME } from '../../theme-mode';
|
|
11
12
|
|
|
13
|
+
type TriggerChrome = Pick<
|
|
14
|
+
ComponentProps<typeof Popover>,
|
|
15
|
+
'variant' | 'tone' | 'size' | 'box' | 'pill' | 'control' | 'block' | 'bare' | 'hitArea' | 'placement' | 'disabled'
|
|
16
|
+
>;
|
|
17
|
+
|
|
12
18
|
let {
|
|
19
|
+
box = 'md',
|
|
20
|
+
placement = 'bottom-end',
|
|
21
|
+
variant,
|
|
22
|
+
tone,
|
|
23
|
+
size,
|
|
24
|
+
pill,
|
|
25
|
+
control,
|
|
26
|
+
block,
|
|
27
|
+
bare,
|
|
28
|
+
hitArea,
|
|
29
|
+
disabled,
|
|
13
30
|
auto = false,
|
|
14
31
|
autoLabel = 'Auto',
|
|
15
32
|
autoHelp = 'Follow the system light/dark setting',
|
|
16
33
|
lightLabel = 'Light',
|
|
17
34
|
darkLabel = 'Dark',
|
|
18
35
|
class: klass = '',
|
|
19
|
-
|
|
36
|
+
style: styleProp = '',
|
|
37
|
+
}: TriggerChrome & {
|
|
20
38
|
/** Offer an "auto" row that follows `prefers-color-scheme`, remembering
|
|
21
39
|
* one light and one dark theme. */
|
|
22
40
|
auto?: boolean;
|
|
@@ -25,6 +43,7 @@
|
|
|
25
43
|
lightLabel?: string;
|
|
26
44
|
darkLabel?: string;
|
|
27
45
|
class?: string;
|
|
46
|
+
style?: string;
|
|
28
47
|
} = $props();
|
|
29
48
|
|
|
30
49
|
let hovered = $state<ThemeDef | null>(null);
|
|
@@ -54,7 +73,22 @@
|
|
|
54
73
|
</span>
|
|
55
74
|
{/snippet}
|
|
56
75
|
|
|
57
|
-
<Popover
|
|
76
|
+
<Popover
|
|
77
|
+
label={title}
|
|
78
|
+
{placement}
|
|
79
|
+
{box}
|
|
80
|
+
{variant}
|
|
81
|
+
{tone}
|
|
82
|
+
{size}
|
|
83
|
+
{pill}
|
|
84
|
+
{control}
|
|
85
|
+
{block}
|
|
86
|
+
{bare}
|
|
87
|
+
{hitArea}
|
|
88
|
+
{disabled}
|
|
89
|
+
triggerClass={klass}
|
|
90
|
+
style={styleProp}
|
|
91
|
+
>
|
|
58
92
|
{#snippet trigger()}<span class="trigger" data-tsu="ThemePicker" {title}>{@render swatch(theme.current)}{#if isAuto}<span class="auto-dot" aria-hidden="true">◐</span>{/if}</span>{/snippet}
|
|
59
93
|
<div class="panel">
|
|
60
94
|
{#each groups as g (g.mode)}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
type
|
|
1
|
+
import type { ComponentProps } from 'svelte';
|
|
2
|
+
import Popover from './Popover.svelte';
|
|
3
|
+
type TriggerChrome = Pick<ComponentProps<typeof Popover>, 'variant' | 'tone' | 'size' | 'box' | 'pill' | 'control' | 'block' | 'bare' | 'hitArea' | 'placement' | 'disabled'>;
|
|
4
|
+
type $$ComponentProps = TriggerChrome & {
|
|
2
5
|
/** Offer an "auto" row that follows `prefers-color-scheme`, remembering
|
|
3
6
|
* one light and one dark theme. */
|
|
4
7
|
auto?: boolean;
|
|
@@ -7,6 +10,7 @@ type $$ComponentProps = {
|
|
|
7
10
|
lightLabel?: string;
|
|
8
11
|
darkLabel?: string;
|
|
9
12
|
class?: string;
|
|
13
|
+
style?: string;
|
|
10
14
|
};
|
|
11
15
|
declare const ThemePicker: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
16
|
type ThemePicker = ReturnType<typeof ThemePicker>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dorsk/tsumikit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.0",
|
|
4
4
|
"description": "Minimal, dependency-free Svelte 5 + pure-CSS UI kit. Token-driven atoms, molecules & layouts with theming out of the box.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"@sveltejs/kit": "^2.22.0",
|
|
74
74
|
"@sveltejs/package": "^2.3.0",
|
|
75
75
|
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
|
76
|
+
"@types/jsdom": "^30.0.0",
|
|
76
77
|
"@types/node": "^25.9.3",
|
|
77
78
|
"esbuild": "^0.28.2",
|
|
78
79
|
"esbuild-svelte": "^0.9.5",
|