@dorsk/tsumikit 0.74.0 → 0.76.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 +46 -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/RichSelect.svelte +583 -0
- package/dist/components/molecules/RichSelect.svelte.d.ts +68 -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/molecules/rich-select-keyboard.d.ts +82 -0
- package/dist/components/molecules/rich-select-keyboard.js +114 -0
- package/dist/components/organisms/ConversationFeed.svelte +24 -1
- package/dist/components/organisms/DataTable.svelte +37 -14
- package/dist/floating.js +26 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -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/dist/styles/reset.css +3 -3
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { Snippet } from 'svelte';
|
|
8
8
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
9
9
|
import { describeMediaTime, formatMediaTime, isLiveDuration } from '../../media-time';
|
|
10
|
+
import { playSound } from '../../sound';
|
|
10
11
|
|
|
11
12
|
type Own = {
|
|
12
13
|
/** Position in seconds. Updated only on commit, never while dragging. */
|
|
@@ -22,6 +23,8 @@
|
|
|
22
23
|
disabled?: boolean;
|
|
23
24
|
size?: 'sm' | 'md';
|
|
24
25
|
label?: string;
|
|
26
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
27
|
+
sound?: boolean;
|
|
25
28
|
formatTime?: (seconds: number, ref?: number) => string;
|
|
26
29
|
/** Fired on every pointer move while scrubbing with the previewed time. */
|
|
27
30
|
oninput?: (seconds: number) => void;
|
|
@@ -44,6 +47,7 @@
|
|
|
44
47
|
disabled = false,
|
|
45
48
|
size = 'md',
|
|
46
49
|
label = 'Seek',
|
|
50
|
+
sound = true,
|
|
47
51
|
formatTime = formatMediaTime,
|
|
48
52
|
oninput,
|
|
49
53
|
onchange,
|
|
@@ -77,6 +81,7 @@
|
|
|
77
81
|
.filter(([a, b]) => b > a)
|
|
78
82
|
);
|
|
79
83
|
const valueText = $derived(describeMediaTime(shown, live ? null : duration, formatTime));
|
|
84
|
+
const discrete = $derived(duration > 0 && duration / step <= 20);
|
|
80
85
|
|
|
81
86
|
function fracAt(el: HTMLElement, clientX: number) {
|
|
82
87
|
const r = el.getBoundingClientRect();
|
|
@@ -125,6 +130,7 @@
|
|
|
125
130
|
const el = e.currentTarget as HTMLElement;
|
|
126
131
|
if (pointerId === e.pointerId) {
|
|
127
132
|
preview = fracAt(el, e.clientX) * duration;
|
|
133
|
+
if (discrete) playSound('tick', sound);
|
|
128
134
|
oninput?.(preview);
|
|
129
135
|
return;
|
|
130
136
|
}
|
|
@@ -182,6 +188,7 @@
|
|
|
182
188
|
}
|
|
183
189
|
if (next === null) return;
|
|
184
190
|
e.preventDefault();
|
|
191
|
+
playSound('tick', sound);
|
|
185
192
|
commit(next);
|
|
186
193
|
}
|
|
187
194
|
</script>
|
|
@@ -233,8 +240,9 @@
|
|
|
233
240
|
<style>
|
|
234
241
|
.seek {
|
|
235
242
|
--seek-track: 3px;
|
|
236
|
-
--seek-track-
|
|
243
|
+
--seek-track-grow: 1.6;
|
|
237
244
|
--seek-thumb: 12px;
|
|
245
|
+
--seek-halo: 0 0 0 4px color-mix(in srgb, var(--seek-accent) 20%, transparent);
|
|
238
246
|
--seek-slab: 1.25rem;
|
|
239
247
|
--seek-accent: var(--slider-accent, var(--accent));
|
|
240
248
|
position: relative;
|
|
@@ -249,7 +257,6 @@
|
|
|
249
257
|
}
|
|
250
258
|
.seek--md {
|
|
251
259
|
--seek-track: 4px;
|
|
252
|
-
--seek-track-active: 6px;
|
|
253
260
|
--seek-thumb: 14px;
|
|
254
261
|
--seek-slab: 1.5rem;
|
|
255
262
|
}
|
|
@@ -259,8 +266,8 @@
|
|
|
259
266
|
}
|
|
260
267
|
}
|
|
261
268
|
.seek--inert {
|
|
262
|
-
cursor:
|
|
263
|
-
opacity:
|
|
269
|
+
cursor: not-allowed;
|
|
270
|
+
opacity: var(--disabled-opacity);
|
|
264
271
|
}
|
|
265
272
|
.seek-track {
|
|
266
273
|
position: absolute;
|
|
@@ -271,19 +278,12 @@
|
|
|
271
278
|
border-radius: var(--r-pill);
|
|
272
279
|
background: var(--bg-elevated-2);
|
|
273
280
|
overflow: hidden;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
margin-top 120ms var(--ease);
|
|
281
|
+
transform-origin: center;
|
|
282
|
+
transition: scale var(--dur-fast) var(--ease);
|
|
277
283
|
}
|
|
278
|
-
.seek:hover .seek-track,
|
|
279
284
|
.seek:focus-visible .seek-track,
|
|
280
285
|
.seek--dragging .seek-track {
|
|
281
|
-
|
|
282
|
-
margin-top: calc(var(--seek-track-active) / -2);
|
|
283
|
-
}
|
|
284
|
-
.seek--inert:hover .seek-track {
|
|
285
|
-
height: var(--seek-track);
|
|
286
|
-
margin-top: calc(var(--seek-track) / -2);
|
|
286
|
+
scale: 1 var(--seek-track-grow);
|
|
287
287
|
}
|
|
288
288
|
.seek-buffered,
|
|
289
289
|
.seek-played {
|
|
@@ -319,28 +319,45 @@
|
|
|
319
319
|
top: 0;
|
|
320
320
|
width: var(--seek-thumb);
|
|
321
321
|
height: var(--seek-thumb);
|
|
322
|
-
border-radius:
|
|
322
|
+
border-radius: var(--r-pill);
|
|
323
323
|
background: var(--seek-accent);
|
|
324
324
|
border: 2px solid var(--bg);
|
|
325
325
|
box-shadow: var(--shadow-sm);
|
|
326
|
-
transform: translate(-50%, -50%)
|
|
326
|
+
transform: translate(-50%, -50%);
|
|
327
|
+
scale: 0.6;
|
|
327
328
|
opacity: 0;
|
|
328
329
|
transition:
|
|
329
|
-
opacity
|
|
330
|
-
|
|
330
|
+
opacity var(--dur-fast) var(--ease),
|
|
331
|
+
scale var(--dur-press) var(--ease-out),
|
|
332
|
+
box-shadow var(--dur-fast) var(--ease);
|
|
331
333
|
}
|
|
332
|
-
.seek:hover .seek-thumb,
|
|
333
334
|
.seek:focus-visible .seek-thumb,
|
|
334
335
|
.seek--dragging .seek-thumb {
|
|
335
336
|
opacity: 1;
|
|
336
|
-
|
|
337
|
+
scale: 1;
|
|
338
|
+
}
|
|
339
|
+
.seek--dragging .seek-thumb {
|
|
340
|
+
scale: 1.15;
|
|
341
|
+
}
|
|
342
|
+
@media (hover: hover) and (pointer: fine) {
|
|
343
|
+
.seek:not(.seek--inert):hover .seek-track {
|
|
344
|
+
scale: 1 var(--seek-track-grow);
|
|
345
|
+
}
|
|
346
|
+
.seek:not(.seek--inert):hover .seek-thumb {
|
|
347
|
+
opacity: 1;
|
|
348
|
+
scale: 1;
|
|
349
|
+
box-shadow: var(--shadow-sm), var(--seek-halo);
|
|
350
|
+
}
|
|
351
|
+
.seek--dragging .seek-thumb {
|
|
352
|
+
scale: 1.15;
|
|
353
|
+
}
|
|
337
354
|
}
|
|
338
355
|
.seek--inert .seek-thumb {
|
|
339
356
|
opacity: 0;
|
|
340
357
|
}
|
|
341
358
|
.seek:focus-visible .seek-thumb {
|
|
342
359
|
outline: var(--focus-ring);
|
|
343
|
-
outline-offset:
|
|
360
|
+
outline-offset: var(--focus-ring-offset);
|
|
344
361
|
}
|
|
345
362
|
.seek-tip {
|
|
346
363
|
position: absolute;
|
|
@@ -361,10 +378,4 @@
|
|
|
361
378
|
font-variant-numeric: tabular-nums;
|
|
362
379
|
white-space: nowrap;
|
|
363
380
|
}
|
|
364
|
-
@media (prefers-reduced-motion: reduce) {
|
|
365
|
-
.seek-track,
|
|
366
|
-
.seek-thumb {
|
|
367
|
-
transition: none;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
381
|
</style>
|
|
@@ -19,6 +19,8 @@ type Own = {
|
|
|
19
19
|
disabled?: boolean;
|
|
20
20
|
size?: 'sm' | 'md';
|
|
21
21
|
label?: string;
|
|
22
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
23
|
+
sound?: boolean;
|
|
22
24
|
formatTime?: (seconds: number, ref?: number) => string;
|
|
23
25
|
/** Fired on every pointer move while scrubbing with the previewed time. */
|
|
24
26
|
oninput?: (seconds: number) => void;
|
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
// Implemented with `radiogroup`/`radio` semantics + roving tabindex so the
|
|
24
24
|
// browser-free keyboard model matches Tabs: ←/→ and Home/End move selection,
|
|
25
25
|
// Space/Enter (or click) activate. `value` is bindable.
|
|
26
|
+
import { tick } from 'svelte';
|
|
26
27
|
import type { Snippet } from 'svelte';
|
|
27
28
|
import type { Attachment } from 'svelte/attachments';
|
|
28
29
|
import Icon from '../atoms/Icon.svelte';
|
|
30
|
+
import { playSound } from '../../sound';
|
|
29
31
|
import { formatSegmentCount } from './segmented-control-count.js';
|
|
30
32
|
import { nextEnabledSegment } from './segmented-control-keyboard.js';
|
|
31
33
|
|
|
@@ -49,6 +51,7 @@
|
|
|
49
51
|
collapseLabels = 'never',
|
|
50
52
|
scroll = false,
|
|
51
53
|
block = false,
|
|
54
|
+
sound = true,
|
|
52
55
|
class: klass = '',
|
|
53
56
|
// Custom rendering per option (overrides the built-in label/count/icon).
|
|
54
57
|
option: optionSnippet
|
|
@@ -75,6 +78,8 @@
|
|
|
75
78
|
scroll?: boolean;
|
|
76
79
|
/** Fill the parent width, sharing it equally between segments. */
|
|
77
80
|
block?: boolean;
|
|
81
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
82
|
+
sound?: boolean;
|
|
78
83
|
class?: string;
|
|
79
84
|
option?: Snippet<[SegmentOption, boolean]>;
|
|
80
85
|
} = $props();
|
|
@@ -116,8 +121,55 @@
|
|
|
116
121
|
};
|
|
117
122
|
};
|
|
118
123
|
|
|
124
|
+
// A thumb slides under the selected segment on translate/scale (never
|
|
125
|
+
// width/left): the size snaps, the old-to-new width ratio is inverted and
|
|
126
|
+
// released (FLIP). It snaps on mount and reflow; until placed the selected
|
|
127
|
+
// segment paints its own fill.
|
|
128
|
+
let thumb = $state<HTMLSpanElement | null>(null);
|
|
129
|
+
let placed = $state(false);
|
|
130
|
+
let rect = { x: 0, y: 0, w: 0, h: 0 };
|
|
131
|
+
function placeThumb(animate: boolean) {
|
|
132
|
+
const el = listEl?.querySelector<HTMLElement>('.seg-item.selected');
|
|
133
|
+
const th = thumb;
|
|
134
|
+
if (!el || !th) {
|
|
135
|
+
placed = false;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const prev = rect;
|
|
139
|
+
rect = { x: el.offsetLeft, y: el.offsetTop, w: el.offsetWidth, h: el.offsetHeight };
|
|
140
|
+
th.style.width = `${rect.w}px`;
|
|
141
|
+
th.style.height = `${rect.h}px`;
|
|
142
|
+
th.style.transition = 'none';
|
|
143
|
+
if (!animate || !placed || rect.w === 0) {
|
|
144
|
+
th.style.translate = `${rect.x}px ${rect.y}px`;
|
|
145
|
+
th.style.scale = '1 1';
|
|
146
|
+
th.getBoundingClientRect();
|
|
147
|
+
th.style.transition = '';
|
|
148
|
+
placed = true;
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
th.style.translate = `${prev.x}px ${prev.y}px`;
|
|
152
|
+
th.style.scale = `${prev.w / rect.w} ${prev.h / rect.h}`;
|
|
153
|
+
th.getBoundingClientRect();
|
|
154
|
+
th.style.transition = '';
|
|
155
|
+
th.style.translate = `${rect.x}px ${rect.y}px`;
|
|
156
|
+
th.style.scale = '1 1';
|
|
157
|
+
}
|
|
158
|
+
$effect(() => {
|
|
159
|
+
value;
|
|
160
|
+
options;
|
|
161
|
+
tick().then(() => placeThumb(true));
|
|
162
|
+
});
|
|
163
|
+
$effect(() => {
|
|
164
|
+
if (!listEl || typeof ResizeObserver === 'undefined') return;
|
|
165
|
+
const ro = new ResizeObserver(() => placeThumb(false));
|
|
166
|
+
ro.observe(listEl);
|
|
167
|
+
return () => ro.disconnect();
|
|
168
|
+
});
|
|
169
|
+
|
|
119
170
|
function select(val: string, focus = false) {
|
|
120
171
|
if (options.find((o) => o.value === val)?.disabled) return;
|
|
172
|
+
if (value !== val) playSound('click', sound);
|
|
121
173
|
value = val;
|
|
122
174
|
onchange?.(val);
|
|
123
175
|
if (focus) {
|
|
@@ -149,10 +201,12 @@
|
|
|
149
201
|
class:seg-scroll={scroll}
|
|
150
202
|
class:seg-block={block}
|
|
151
203
|
class:seg-overflow={overflowing}
|
|
204
|
+
class:placed
|
|
152
205
|
data-tsu="SegmentedControl"
|
|
153
206
|
{onkeydown}
|
|
154
207
|
{@attach watchOverflow}
|
|
155
208
|
>
|
|
209
|
+
<span bind:this={thumb} class="seg-thumb" aria-hidden="true"></span>
|
|
156
210
|
{#each options as o (o.value)}
|
|
157
211
|
{@const selected = value === o.value}
|
|
158
212
|
<button
|
|
@@ -180,11 +234,19 @@
|
|
|
180
234
|
</div>
|
|
181
235
|
|
|
182
236
|
<style>
|
|
237
|
+
/* Every size pins the track height (sm 32 / md 40 / control 44 / box 40);
|
|
238
|
+
items fill the inside, which is the height minus border and padding. */
|
|
183
239
|
.seg {
|
|
240
|
+
--seg-h: var(--control-md);
|
|
241
|
+
--seg-inset: calc(var(--sp-0-5) + 1px);
|
|
242
|
+
--seg-item-h: calc(var(--seg-h) - 2 * var(--seg-inset));
|
|
243
|
+
position: relative;
|
|
184
244
|
display: inline-flex;
|
|
185
245
|
align-items: center;
|
|
186
246
|
gap: var(--sp-1);
|
|
187
|
-
|
|
247
|
+
height: var(--seg-h);
|
|
248
|
+
min-height: var(--seg-h);
|
|
249
|
+
padding: var(--sp-0-5);
|
|
188
250
|
background: var(--bg-elevated-2);
|
|
189
251
|
border: 1px solid var(--border);
|
|
190
252
|
border-radius: var(--r-pill);
|
|
@@ -194,8 +256,19 @@
|
|
|
194
256
|
.seg-icon {
|
|
195
257
|
border-radius: var(--r-md);
|
|
196
258
|
}
|
|
259
|
+
.seg-sm {
|
|
260
|
+
--seg-h: var(--control-sm);
|
|
261
|
+
}
|
|
262
|
+
.seg-control {
|
|
263
|
+
--seg-h: var(--control-height);
|
|
264
|
+
height: var(--control-height);
|
|
265
|
+
}
|
|
266
|
+
.seg-box {
|
|
267
|
+
--seg-h: var(--control-height-default);
|
|
268
|
+
}
|
|
197
269
|
.seg-wrap {
|
|
198
270
|
flex-wrap: wrap;
|
|
271
|
+
height: auto;
|
|
199
272
|
}
|
|
200
273
|
.justify-start {
|
|
201
274
|
margin-inline-end: auto;
|
|
@@ -246,103 +319,83 @@
|
|
|
246
319
|
text-overflow: ellipsis;
|
|
247
320
|
}
|
|
248
321
|
|
|
322
|
+
.seg-thumb {
|
|
323
|
+
position: absolute;
|
|
324
|
+
top: 0;
|
|
325
|
+
left: 0;
|
|
326
|
+
width: 0;
|
|
327
|
+
height: 0;
|
|
328
|
+
border-radius: var(--r-pill);
|
|
329
|
+
background: var(--bg);
|
|
330
|
+
box-shadow: var(--shadow-sm);
|
|
331
|
+
transform-origin: top left;
|
|
332
|
+
pointer-events: none;
|
|
333
|
+
transition:
|
|
334
|
+
translate var(--dur-spring) var(--ease-spring),
|
|
335
|
+
scale var(--dur-spring) var(--ease-spring);
|
|
336
|
+
}
|
|
337
|
+
.seg:not(.placed) .seg-thumb {
|
|
338
|
+
display: none;
|
|
339
|
+
}
|
|
249
340
|
.seg-item {
|
|
341
|
+
position: relative;
|
|
250
342
|
display: inline-flex;
|
|
251
343
|
align-items: center;
|
|
252
344
|
justify-content: center;
|
|
253
345
|
gap: var(--sp-2);
|
|
254
|
-
|
|
346
|
+
height: 100%;
|
|
347
|
+
padding: 0 var(--control-px-md);
|
|
255
348
|
border: 0;
|
|
256
349
|
border-radius: var(--r-pill);
|
|
257
350
|
background: transparent;
|
|
258
351
|
color: var(--text-muted);
|
|
259
|
-
font-size: var(--fs-
|
|
352
|
+
font-size: var(--control-fs-md);
|
|
260
353
|
font-weight: var(--fw-medium);
|
|
261
|
-
line-height:
|
|
354
|
+
line-height: var(--lh-none);
|
|
355
|
+
--icon-size: var(--control-icon-md);
|
|
262
356
|
white-space: nowrap;
|
|
263
357
|
cursor: pointer;
|
|
264
358
|
transition:
|
|
265
|
-
background
|
|
266
|
-
color
|
|
267
|
-
}
|
|
268
|
-
.seg-sm .seg-item {
|
|
269
|
-
padding: 0.2rem var(--sp-2);
|
|
270
|
-
font-size: var(--fs-xs);
|
|
271
|
-
}
|
|
272
|
-
/* Compact toolbar contract: the outer interactive box exactly matches
|
|
273
|
-
Button size="sm" and Popover size="sm". */
|
|
274
|
-
.seg-sm {
|
|
275
|
-
height: var(--control-height-compact);
|
|
276
|
-
min-height: var(--control-height-compact);
|
|
359
|
+
background var(--dur-fast) var(--ease),
|
|
360
|
+
color var(--dur-fast) var(--ease);
|
|
277
361
|
}
|
|
278
362
|
.seg-sm .seg-item {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
.seg-control {
|
|
283
|
-
height: var(--control-height);
|
|
284
|
-
min-height: var(--control-height);
|
|
285
|
-
}
|
|
286
|
-
.seg-control .seg-item {
|
|
287
|
-
height: 100%;
|
|
288
|
-
}
|
|
289
|
-
.seg-box {
|
|
290
|
-
height: var(--control-height-default);
|
|
291
|
-
min-height: var(--control-height-default);
|
|
292
|
-
}
|
|
293
|
-
.seg-box .seg-item {
|
|
294
|
-
height: 100%;
|
|
295
|
-
}
|
|
296
|
-
/* Wrapped rows size to their content; each item keeps the one-row height. */
|
|
297
|
-
.seg-wrap.seg-sm,
|
|
298
|
-
.seg-wrap.seg-control,
|
|
299
|
-
.seg-wrap.seg-box {
|
|
300
|
-
height: auto;
|
|
301
|
-
}
|
|
302
|
-
.seg-wrap.seg-sm .seg-item {
|
|
303
|
-
height: auto;
|
|
304
|
-
min-height: calc(var(--control-height-compact) - 8px);
|
|
363
|
+
padding: 0 var(--control-px-sm);
|
|
364
|
+
font-size: var(--control-fs-sm);
|
|
365
|
+
--icon-size: var(--control-icon-sm);
|
|
305
366
|
}
|
|
306
|
-
.seg-wrap
|
|
367
|
+
.seg-wrap .seg-item {
|
|
307
368
|
height: auto;
|
|
308
|
-
min-height:
|
|
309
|
-
}
|
|
310
|
-
.seg-wrap.seg-box .seg-item {
|
|
311
|
-
height: auto;
|
|
312
|
-
min-height: calc(var(--control-height-default) - 8px);
|
|
369
|
+
min-height: var(--seg-item-h);
|
|
313
370
|
}
|
|
314
371
|
/* Capped at half a row: one row stays a pill, extra rows square the corners. */
|
|
315
372
|
.seg-wrap:not(.seg-icon) {
|
|
316
|
-
border-radius: calc(
|
|
317
|
-
}
|
|
318
|
-
.seg-wrap.seg-sm:not(.seg-icon) {
|
|
319
|
-
border-radius: calc(var(--control-height-compact) / 2);
|
|
320
|
-
}
|
|
321
|
-
.seg-wrap.seg-control:not(.seg-icon) {
|
|
322
|
-
border-radius: calc(var(--control-height) / 2);
|
|
373
|
+
border-radius: calc(var(--seg-h) / 2);
|
|
323
374
|
}
|
|
324
|
-
|
|
325
|
-
|
|
375
|
+
/* Icon-only segments are squares; corners stay concentric with the track. */
|
|
376
|
+
.seg-icon .seg-item,
|
|
377
|
+
.seg-icon .seg-thumb {
|
|
378
|
+
border-radius: calc(var(--r-md) - var(--seg-inset));
|
|
326
379
|
}
|
|
327
380
|
.seg-icon .seg-item {
|
|
328
381
|
gap: 0;
|
|
329
|
-
padding: 0
|
|
330
|
-
|
|
331
|
-
font-size: var(--
|
|
382
|
+
padding: 0;
|
|
383
|
+
width: var(--seg-item-h);
|
|
384
|
+
font-size: var(--control-icon-md);
|
|
332
385
|
}
|
|
333
386
|
.seg-icon.seg-sm .seg-item {
|
|
334
|
-
|
|
335
|
-
font-size: var(--fs-sm);
|
|
387
|
+
font-size: var(--control-icon-sm);
|
|
336
388
|
}
|
|
337
389
|
/* A labelled icon segment renders <icon><label>, so it must keep the normal
|
|
338
390
|
icon/label gap and the roomier pill padding — only true icon-only segments
|
|
339
|
-
collapse to the square
|
|
391
|
+
collapse to the square layout above. */
|
|
340
392
|
.seg-icon .seg-item.has-label {
|
|
341
393
|
gap: var(--sp-2);
|
|
342
|
-
|
|
394
|
+
width: auto;
|
|
395
|
+
padding: 0 var(--control-px-md);
|
|
343
396
|
}
|
|
344
397
|
.seg-icon.seg-sm .seg-item.has-label {
|
|
345
|
-
padding: 0
|
|
398
|
+
padding: 0 var(--control-px-sm);
|
|
346
399
|
}
|
|
347
400
|
|
|
348
401
|
/* collapseLabels='mobile': below the breakpoint, drop the label text and
|
|
@@ -354,10 +407,8 @@
|
|
|
354
407
|
}
|
|
355
408
|
.seg-collapse-mobile .seg-item.has-label {
|
|
356
409
|
gap: 0;
|
|
357
|
-
padding: 0
|
|
358
|
-
|
|
359
|
-
.seg-collapse-mobile.seg-sm .seg-item.has-label {
|
|
360
|
-
padding: 0.25rem;
|
|
410
|
+
padding: 0;
|
|
411
|
+
width: var(--seg-item-h);
|
|
361
412
|
}
|
|
362
413
|
}
|
|
363
414
|
@container (max-width: 35rem) {
|
|
@@ -366,37 +417,42 @@
|
|
|
366
417
|
}
|
|
367
418
|
.collapse-container .seg-item.has-label {
|
|
368
419
|
gap: 0;
|
|
369
|
-
padding: 0
|
|
370
|
-
|
|
371
|
-
.collapse-container.seg-sm .seg-item.has-label {
|
|
372
|
-
padding: 0.25rem;
|
|
420
|
+
padding: 0;
|
|
421
|
+
width: var(--seg-item-h);
|
|
373
422
|
}
|
|
374
423
|
}
|
|
375
424
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
425
|
+
@media (hover: hover) and (pointer: fine) {
|
|
426
|
+
.seg-item:hover:not(:disabled):not(.selected) {
|
|
427
|
+
color: var(--text);
|
|
428
|
+
background: var(--bg-elevated);
|
|
429
|
+
}
|
|
379
430
|
}
|
|
380
431
|
.seg-item.selected {
|
|
381
432
|
color: var(--text);
|
|
382
433
|
background: var(--bg);
|
|
383
434
|
box-shadow: var(--shadow-sm);
|
|
384
435
|
}
|
|
436
|
+
.seg.placed .seg-item.selected {
|
|
437
|
+
background: transparent;
|
|
438
|
+
box-shadow: none;
|
|
439
|
+
}
|
|
385
440
|
.seg-item:disabled {
|
|
386
|
-
opacity:
|
|
441
|
+
opacity: var(--disabled-opacity);
|
|
387
442
|
cursor: not-allowed;
|
|
388
443
|
}
|
|
444
|
+
/* Scrolling tracks clip their overflow, so the ring sits inside the item. */
|
|
389
445
|
.seg-item:focus-visible {
|
|
390
|
-
outline:
|
|
391
|
-
outline-offset:
|
|
446
|
+
outline: var(--focus-ring);
|
|
447
|
+
outline-offset: var(--focus-ring-offset-inset);
|
|
392
448
|
}
|
|
393
449
|
|
|
394
450
|
.seg-count {
|
|
395
451
|
display: inline-flex;
|
|
396
452
|
align-items: center;
|
|
397
453
|
justify-content: center;
|
|
398
|
-
min-width:
|
|
399
|
-
padding: 0
|
|
454
|
+
min-width: var(--sp-5);
|
|
455
|
+
padding: 0 var(--sp-1-5);
|
|
400
456
|
border-radius: var(--r-pill);
|
|
401
457
|
background: var(--bg-elevated-2);
|
|
402
458
|
color: var(--text-faint);
|
|
@@ -404,13 +460,10 @@
|
|
|
404
460
|
font-weight: var(--fw-semibold);
|
|
405
461
|
font-variant-numeric: tabular-nums;
|
|
406
462
|
transition:
|
|
407
|
-
background
|
|
408
|
-
color
|
|
463
|
+
background var(--dur-fast) var(--ease),
|
|
464
|
+
color var(--dur-fast) var(--ease);
|
|
409
465
|
}
|
|
410
466
|
@media (pointer: coarse) {
|
|
411
|
-
.seg-item {
|
|
412
|
-
position: relative;
|
|
413
|
-
}
|
|
414
467
|
.seg-item::before {
|
|
415
468
|
content: '';
|
|
416
469
|
position: absolute;
|
|
@@ -36,6 +36,8 @@ type $$ComponentProps = {
|
|
|
36
36
|
scroll?: boolean;
|
|
37
37
|
/** Fill the parent width, sharing it equally between segments. */
|
|
38
38
|
block?: boolean;
|
|
39
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
40
|
+
sound?: boolean;
|
|
39
41
|
class?: string;
|
|
40
42
|
option?: Snippet<[SegmentOption, boolean]>;
|
|
41
43
|
};
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import Button from '../atoms/Button.svelte';
|
|
9
9
|
import Icon, { type IconName } from '../atoms/Icon.svelte';
|
|
10
10
|
import Select from '../atoms/Select.svelte';
|
|
11
|
+
import { playSound } from '../../sound';
|
|
11
12
|
|
|
12
13
|
type Option = { value: string; label: string };
|
|
13
14
|
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
grow = false,
|
|
29
30
|
shrink = true,
|
|
30
31
|
block = false,
|
|
32
|
+
sound = true,
|
|
31
33
|
class: klass = '',
|
|
32
34
|
...rest
|
|
33
35
|
}: {
|
|
@@ -60,9 +62,16 @@
|
|
|
60
62
|
// Shared square box scale (`--box-xs/sm/md/lg`); default is the 2.25rem icon box.
|
|
61
63
|
box?: 'xs' | 'sm' | 'md' | 'lg';
|
|
62
64
|
hitArea?: 'auto' | 'compact';
|
|
65
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
66
|
+
sound?: boolean;
|
|
63
67
|
class?: string;
|
|
64
68
|
[key: string]: unknown;
|
|
65
69
|
} = $props();
|
|
70
|
+
|
|
71
|
+
function change(e: Event) {
|
|
72
|
+
playSound('click', sound);
|
|
73
|
+
onchange((e.currentTarget as HTMLSelectElement).value);
|
|
74
|
+
}
|
|
66
75
|
</script>
|
|
67
76
|
|
|
68
77
|
<!-- The wrapper (owned here, so styled scoped) is the positioning context that
|
|
@@ -89,7 +98,7 @@
|
|
|
89
98
|
aria-label={label}
|
|
90
99
|
{title}
|
|
91
100
|
{value}
|
|
92
|
-
onchange={
|
|
101
|
+
onchange={change}
|
|
93
102
|
>
|
|
94
103
|
{#if groups}
|
|
95
104
|
{#each groups as g, i (`${i}:${g.label}`)}
|
|
@@ -129,14 +138,20 @@
|
|
|
129
138
|
.select-button.active {
|
|
130
139
|
color: var(--accent);
|
|
131
140
|
}
|
|
132
|
-
/* The inert face takes no pointer events: mirror ghost hover and the
|
|
141
|
+
/* The inert face takes no pointer events: mirror ghost hover, press and the
|
|
133
142
|
focus ring from the select overlaying it. */
|
|
134
|
-
.select-button:
|
|
135
|
-
|
|
143
|
+
.select-button:active :global(.btn-ghost) {
|
|
144
|
+
--btn-scale-dur: var(--dur-press);
|
|
145
|
+
scale: var(--press-scale-sm);
|
|
136
146
|
}
|
|
137
147
|
.select-button:has(:focus-visible) {
|
|
138
|
-
outline:
|
|
139
|
-
outline-offset:
|
|
148
|
+
outline: var(--focus-ring);
|
|
149
|
+
outline-offset: var(--focus-ring-offset);
|
|
150
|
+
}
|
|
151
|
+
@media (hover: hover) and (pointer: fine) {
|
|
152
|
+
.select-button:hover :global(.btn-ghost) {
|
|
153
|
+
background: var(--bg-elevated-2);
|
|
154
|
+
}
|
|
140
155
|
}
|
|
141
156
|
.select-button.row :global(.btn) {
|
|
142
157
|
justify-content: flex-start;
|
|
@@ -32,6 +32,8 @@ type $$ComponentProps = {
|
|
|
32
32
|
onchange: (value: string) => void;
|
|
33
33
|
box?: 'xs' | 'sm' | 'md' | 'lg';
|
|
34
34
|
hitArea?: 'auto' | 'compact';
|
|
35
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
36
|
+
sound?: boolean;
|
|
35
37
|
class?: string;
|
|
36
38
|
[key: string]: unknown;
|
|
37
39
|
};
|
|
@@ -585,8 +585,8 @@
|
|
|
585
585
|
border-radius: var(--r-sm);
|
|
586
586
|
background: transparent;
|
|
587
587
|
transition:
|
|
588
|
-
transform
|
|
589
|
-
box-shadow
|
|
588
|
+
transform var(--dur-base) var(--ease),
|
|
589
|
+
box-shadow var(--dur-base) var(--ease);
|
|
590
590
|
}
|
|
591
591
|
.sortable-sm .sl-lift {
|
|
592
592
|
min-height: var(--sortable-row-h, 1.875rem);
|
|
@@ -637,13 +637,17 @@
|
|
|
637
637
|
translate: -50% -50%;
|
|
638
638
|
background: radial-gradient(circle, currentColor 1px, transparent 1.5px) 0 0 / 5px 5px;
|
|
639
639
|
}
|
|
640
|
-
|
|
640
|
+
@media (hover: hover) and (pointer: fine) {
|
|
641
|
+
.sl-grip:hover {
|
|
642
|
+
color: var(--accent);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
641
645
|
.sl-grip[aria-pressed='true'] {
|
|
642
646
|
color: var(--accent);
|
|
643
647
|
}
|
|
644
648
|
.sl-grip:focus-visible {
|
|
645
649
|
outline: var(--focus-ring);
|
|
646
|
-
outline-offset: -
|
|
650
|
+
outline-offset: var(--focus-ring-offset-inset);
|
|
647
651
|
}
|
|
648
652
|
.handle-row .sl-row:not(.locked) {
|
|
649
653
|
cursor: grab;
|
|
@@ -676,7 +680,7 @@
|
|
|
676
680
|
}
|
|
677
681
|
button.sl-body:focus-visible {
|
|
678
682
|
outline: var(--focus-ring);
|
|
679
|
-
outline-offset: -
|
|
683
|
+
outline-offset: var(--focus-ring-offset-inset);
|
|
680
684
|
border-radius: var(--r-sm);
|
|
681
685
|
}
|
|
682
686
|
.sl-row.selected button.sl-body {
|
|
@@ -687,7 +691,7 @@
|
|
|
687
691
|
flex: none;
|
|
688
692
|
display: inline-flex;
|
|
689
693
|
align-items: center;
|
|
690
|
-
gap:
|
|
694
|
+
gap: var(--sp-0-5);
|
|
691
695
|
}
|
|
692
696
|
.sl-live {
|
|
693
697
|
position: absolute;
|
|
@@ -712,9 +716,4 @@
|
|
|
712
716
|
inset-inline: 0;
|
|
713
717
|
}
|
|
714
718
|
}
|
|
715
|
-
@media (prefers-reduced-motion: reduce) {
|
|
716
|
-
.sl-lift {
|
|
717
|
-
transition: none;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
719
|
</style>
|