@dorsk/tsumikit 0.74.0 → 0.75.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 +38 -1
- package/dist/autoresize.d.ts +2 -0
- package/dist/autoresize.js +8 -1
- package/dist/components/atoms/Artwork.svelte +12 -5
- package/dist/components/atoms/Avatar.svelte +1 -1
- package/dist/components/atoms/Badge.svelte +59 -23
- package/dist/components/atoms/Button.svelte +130 -96
- package/dist/components/atoms/Button.svelte.d.ts +2 -0
- package/dist/components/atoms/Card.svelte +10 -4
- package/dist/components/atoms/Checkbox.svelte +36 -9
- package/dist/components/atoms/Checkbox.svelte.d.ts +2 -0
- package/dist/components/atoms/Gauge.svelte +20 -14
- package/dist/components/atoms/Heading.svelte +1 -1
- package/dist/components/atoms/Icon.svelte +1 -0
- package/dist/components/atoms/Input.svelte +67 -13
- package/dist/components/atoms/Kbd.svelte +3 -3
- package/dist/components/atoms/Link.svelte +7 -2
- package/dist/components/atoms/Progress.svelte +4 -2
- package/dist/components/atoms/Scrim.svelte +3 -8
- package/dist/components/atoms/SegmentedProgress.svelte +5 -4
- package/dist/components/atoms/Select.svelte +58 -27
- package/dist/components/atoms/Slider.svelte +79 -22
- package/dist/components/atoms/Slider.svelte.d.ts +2 -0
- package/dist/components/atoms/Swatch.svelte +19 -8
- package/dist/components/atoms/Switch.svelte +34 -21
- package/dist/components/atoms/Switch.svelte.d.ts +2 -0
- package/dist/components/atoms/Text.svelte +1 -1
- package/dist/components/atoms/Textarea.svelte +34 -12
- package/dist/components/atoms/TimeDisplay.svelte +4 -2
- package/dist/components/layouts/AppShell.svelte +26 -15
- package/dist/components/layouts/MasterDetail.svelte +23 -21
- package/dist/components/layouts/NavBar.svelte +11 -8
- package/dist/components/layouts/NavItem.svelte +15 -7
- package/dist/components/layouts/NavSection.svelte +1 -1
- package/dist/components/layouts/ResizablePanel.svelte +37 -16
- package/dist/components/layouts/TabBar.svelte +24 -14
- package/dist/components/layouts/TabBar.svelte.d.ts +2 -0
- package/dist/components/molecules/Accordion.svelte +4 -0
- package/dist/components/molecules/Accordion.svelte.d.ts +2 -0
- package/dist/components/molecules/AttachmentList.svelte +14 -11
- package/dist/components/molecules/Breadcrumb.svelte +13 -9
- package/dist/components/molecules/Callout.svelte +3 -3
- package/dist/components/molecules/CapBar.svelte +23 -22
- package/dist/components/molecules/Carousel.svelte +27 -17
- package/dist/components/molecules/ChatBubble.svelte +2 -2
- package/dist/components/molecules/CodeBlock.svelte +2 -2
- package/dist/components/molecules/Combobox.svelte +30 -1
- package/dist/components/molecules/Combobox.svelte.d.ts +4 -0
- package/dist/components/molecules/Composer.svelte +2 -2
- package/dist/components/molecules/ConfirmModal.svelte +4 -0
- package/dist/components/molecules/ConfirmModal.svelte.d.ts +2 -0
- package/dist/components/molecules/CopyButton.svelte +46 -2
- package/dist/components/molecules/CopyButton.svelte.d.ts +2 -0
- package/dist/components/molecules/Disclosure.svelte +23 -12
- package/dist/components/molecules/Disclosure.svelte.d.ts +2 -0
- package/dist/components/molecules/Drawer.svelte +43 -23
- package/dist/components/molecules/Drawer.svelte.d.ts +2 -0
- package/dist/components/molecules/Dropzone.svelte +24 -8
- package/dist/components/molecules/EmojiPicker.svelte +27 -8
- package/dist/components/molecules/EmojiPicker.svelte.d.ts +2 -0
- package/dist/components/molecules/EmptyState.svelte +5 -5
- package/dist/components/molecules/Fieldset.svelte +8 -13
- package/dist/components/molecules/FileButton.svelte +57 -31
- package/dist/components/molecules/FileButton.svelte.d.ts +2 -0
- package/dist/components/molecules/FilterInput.svelte +51 -11
- package/dist/components/molecules/FontScalePicker.svelte +20 -9
- package/dist/components/molecules/GitRef.svelte +9 -7
- package/dist/components/molecules/GlyphStack.svelte +38 -14
- package/dist/components/molecules/IconButton.svelte +6 -3
- package/dist/components/molecules/IconButton.svelte.d.ts +2 -0
- package/dist/components/molecules/InputGroup.svelte +13 -12
- package/dist/components/molecules/LoadMore.svelte +6 -1
- package/dist/components/molecules/LoadMore.svelte.d.ts +2 -0
- package/dist/components/molecules/Menu.svelte +33 -7
- package/dist/components/molecules/Menu.svelte.d.ts +2 -0
- package/dist/components/molecules/Metric.svelte +11 -9
- package/dist/components/molecules/Modal.svelte +76 -26
- package/dist/components/molecules/Modal.svelte.d.ts +2 -0
- package/dist/components/molecules/OptionButton.svelte +28 -10
- package/dist/components/molecules/OptionButton.svelte.d.ts +2 -0
- package/dist/components/molecules/Pagination.svelte +26 -9
- package/dist/components/molecules/Pagination.svelte.d.ts +2 -0
- package/dist/components/molecules/Popover.svelte +107 -51
- package/dist/components/molecules/Popover.svelte.d.ts +2 -0
- package/dist/components/molecules/Prose.svelte +1 -1
- package/dist/components/molecules/PullToRefresh.svelte +14 -10
- package/dist/components/molecules/PullToRefresh.svelte.d.ts +2 -0
- package/dist/components/molecules/RadialMenu.svelte +52 -19
- package/dist/components/molecules/RadialMenu.svelte.d.ts +2 -0
- package/dist/components/molecules/RadioGroup.svelte +38 -23
- package/dist/components/molecules/RadioGroup.svelte.d.ts +2 -0
- package/dist/components/molecules/SectionHeader.svelte +13 -8
- package/dist/components/molecules/SeekSlider.svelte +38 -27
- package/dist/components/molecules/SeekSlider.svelte.d.ts +2 -0
- package/dist/components/molecules/SegmentedControl.svelte +139 -86
- package/dist/components/molecules/SegmentedControl.svelte.d.ts +2 -0
- package/dist/components/molecules/SelectButton.svelte +21 -6
- package/dist/components/molecules/SelectButton.svelte.d.ts +2 -0
- package/dist/components/molecules/SortableList.svelte +10 -11
- package/dist/components/molecules/SoundSettings.svelte +161 -0
- package/dist/components/molecules/SoundSettings.svelte.d.ts +17 -0
- package/dist/components/molecules/SplitButton.svelte +30 -3
- package/dist/components/molecules/SplitButton.svelte.d.ts +2 -0
- package/dist/components/molecules/SwipeRow.svelte +18 -15
- package/dist/components/molecules/SwipeRow.svelte.d.ts +2 -0
- package/dist/components/molecules/Tabs.svelte +92 -10
- package/dist/components/molecules/Tabs.svelte.d.ts +2 -0
- package/dist/components/molecules/ThemePicker.svelte +25 -10
- package/dist/components/molecules/Timestamp.svelte +12 -7
- package/dist/components/molecules/Toaster.svelte +65 -14
- package/dist/components/molecules/Toaster.svelte.d.ts +2 -0
- package/dist/components/molecules/Toggle.svelte +36 -13
- package/dist/components/molecules/Toggle.svelte.d.ts +2 -0
- package/dist/components/molecules/Tooltip.svelte +41 -10
- package/dist/components/molecules/Tooltip.svelte.d.ts +1 -1
- package/dist/components/molecules/VolumeSlider.svelte +34 -14
- package/dist/components/molecules/VolumeSlider.svelte.d.ts +2 -0
- package/dist/components/molecules/WorkingDir.svelte +1 -0
- package/dist/components/organisms/ConversationFeed.svelte +4 -0
- package/dist/components/organisms/DataTable.svelte +37 -14
- package/dist/floating.js +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/sound-synth.js +17 -12
- package/dist/stores/toast.svelte.d.ts +9 -0
- package/dist/stores/toast.svelte.js +45 -9
- package/package.json +1 -1
|
@@ -48,6 +48,8 @@
|
|
|
48
48
|
loading?: boolean;
|
|
49
49
|
/** Handler of the primary action. */
|
|
50
50
|
onclick?: (e: MouseEvent) => void;
|
|
51
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
52
|
+
sound?: boolean;
|
|
51
53
|
class?: string;
|
|
52
54
|
style?: string;
|
|
53
55
|
/** Content of the primary action. */
|
|
@@ -75,6 +77,7 @@
|
|
|
75
77
|
type = 'button',
|
|
76
78
|
loading = false,
|
|
77
79
|
onclick,
|
|
80
|
+
sound = true,
|
|
78
81
|
class: klass = '',
|
|
79
82
|
style: styleProp = '',
|
|
80
83
|
children,
|
|
@@ -102,6 +105,8 @@
|
|
|
102
105
|
class="split {klass}"
|
|
103
106
|
class:split-primary={variant === 'primary'}
|
|
104
107
|
class:split-half={caret === 'half'}
|
|
108
|
+
class:split-sm={size === 'sm'}
|
|
109
|
+
class:split-lg={size === 'lg'}
|
|
105
110
|
style={styleProp}
|
|
106
111
|
style:--split-height={height}
|
|
107
112
|
>
|
|
@@ -114,6 +119,7 @@
|
|
|
114
119
|
{type}
|
|
115
120
|
{loading}
|
|
116
121
|
{disabled}
|
|
122
|
+
{sound}
|
|
117
123
|
block
|
|
118
124
|
{onclick}
|
|
119
125
|
onkeydown={onPrimaryKeydown}
|
|
@@ -139,7 +145,7 @@
|
|
|
139
145
|
{onopen}
|
|
140
146
|
{onclose}
|
|
141
147
|
>
|
|
142
|
-
{#snippet trigger()}<Icon name="chevron-down"
|
|
148
|
+
{#snippet trigger()}<span class="caret-glyph"><Icon name="chevron-down" /></span>{/snippet}
|
|
143
149
|
</Menu>
|
|
144
150
|
</span>
|
|
145
151
|
</div>
|
|
@@ -169,14 +175,35 @@
|
|
|
169
175
|
.split-half .split-caret {
|
|
170
176
|
width: calc(var(--split-height) / 2);
|
|
171
177
|
}
|
|
178
|
+
.caret-glyph {
|
|
179
|
+
display: inline-flex;
|
|
180
|
+
align-items: center;
|
|
181
|
+
font-size: var(--control-icon-md);
|
|
182
|
+
}
|
|
183
|
+
.split-sm .caret-glyph {
|
|
184
|
+
font-size: var(--control-icon-sm);
|
|
185
|
+
}
|
|
186
|
+
.split-lg .caret-glyph {
|
|
187
|
+
font-size: var(--control-icon-lg);
|
|
188
|
+
}
|
|
189
|
+
/* Coarse pointers: the narrow caret keeps a 24px hit box without changing its drawn width. */
|
|
190
|
+
@media (pointer: coarse) {
|
|
191
|
+
.split-half .split-caret {
|
|
192
|
+
min-width: var(--box-xs);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
172
195
|
/* The hovered or focused segment paints above its neighbour, so its
|
|
173
196
|
accent border and focus ring are not cut by the overlap. */
|
|
174
|
-
.split-main:hover,
|
|
175
197
|
.split-main:focus-within,
|
|
176
|
-
.split-caret:hover,
|
|
177
198
|
.split-caret:focus-within {
|
|
178
199
|
z-index: 1;
|
|
179
200
|
}
|
|
201
|
+
@media (hover: hover) and (pointer: fine) {
|
|
202
|
+
.split-main:hover,
|
|
203
|
+
.split-caret:hover {
|
|
204
|
+
z-index: 1;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
180
207
|
/* On a filled primary the borders match the fill; paint the divider in the
|
|
181
208
|
on-accent foreground instead. */
|
|
182
209
|
.split-primary .split-caret::before {
|
|
@@ -40,6 +40,8 @@ type Own = {
|
|
|
40
40
|
loading?: boolean;
|
|
41
41
|
/** Handler of the primary action. */
|
|
42
42
|
onclick?: (e: MouseEvent) => void;
|
|
43
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
44
|
+
sound?: boolean;
|
|
43
45
|
class?: string;
|
|
44
46
|
style?: string;
|
|
45
47
|
/** Content of the primary action. */
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
28
28
|
import Icon from '../atoms/Icon.svelte';
|
|
29
29
|
import { canonicalTone } from '../../tone';
|
|
30
|
+
import { playSound } from '../../sound';
|
|
30
31
|
|
|
31
32
|
type Own = {
|
|
32
33
|
children: Snippet;
|
|
@@ -40,6 +41,8 @@
|
|
|
40
41
|
/** Fraction of the row width the content must travel to fire. */
|
|
41
42
|
threshold?: number;
|
|
42
43
|
disabled?: boolean;
|
|
44
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
45
|
+
sound?: boolean;
|
|
43
46
|
class?: string;
|
|
44
47
|
style?: string;
|
|
45
48
|
};
|
|
@@ -52,6 +55,7 @@
|
|
|
52
55
|
trailing,
|
|
53
56
|
threshold = 0.35,
|
|
54
57
|
disabled = false,
|
|
58
|
+
sound = true,
|
|
55
59
|
class: klass = '',
|
|
56
60
|
style: styleProp,
|
|
57
61
|
...rest
|
|
@@ -150,7 +154,12 @@
|
|
|
150
154
|
const fire = locked === 'x' && armed ? active : undefined;
|
|
151
155
|
release(el);
|
|
152
156
|
settle();
|
|
153
|
-
fire
|
|
157
|
+
if (fire) run(fire);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function run(action: SwipeAction) {
|
|
161
|
+
playSound('click', sound);
|
|
162
|
+
action.onaction();
|
|
154
163
|
}
|
|
155
164
|
|
|
156
165
|
function onpointercancel(e: PointerEvent) {
|
|
@@ -223,12 +232,12 @@
|
|
|
223
232
|
{#if leadingAction || trailingAction}
|
|
224
233
|
<div class="swiperow-actions" data-part="actions">
|
|
225
234
|
{#if leadingAction}
|
|
226
|
-
<button type="button" class="swiperow-sr" data-part="leading-button" {disabled} onclick={() => leadingAction
|
|
235
|
+
<button type="button" class="swiperow-sr" data-part="leading-button" {disabled} onclick={() => leadingAction && run(leadingAction)}>
|
|
227
236
|
{leadingAction.label}
|
|
228
237
|
</button>
|
|
229
238
|
{/if}
|
|
230
239
|
{#if trailingAction}
|
|
231
|
-
<button type="button" class="swiperow-sr" data-part="trailing-button" {disabled} onclick={() => trailingAction
|
|
240
|
+
<button type="button" class="swiperow-sr" data-part="trailing-button" {disabled} onclick={() => trailingAction && run(trailingAction)}>
|
|
232
241
|
{trailingAction.label}
|
|
233
242
|
</button>
|
|
234
243
|
{/if}
|
|
@@ -276,7 +285,7 @@
|
|
|
276
285
|
background: var(--swiperow-tone);
|
|
277
286
|
color: var(--swiperow-ink);
|
|
278
287
|
font-size: var(--fs-sm);
|
|
279
|
-
font-weight:
|
|
288
|
+
font-weight: var(--fw-semibold);
|
|
280
289
|
visibility: hidden;
|
|
281
290
|
pointer-events: none;
|
|
282
291
|
}
|
|
@@ -291,10 +300,10 @@
|
|
|
291
300
|
visibility: visible;
|
|
292
301
|
}
|
|
293
302
|
.swiperow-pane :global(svg) {
|
|
294
|
-
transition:
|
|
303
|
+
transition: scale var(--dur-spring) var(--ease-spring);
|
|
295
304
|
}
|
|
296
305
|
.swiperow--armed .swiperow-pane :global(svg) {
|
|
297
|
-
|
|
306
|
+
scale: 1.2;
|
|
298
307
|
}
|
|
299
308
|
.swiperow-content {
|
|
300
309
|
position: relative;
|
|
@@ -303,7 +312,7 @@
|
|
|
303
312
|
will-change: transform;
|
|
304
313
|
}
|
|
305
314
|
.swiperow--settling .swiperow-content {
|
|
306
|
-
transition: transform
|
|
315
|
+
transition: transform var(--dur-exit-slow) var(--ease-out);
|
|
307
316
|
}
|
|
308
317
|
.swiperow--dragging .swiperow-content {
|
|
309
318
|
cursor: grabbing;
|
|
@@ -341,13 +350,7 @@
|
|
|
341
350
|
color: var(--swiperow-ink);
|
|
342
351
|
background: var(--swiperow-tone);
|
|
343
352
|
border-radius: var(--r-sm);
|
|
344
|
-
outline:
|
|
345
|
-
outline-offset:
|
|
346
|
-
}
|
|
347
|
-
@media (prefers-reduced-motion: reduce) {
|
|
348
|
-
.swiperow--settling .swiperow-content,
|
|
349
|
-
.swiperow-pane :global(svg) {
|
|
350
|
-
transition: none;
|
|
351
|
-
}
|
|
353
|
+
outline: var(--focus-ring);
|
|
354
|
+
outline-offset: var(--sp-px);
|
|
352
355
|
}
|
|
353
356
|
</style>
|
|
@@ -32,6 +32,8 @@ type Own = {
|
|
|
32
32
|
/** Fraction of the row width the content must travel to fire. */
|
|
33
33
|
threshold?: number;
|
|
34
34
|
disabled?: boolean;
|
|
35
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
36
|
+
sound?: boolean;
|
|
35
37
|
class?: string;
|
|
36
38
|
style?: string;
|
|
37
39
|
};
|
|
@@ -31,10 +31,12 @@
|
|
|
31
31
|
// Without `panel` the tablist stands alone as a document strip: `closable`
|
|
32
32
|
// tabs gain a close control, Delete and middle-click close the focused tab,
|
|
33
33
|
// `leading` decorates each tab and `actions` trails the strip.
|
|
34
|
+
import { tick } from 'svelte';
|
|
34
35
|
import type { Snippet } from 'svelte';
|
|
35
36
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
36
37
|
import Icon from '../atoms/Icon.svelte';
|
|
37
38
|
import IconButton from './IconButton.svelte';
|
|
39
|
+
import { playSound } from '../../sound';
|
|
38
40
|
|
|
39
41
|
let {
|
|
40
42
|
tabs,
|
|
@@ -50,6 +52,7 @@
|
|
|
50
52
|
style: styleProp = '',
|
|
51
53
|
panelClass = '',
|
|
52
54
|
panelPadding = 'md',
|
|
55
|
+
sound = true,
|
|
53
56
|
...rest
|
|
54
57
|
}: Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own = $props();
|
|
55
58
|
|
|
@@ -73,6 +76,8 @@
|
|
|
73
76
|
style?: string;
|
|
74
77
|
panelClass?: string;
|
|
75
78
|
panelPadding?: 'none' | 'sm' | 'md';
|
|
79
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
80
|
+
sound?: boolean;
|
|
76
81
|
};
|
|
77
82
|
|
|
78
83
|
// Default to the first selectable tab when no value is supplied.
|
|
@@ -97,8 +102,59 @@
|
|
|
97
102
|
return t.closable ?? closable;
|
|
98
103
|
}
|
|
99
104
|
|
|
105
|
+
// One indicator slides under the selected tab on translate/scale (never
|
|
106
|
+
// width/left). It snaps on mount and on reflow, and only animates on a
|
|
107
|
+
// selection change; until it is placed the selected tab draws its own line.
|
|
108
|
+
let indicator = $state<HTMLSpanElement | null>(null);
|
|
109
|
+
let placed = $state(false);
|
|
110
|
+
let indLeft = 0;
|
|
111
|
+
let indWidth = 0;
|
|
112
|
+
function placeIndicator(animate: boolean) {
|
|
113
|
+
const el = listEl?.querySelector<HTMLElement>('.tab.selected');
|
|
114
|
+
const ind = indicator;
|
|
115
|
+
if (!el || !ind) {
|
|
116
|
+
placed = false;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const left = el.offsetLeft;
|
|
120
|
+
const width = el.offsetWidth;
|
|
121
|
+
const prevLeft = indLeft;
|
|
122
|
+
const prevWidth = indWidth;
|
|
123
|
+
indLeft = left;
|
|
124
|
+
indWidth = width;
|
|
125
|
+
ind.style.width = `${width}px`;
|
|
126
|
+
if (!animate || !placed || width === 0) {
|
|
127
|
+
ind.style.transition = 'none';
|
|
128
|
+
ind.style.translate = `${left}px 0`;
|
|
129
|
+
ind.style.scale = '1 1';
|
|
130
|
+
ind.getBoundingClientRect();
|
|
131
|
+
ind.style.transition = '';
|
|
132
|
+
placed = true;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
ind.style.transition = 'none';
|
|
136
|
+
ind.style.translate = `${prevLeft}px 0`;
|
|
137
|
+
ind.style.scale = `${prevWidth / width} 1`;
|
|
138
|
+
ind.getBoundingClientRect();
|
|
139
|
+
ind.style.transition = '';
|
|
140
|
+
ind.style.translate = `${left}px 0`;
|
|
141
|
+
ind.style.scale = '1 1';
|
|
142
|
+
}
|
|
143
|
+
$effect(() => {
|
|
144
|
+
value;
|
|
145
|
+
tabs;
|
|
146
|
+
tick().then(() => placeIndicator(true));
|
|
147
|
+
});
|
|
148
|
+
$effect(() => {
|
|
149
|
+
if (!listEl || typeof ResizeObserver === 'undefined') return;
|
|
150
|
+
const ro = new ResizeObserver(() => placeIndicator(false));
|
|
151
|
+
ro.observe(listEl);
|
|
152
|
+
return () => ro.disconnect();
|
|
153
|
+
});
|
|
154
|
+
|
|
100
155
|
function select(id: string, focus = false) {
|
|
101
156
|
if (tabs.find((t) => t.id === id)?.disabled) return;
|
|
157
|
+
if (value !== id) playSound('click', sound);
|
|
102
158
|
value = id;
|
|
103
159
|
queueMicrotask(() => {
|
|
104
160
|
const el = listEl?.ownerDocument.getElementById(tabId(id));
|
|
@@ -163,7 +219,8 @@
|
|
|
163
219
|
|
|
164
220
|
<div {...rest} class="tabs {klass}" style={styleProp} data-tsu="Tabs">
|
|
165
221
|
<div class="strip">
|
|
166
|
-
<div bind:this={listEl} role="tablist" aria-label={label} tabindex="-1" class="tablist" {onkeydown}>
|
|
222
|
+
<div bind:this={listEl} role="tablist" aria-label={label} tabindex="-1" class="tablist" class:placed {onkeydown}>
|
|
223
|
+
<span bind:this={indicator} class="indicator" aria-hidden="true"></span>
|
|
167
224
|
{#each tabs as t (t.id)}
|
|
168
225
|
{#if canClose(t)}
|
|
169
226
|
<!-- A close control cannot nest inside a <button>, so a closable tab is a div. -->
|
|
@@ -235,6 +292,7 @@
|
|
|
235
292
|
align-items: stretch;
|
|
236
293
|
}
|
|
237
294
|
.tablist {
|
|
295
|
+
position: relative;
|
|
238
296
|
display: flex;
|
|
239
297
|
flex: 1 1 auto;
|
|
240
298
|
min-width: 0;
|
|
@@ -244,42 +302,66 @@
|
|
|
244
302
|
overflow-y: hidden;
|
|
245
303
|
scrollbar-width: thin;
|
|
246
304
|
}
|
|
305
|
+
.indicator {
|
|
306
|
+
position: absolute;
|
|
307
|
+
left: 0;
|
|
308
|
+
bottom: 0;
|
|
309
|
+
width: 0;
|
|
310
|
+
height: 2px;
|
|
311
|
+
background: var(--accent);
|
|
312
|
+
transform-origin: left center;
|
|
313
|
+
pointer-events: none;
|
|
314
|
+
transition:
|
|
315
|
+
translate var(--dur-spring) var(--ease-spring),
|
|
316
|
+
scale var(--dur-spring) var(--ease-spring);
|
|
317
|
+
}
|
|
247
318
|
.tab {
|
|
248
319
|
display: inline-flex;
|
|
249
320
|
flex: 0 0 auto;
|
|
250
321
|
white-space: nowrap;
|
|
251
322
|
align-items: center;
|
|
252
323
|
gap: var(--sp-2);
|
|
253
|
-
|
|
324
|
+
min-height: var(--control-md);
|
|
325
|
+
padding: 0 var(--control-px-md);
|
|
254
326
|
border: none;
|
|
255
327
|
background: none;
|
|
256
328
|
color: var(--text-muted);
|
|
257
|
-
font-size: var(--fs-
|
|
329
|
+
font-size: var(--control-fs-md);
|
|
258
330
|
font-weight: var(--fw-medium);
|
|
331
|
+
--icon-size: var(--control-icon-md);
|
|
259
332
|
border-bottom: 2px solid transparent;
|
|
260
333
|
margin-bottom: -1px;
|
|
261
334
|
max-width: var(--tab-max-width, none);
|
|
262
335
|
transition:
|
|
263
|
-
color
|
|
264
|
-
border-color
|
|
336
|
+
color var(--dur-fast) var(--ease),
|
|
337
|
+
border-color var(--dur-fast) var(--ease);
|
|
265
338
|
}
|
|
266
339
|
.tab.closable {
|
|
267
340
|
cursor: pointer;
|
|
268
341
|
padding-inline-end: var(--sp-2);
|
|
269
342
|
user-select: none;
|
|
270
343
|
}
|
|
271
|
-
|
|
272
|
-
|
|
344
|
+
@media (hover: hover) and (pointer: fine) {
|
|
345
|
+
.tab:hover:not(:disabled, .disabled) {
|
|
346
|
+
color: var(--text);
|
|
347
|
+
}
|
|
273
348
|
}
|
|
274
349
|
.tab:disabled,
|
|
275
350
|
.tab.disabled {
|
|
276
|
-
opacity:
|
|
351
|
+
opacity: var(--disabled-opacity);
|
|
277
352
|
cursor: not-allowed;
|
|
278
353
|
}
|
|
279
354
|
.tab.selected {
|
|
280
355
|
color: var(--accent);
|
|
356
|
+
}
|
|
357
|
+
.tablist:not(.placed) .tab.selected {
|
|
281
358
|
border-bottom-color: var(--accent);
|
|
282
359
|
}
|
|
360
|
+
/* The list clips its overflow, so the ring sits inside the tab. */
|
|
361
|
+
.tab:focus-visible {
|
|
362
|
+
outline: var(--focus-ring);
|
|
363
|
+
outline-offset: var(--focus-ring-offset-inset);
|
|
364
|
+
}
|
|
283
365
|
.label {
|
|
284
366
|
min-width: 0;
|
|
285
367
|
overflow: hidden;
|
|
@@ -303,8 +385,8 @@
|
|
|
303
385
|
padding-top: 0;
|
|
304
386
|
}
|
|
305
387
|
.tabpanel:focus-visible {
|
|
306
|
-
outline:
|
|
307
|
-
outline-offset:
|
|
388
|
+
outline: var(--focus-ring);
|
|
389
|
+
outline-offset: var(--focus-ring-offset);
|
|
308
390
|
border-radius: var(--r-sm);
|
|
309
391
|
}
|
|
310
392
|
</style>
|
|
@@ -39,6 +39,8 @@ type Own = {
|
|
|
39
39
|
style?: string;
|
|
40
40
|
panelClass?: string;
|
|
41
41
|
panelPadding?: 'none' | 'sm' | 'md';
|
|
42
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
43
|
+
sound?: boolean;
|
|
42
44
|
};
|
|
43
45
|
type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own;
|
|
44
46
|
declare const Tabs: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
@@ -157,11 +157,11 @@
|
|
|
157
157
|
position: absolute;
|
|
158
158
|
right: -0.35rem;
|
|
159
159
|
bottom: -0.35rem;
|
|
160
|
-
border-radius:
|
|
160
|
+
border-radius: var(--r-pill);
|
|
161
161
|
background: var(--bg);
|
|
162
162
|
color: var(--text-muted);
|
|
163
163
|
font-size: var(--fs-xs);
|
|
164
|
-
line-height:
|
|
164
|
+
line-height: var(--lh-none);
|
|
165
165
|
}
|
|
166
166
|
.swatch {
|
|
167
167
|
display: grid;
|
|
@@ -215,19 +215,27 @@
|
|
|
215
215
|
gap: var(--sp-1);
|
|
216
216
|
}
|
|
217
217
|
.cell {
|
|
218
|
+
--cell-scale-dur: var(--dur-fast);
|
|
218
219
|
display: inline-flex;
|
|
219
|
-
padding:
|
|
220
|
+
padding: var(--sp-0-5);
|
|
220
221
|
border: 2px solid transparent;
|
|
221
222
|
border-radius: var(--r-md);
|
|
222
223
|
background: none;
|
|
223
224
|
cursor: pointer;
|
|
225
|
+
transition:
|
|
226
|
+
background-color var(--dur-fast) ease,
|
|
227
|
+
border-color var(--dur-fast) ease,
|
|
228
|
+
scale var(--cell-scale-dur) var(--ease-out);
|
|
224
229
|
}
|
|
230
|
+
/* Concentric with the cell: outer radius minus border and padding. */
|
|
225
231
|
.cell .swatch {
|
|
226
232
|
width: 1.6rem;
|
|
227
233
|
height: 1.6rem;
|
|
234
|
+
border-radius: calc(var(--r-md) - var(--sp-1));
|
|
228
235
|
}
|
|
229
|
-
.cell:
|
|
230
|
-
|
|
236
|
+
.cell:active {
|
|
237
|
+
--cell-scale-dur: var(--dur-press);
|
|
238
|
+
scale: var(--press-scale-sm);
|
|
231
239
|
}
|
|
232
240
|
.cell.current {
|
|
233
241
|
border-color: var(--accent);
|
|
@@ -238,8 +246,8 @@
|
|
|
238
246
|
}
|
|
239
247
|
.cell:focus-visible,
|
|
240
248
|
.auto:focus-visible {
|
|
241
|
-
outline:
|
|
242
|
-
outline-offset:
|
|
249
|
+
outline: var(--focus-ring);
|
|
250
|
+
outline-offset: var(--focus-ring-offset-inset);
|
|
243
251
|
}
|
|
244
252
|
.auto {
|
|
245
253
|
display: flex;
|
|
@@ -255,16 +263,23 @@
|
|
|
255
263
|
color: inherit;
|
|
256
264
|
text-align: left;
|
|
257
265
|
cursor: pointer;
|
|
266
|
+
transition:
|
|
267
|
+
background-color var(--dur-fast) ease,
|
|
268
|
+
border-color var(--dur-fast) ease;
|
|
258
269
|
}
|
|
259
|
-
|
|
260
|
-
|
|
270
|
+
@media (hover: hover) and (pointer: fine) {
|
|
271
|
+
.cell:hover,
|
|
272
|
+
.auto:hover {
|
|
273
|
+
background: var(--bg-elevated-2);
|
|
274
|
+
transition-duration: var(--dur-instant);
|
|
275
|
+
}
|
|
261
276
|
}
|
|
262
277
|
.auto.current {
|
|
263
278
|
border-color: var(--accent);
|
|
264
279
|
}
|
|
265
280
|
.auto-glyph {
|
|
266
281
|
font-size: var(--fs-md);
|
|
267
|
-
line-height:
|
|
282
|
+
line-height: var(--lh-none);
|
|
268
283
|
}
|
|
269
284
|
.auto-text {
|
|
270
285
|
display: flex;
|
|
@@ -199,10 +199,19 @@
|
|
|
199
199
|
text-underline-offset: 2px;
|
|
200
200
|
text-decoration-color: var(--border-strong);
|
|
201
201
|
}
|
|
202
|
-
.ts-trigger:hover,
|
|
203
202
|
.ts-trigger:focus-visible {
|
|
204
203
|
text-decoration-color: currentColor;
|
|
205
204
|
}
|
|
205
|
+
@media (hover: hover) and (pointer: fine) {
|
|
206
|
+
.ts-trigger:hover {
|
|
207
|
+
text-decoration-color: currentColor;
|
|
208
|
+
}
|
|
209
|
+
.ts-mode:hover {
|
|
210
|
+
background: var(--bg-elevated-2);
|
|
211
|
+
color: var(--text);
|
|
212
|
+
transition-duration: var(--dur-instant);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
206
215
|
|
|
207
216
|
.ts-panel {
|
|
208
217
|
display: flex;
|
|
@@ -224,12 +233,8 @@
|
|
|
224
233
|
font-size: var(--fs-xs);
|
|
225
234
|
cursor: pointer;
|
|
226
235
|
transition:
|
|
227
|
-
background
|
|
228
|
-
color
|
|
229
|
-
}
|
|
230
|
-
.ts-mode:hover {
|
|
231
|
-
background: var(--bg-elevated-2);
|
|
232
|
-
color: var(--text);
|
|
236
|
+
background var(--dur-fast) var(--ease),
|
|
237
|
+
color var(--dur-fast) var(--ease);
|
|
233
238
|
}
|
|
234
239
|
.ts-mode.active {
|
|
235
240
|
background: var(--bg-elevated-2);
|
|
@@ -7,11 +7,21 @@
|
|
|
7
7
|
// everything outside it is inert, top layer included, so the stack moves
|
|
8
8
|
// into the topmost modal dialog and back to its host when it closes.
|
|
9
9
|
import { toasts } from '../../stores/toast.svelte';
|
|
10
|
+
import { playSound } from '../../sound';
|
|
10
11
|
import Button from '../atoms/Button.svelte';
|
|
11
12
|
import Card from '../atoms/Card.svelte';
|
|
12
13
|
import Icon from '../atoms/Icon.svelte';
|
|
13
14
|
|
|
14
|
-
let {
|
|
15
|
+
let {
|
|
16
|
+
class: klass = '',
|
|
17
|
+
style: styleProp = '',
|
|
18
|
+
sound = true
|
|
19
|
+
}: {
|
|
20
|
+
class?: string;
|
|
21
|
+
style?: string;
|
|
22
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
23
|
+
sound?: boolean;
|
|
24
|
+
} = $props();
|
|
15
25
|
|
|
16
26
|
let host: HTMLDivElement | undefined = $state();
|
|
17
27
|
let el: HTMLDivElement | undefined = $state();
|
|
@@ -69,12 +79,45 @@
|
|
|
69
79
|
$effect(() => () => {
|
|
70
80
|
if (el && host && el.parentNode !== host) host.appendChild(el);
|
|
71
81
|
});
|
|
82
|
+
|
|
83
|
+
// Feedback tones sound once, when the toast appears; the queue present at
|
|
84
|
+
// mount is a sync, not an arrival.
|
|
85
|
+
let seen: Set<number> | null = null;
|
|
86
|
+
$effect(() => {
|
|
87
|
+
const items = toasts.items;
|
|
88
|
+
if (!seen) {
|
|
89
|
+
seen = new Set(items.map((t) => t.id));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
for (const t of items) {
|
|
93
|
+
if (seen.has(t.id)) continue;
|
|
94
|
+
seen.add(t.id);
|
|
95
|
+
if (t.tone === 'ok') playSound('success', sound);
|
|
96
|
+
else if (t.tone === 'error') playSound('error', sound);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Hovering a toast holds its timer; only a real pointer counts.
|
|
101
|
+
function hold(e: PointerEvent, id: number) {
|
|
102
|
+
if (e.pointerType === 'mouse') toasts.pause(id);
|
|
103
|
+
}
|
|
104
|
+
function release(e: PointerEvent, id: number) {
|
|
105
|
+
if (e.pointerType === 'mouse') toasts.resume(id);
|
|
106
|
+
}
|
|
72
107
|
</script>
|
|
73
108
|
|
|
74
109
|
{#snippet toast(t: (typeof toasts.items)[number])}
|
|
75
110
|
{@const tone = t.tone === 'ok' ? 'ok' : t.tone === 'error' ? 'danger' : t.tone === 'info' ? 'info' : undefined}
|
|
76
111
|
{#if t.action}
|
|
77
|
-
<Card
|
|
112
|
+
<Card
|
|
113
|
+
as="div"
|
|
114
|
+
surface="raised"
|
|
115
|
+
class="toast"
|
|
116
|
+
{tone}
|
|
117
|
+
data-leaving={t.leaving || undefined}
|
|
118
|
+
onpointerenter={(e: PointerEvent) => hold(e, t.id)}
|
|
119
|
+
onpointerleave={(e: PointerEvent) => release(e, t.id)}
|
|
120
|
+
>
|
|
78
121
|
<button
|
|
79
122
|
type="button"
|
|
80
123
|
class="toast-text"
|
|
@@ -98,6 +141,9 @@
|
|
|
98
141
|
{tone}
|
|
99
142
|
type="button"
|
|
100
143
|
aria-label="Dismiss: {t.message}"
|
|
144
|
+
data-leaving={t.leaving || undefined}
|
|
145
|
+
onpointerenter={(e: PointerEvent) => hold(e, t.id)}
|
|
146
|
+
onpointerleave={(e: PointerEvent) => release(e, t.id)}
|
|
101
147
|
onclick={() => toasts.dismiss(t.id)}
|
|
102
148
|
>
|
|
103
149
|
<span aria-hidden="true" class="toast-icon">
|
|
@@ -160,7 +206,23 @@
|
|
|
160
206
|
width: 100%;
|
|
161
207
|
text-align: left;
|
|
162
208
|
font-size: var(--fs-sm);
|
|
163
|
-
|
|
209
|
+
transition:
|
|
210
|
+
opacity var(--dur-sheet) var(--ease-out),
|
|
211
|
+
translate var(--dur-sheet) var(--ease-out),
|
|
212
|
+
scale var(--dur-sheet) var(--ease-out);
|
|
213
|
+
}
|
|
214
|
+
@starting-style {
|
|
215
|
+
.toaster :global(.toast) {
|
|
216
|
+
opacity: 0;
|
|
217
|
+
translate: 0 var(--shift-md);
|
|
218
|
+
scale: 0.98;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
.toaster :global(.toast[data-leaving]) {
|
|
222
|
+
opacity: 0;
|
|
223
|
+
scale: 0.96;
|
|
224
|
+
pointer-events: none;
|
|
225
|
+
transition-duration: var(--dur-exit-slow);
|
|
164
226
|
}
|
|
165
227
|
.toaster :global(.toast.card-ok) {
|
|
166
228
|
color: var(--ok);
|
|
@@ -188,15 +250,4 @@
|
|
|
188
250
|
.msg {
|
|
189
251
|
flex: 1;
|
|
190
252
|
}
|
|
191
|
-
@keyframes toast-in {
|
|
192
|
-
from {
|
|
193
|
-
transform: translateY(8px);
|
|
194
|
-
opacity: 0;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
@media (prefers-reduced-motion: reduce) {
|
|
198
|
-
.toaster :global(.toast) {
|
|
199
|
-
animation: none;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
253
|
</style>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
type $$ComponentProps = {
|
|
2
2
|
class?: string;
|
|
3
3
|
style?: string;
|
|
4
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
5
|
+
sound?: boolean;
|
|
4
6
|
};
|
|
5
7
|
declare const Toaster: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
8
|
type Toaster = ReturnType<typeof Toaster>;
|