@dorsk/tsumikit 0.71.0 → 0.73.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 +54 -5
- package/dist/autoresize.js +13 -0
- package/dist/clipboard.js +12 -0
- package/dist/components/atoms/Artwork.svelte +4 -2
- package/dist/components/atoms/Avatar.svelte +4 -3
- package/dist/components/atoms/Badge.svelte +13 -0
- package/dist/components/atoms/Button.svelte +25 -9
- package/dist/components/atoms/Card.svelte +2 -0
- package/dist/components/atoms/Dot.svelte +1 -1
- package/dist/components/atoms/Icon.svelte +7 -0
- package/dist/components/atoms/Icon.svelte.d.ts +7 -0
- package/dist/components/atoms/Kbd.svelte +28 -16
- package/dist/components/atoms/Link.svelte +6 -4
- package/dist/components/atoms/Scrim.svelte +18 -1
- package/dist/components/atoms/Slider.svelte +11 -1
- package/dist/components/atoms/Swatch.svelte +12 -0
- package/dist/components/atoms/TimeDisplay.svelte +98 -0
- package/dist/components/atoms/TimeDisplay.svelte.d.ts +24 -0
- package/dist/components/layouts/AppShell.svelte +72 -40
- package/dist/components/layouts/Container.svelte +25 -3
- package/dist/components/layouts/MasterDetail.svelte +59 -33
- package/dist/components/layouts/MasterDetail.svelte.d.ts +1 -1
- package/dist/components/layouts/NavItem.svelte +8 -4
- package/dist/components/layouts/NavSection.svelte +8 -2
- package/dist/components/layouts/ResizablePanel.svelte +19 -4
- package/dist/components/layouts/TabBar.svelte +217 -0
- package/dist/components/layouts/TabBar.svelte.d.ts +27 -0
- package/dist/components/layouts/Toolbar.svelte +5 -0
- package/dist/components/layouts/toolbar-roving.d.ts +16 -0
- package/dist/components/layouts/toolbar-roving.js +47 -1
- package/dist/components/molecules/Accordion.svelte +2 -1
- package/dist/components/molecules/AttachmentList.svelte +1 -0
- package/dist/components/molecules/Breadcrumb.svelte +7 -6
- package/dist/components/molecules/Breadcrumb.svelte.d.ts +2 -1
- package/dist/components/molecules/CapBar.svelte +3 -1
- package/dist/components/molecules/Composer.svelte +27 -5
- package/dist/components/molecules/Drawer.svelte +10 -1
- package/dist/components/molecules/Dropzone.svelte +2 -1
- package/dist/components/molecules/FilterInput.svelte +17 -1
- package/dist/components/molecules/FilterInput.svelte.d.ts +2 -0
- package/dist/components/molecules/Metric.svelte +28 -3
- package/dist/components/molecules/Modal.svelte +20 -4
- package/dist/components/molecules/Popover.svelte +7 -3
- package/dist/components/molecules/PullToRefresh.svelte +205 -0
- package/dist/components/molecules/PullToRefresh.svelte.d.ts +18 -0
- package/dist/components/molecules/SectionHeader.svelte +29 -14
- package/dist/components/molecules/SeekSlider.svelte +370 -0
- package/dist/components/molecules/SeekSlider.svelte.d.ts +37 -0
- package/dist/components/molecules/SegmentedControl.svelte +10 -6
- package/dist/components/molecules/SelectButton.svelte +33 -22
- package/dist/components/molecules/SwipeRow.svelte +349 -0
- package/dist/components/molecules/SwipeRow.svelte.d.ts +37 -0
- package/dist/components/molecules/Tabs.svelte +11 -5
- package/dist/components/molecules/Timestamp.svelte +1 -1
- package/dist/components/molecules/Toaster.svelte +70 -13
- package/dist/components/molecules/Tooltip.svelte +19 -10
- package/dist/components/molecules/VolumeSlider.svelte +367 -0
- package/dist/components/molecules/VolumeSlider.svelte.d.ts +30 -0
- package/dist/components/molecules/combobox-keyboard.js +2 -1
- package/dist/components/organisms/ConversationFeed.svelte +22 -8
- package/dist/components/organisms/ConversationFeed.svelte.d.ts +2 -0
- package/dist/components/organisms/DataTable.svelte +22 -5
- package/dist/components/organisms/DataTable.svelte.d.ts +4 -2
- package/dist/components/organisms/FilterSearchBar.svelte +4 -0
- package/dist/components/organisms/FilterSearchBar.svelte.d.ts +2 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/markdown.js +15 -7
- package/dist/media-time.d.ts +6 -0
- package/dist/media-time.js +25 -0
- package/dist/query/edit.js +1 -16
- package/dist/query/parser.js +68 -28
- package/dist/query/query.js +9 -5
- package/dist/query/quotes.d.ts +24 -0
- package/dist/query/quotes.js +78 -0
- package/dist/query/single.js +1 -3
- package/dist/query/suggest.js +25 -32
- package/dist/stores/fontscale.svelte.js +3 -2
- package/dist/stores/storage.d.ts +2 -0
- package/dist/stores/storage.js +16 -0
- package/dist/stores/theme.svelte.js +4 -7
- package/dist/styles/reset.css +30 -0
- package/dist/styles/syntax.css +0 -29
- package/package.json +1 -1
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Compact volume control: a mute toggle whose glyph tracks the level, and a
|
|
4
|
+
* short slider. `oninput` fires on every change, `onchange` once per
|
|
5
|
+
* release or keyboard/wheel step. The slider hides on touch devices by
|
|
6
|
+
* default (`hideOnTouch`), where the OS owns volume; the mute button stays.
|
|
7
|
+
*/
|
|
8
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
9
|
+
import type { IconName } from '../atoms/Icon.svelte';
|
|
10
|
+
import IconButton from './IconButton.svelte';
|
|
11
|
+
|
|
12
|
+
type Own = {
|
|
13
|
+
/** Level 0–1. */
|
|
14
|
+
value?: number;
|
|
15
|
+
muted?: boolean;
|
|
16
|
+
orientation?: 'horizontal' | 'vertical';
|
|
17
|
+
size?: 'sm' | 'md';
|
|
18
|
+
/** Level per arrow key or wheel notch. */
|
|
19
|
+
step?: number;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
hideOnTouch?: boolean;
|
|
22
|
+
label?: string;
|
|
23
|
+
muteLabel?: string;
|
|
24
|
+
unmuteLabel?: string;
|
|
25
|
+
oninput?: (value: number) => void;
|
|
26
|
+
onchange?: (value: number) => void;
|
|
27
|
+
onmutechange?: (muted: boolean) => void;
|
|
28
|
+
class?: string;
|
|
29
|
+
style?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
value = $bindable(1),
|
|
34
|
+
muted = $bindable(false),
|
|
35
|
+
orientation = 'horizontal',
|
|
36
|
+
size = 'sm',
|
|
37
|
+
step = 0.05,
|
|
38
|
+
disabled = false,
|
|
39
|
+
hideOnTouch = true,
|
|
40
|
+
label = 'Volume',
|
|
41
|
+
muteLabel = 'Mute',
|
|
42
|
+
unmuteLabel = 'Unmute',
|
|
43
|
+
oninput,
|
|
44
|
+
onchange,
|
|
45
|
+
onmutechange,
|
|
46
|
+
class: klass = '',
|
|
47
|
+
style: styleProp,
|
|
48
|
+
...rest
|
|
49
|
+
}: Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own = $props();
|
|
50
|
+
|
|
51
|
+
let dragging = $state(false);
|
|
52
|
+
let pointerId: number | null = null;
|
|
53
|
+
|
|
54
|
+
const clamp = (v: number) => Math.round(Math.min(1, Math.max(0, v)) * 1e4) / 1e4;
|
|
55
|
+
const level = $derived(muted ? 0 : clamp(value));
|
|
56
|
+
const pct = $derived(Math.round(level * 100));
|
|
57
|
+
const vertical = $derived(orientation === 'vertical');
|
|
58
|
+
const icon = $derived<IconName>(
|
|
59
|
+
muted ? 'volume-mute' : level === 0 ? 'volume' : level < 0.5 ? 'volume-low' : 'volume-high'
|
|
60
|
+
);
|
|
61
|
+
const valueText = $derived(muted ? 'Muted' : `${pct}%`);
|
|
62
|
+
|
|
63
|
+
function setMuted(m: boolean) {
|
|
64
|
+
if (m === muted) return;
|
|
65
|
+
muted = m;
|
|
66
|
+
onmutechange?.(m);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function apply(v: number) {
|
|
70
|
+
const next = clamp(v);
|
|
71
|
+
value = next;
|
|
72
|
+
if (next > 0) setMuted(false);
|
|
73
|
+
oninput?.(next);
|
|
74
|
+
return next;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function commit(v: number) {
|
|
78
|
+
onchange?.(apply(v));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function fracAt(el: HTMLElement, e: PointerEvent) {
|
|
82
|
+
const r = el.getBoundingClientRect();
|
|
83
|
+
let f: number;
|
|
84
|
+
if (vertical) {
|
|
85
|
+
f = r.height ? 1 - (e.clientY - r.top) / r.height : 0;
|
|
86
|
+
} else {
|
|
87
|
+
f = r.width ? (e.clientX - r.left) / r.width : 0;
|
|
88
|
+
if (getComputedStyle(el).direction === 'rtl') f = 1 - f;
|
|
89
|
+
}
|
|
90
|
+
return clamp(f);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function release(el: HTMLElement) {
|
|
94
|
+
if (pointerId !== null && typeof el.releasePointerCapture === 'function') {
|
|
95
|
+
try {
|
|
96
|
+
el.releasePointerCapture(pointerId);
|
|
97
|
+
} catch {}
|
|
98
|
+
}
|
|
99
|
+
pointerId = null;
|
|
100
|
+
dragging = false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function onpointerdown(e: PointerEvent) {
|
|
104
|
+
if (disabled || e.button !== 0 || pointerId !== null) return;
|
|
105
|
+
const el = e.currentTarget as HTMLElement;
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
pointerId = e.pointerId;
|
|
108
|
+
dragging = true;
|
|
109
|
+
if (typeof el.setPointerCapture === 'function') {
|
|
110
|
+
try {
|
|
111
|
+
el.setPointerCapture(e.pointerId);
|
|
112
|
+
} catch {}
|
|
113
|
+
}
|
|
114
|
+
el.focus?.({ preventScroll: true });
|
|
115
|
+
apply(fracAt(el, e));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function onpointermove(e: PointerEvent) {
|
|
119
|
+
if (pointerId !== e.pointerId) return;
|
|
120
|
+
apply(fracAt(e.currentTarget as HTMLElement, e));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function onpointerup(e: PointerEvent) {
|
|
124
|
+
if (pointerId !== e.pointerId) return;
|
|
125
|
+
release(e.currentTarget as HTMLElement);
|
|
126
|
+
onchange?.(value);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function onpointercancel(e: PointerEvent) {
|
|
130
|
+
if (pointerId !== e.pointerId) return;
|
|
131
|
+
release(e.currentTarget as HTMLElement);
|
|
132
|
+
onchange?.(value);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function onlostpointercapture(e: PointerEvent) {
|
|
136
|
+
if (pointerId === null || pointerId !== e.pointerId) return;
|
|
137
|
+
pointerId = null;
|
|
138
|
+
dragging = false;
|
|
139
|
+
onchange?.(value);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function onkeydown(e: KeyboardEvent) {
|
|
143
|
+
if (disabled) return;
|
|
144
|
+
const base = level;
|
|
145
|
+
let next: number | null = null;
|
|
146
|
+
switch (e.key) {
|
|
147
|
+
case 'ArrowRight':
|
|
148
|
+
case 'ArrowUp':
|
|
149
|
+
next = base + step;
|
|
150
|
+
break;
|
|
151
|
+
case 'ArrowLeft':
|
|
152
|
+
case 'ArrowDown':
|
|
153
|
+
next = base - step;
|
|
154
|
+
break;
|
|
155
|
+
case 'PageUp':
|
|
156
|
+
next = base + step * 2;
|
|
157
|
+
break;
|
|
158
|
+
case 'PageDown':
|
|
159
|
+
next = base - step * 2;
|
|
160
|
+
break;
|
|
161
|
+
case 'Home':
|
|
162
|
+
next = 0;
|
|
163
|
+
break;
|
|
164
|
+
case 'End':
|
|
165
|
+
next = 1;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
if (next === null) return;
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
commit(next);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function wheel(el: HTMLElement) {
|
|
174
|
+
const onwheel = (e: WheelEvent) => {
|
|
175
|
+
if (disabled) return;
|
|
176
|
+
const d = e.deltaY || e.deltaX;
|
|
177
|
+
if (!d) return;
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
commit(level + (d < 0 ? step : -step));
|
|
180
|
+
};
|
|
181
|
+
el.addEventListener('wheel', onwheel, { passive: false });
|
|
182
|
+
return () => el.removeEventListener('wheel', onwheel);
|
|
183
|
+
}
|
|
184
|
+
</script>
|
|
185
|
+
|
|
186
|
+
<div
|
|
187
|
+
data-tsu="VolumeSlider"
|
|
188
|
+
class="vol vol--{size} vol--{orientation} {klass}"
|
|
189
|
+
class:vol--hide-touch={hideOnTouch}
|
|
190
|
+
class:vol--dragging={dragging}
|
|
191
|
+
class:vol--muted={muted}
|
|
192
|
+
style="--pct: {pct};{styleProp ?? ''}"
|
|
193
|
+
{...rest}
|
|
194
|
+
>
|
|
195
|
+
<IconButton
|
|
196
|
+
{icon}
|
|
197
|
+
label={muted ? unmuteLabel : muteLabel}
|
|
198
|
+
pressed={muted}
|
|
199
|
+
box="sm"
|
|
200
|
+
size={size === 'sm' ? 16 : 18}
|
|
201
|
+
{disabled}
|
|
202
|
+
onclick={() => setMuted(!muted)}
|
|
203
|
+
data-part="mute"
|
|
204
|
+
/>
|
|
205
|
+
<div
|
|
206
|
+
class="vol-slab"
|
|
207
|
+
data-part="slab"
|
|
208
|
+
role="slider"
|
|
209
|
+
tabindex={disabled ? -1 : 0}
|
|
210
|
+
aria-label={label}
|
|
211
|
+
aria-orientation={orientation}
|
|
212
|
+
aria-valuemin={0}
|
|
213
|
+
aria-valuemax={100}
|
|
214
|
+
aria-valuenow={pct}
|
|
215
|
+
aria-valuetext={valueText}
|
|
216
|
+
aria-disabled={disabled ? 'true' : undefined}
|
|
217
|
+
{onpointerdown}
|
|
218
|
+
{onpointermove}
|
|
219
|
+
{onpointerup}
|
|
220
|
+
{onpointercancel}
|
|
221
|
+
{onlostpointercapture}
|
|
222
|
+
{onkeydown}
|
|
223
|
+
{@attach wheel}
|
|
224
|
+
>
|
|
225
|
+
<span class="vol-track" data-part="track">
|
|
226
|
+
<i class="vol-fill" data-part="fill"></i>
|
|
227
|
+
</span>
|
|
228
|
+
<span class="vol-anchor">
|
|
229
|
+
<i class="vol-thumb" data-part="thumb"></i>
|
|
230
|
+
</span>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<style>
|
|
235
|
+
.vol {
|
|
236
|
+
--vol-track: 3px;
|
|
237
|
+
--vol-thumb: 10px;
|
|
238
|
+
--vol-length: 5rem;
|
|
239
|
+
--vol-accent: var(--slider-accent, var(--accent));
|
|
240
|
+
display: inline-flex;
|
|
241
|
+
align-items: center;
|
|
242
|
+
gap: var(--sp-1);
|
|
243
|
+
vertical-align: middle;
|
|
244
|
+
}
|
|
245
|
+
.vol--md {
|
|
246
|
+
--vol-track: 4px;
|
|
247
|
+
--vol-thumb: 12px;
|
|
248
|
+
--vol-length: 7rem;
|
|
249
|
+
}
|
|
250
|
+
.vol--vertical {
|
|
251
|
+
flex-direction: column-reverse;
|
|
252
|
+
}
|
|
253
|
+
.vol-slab {
|
|
254
|
+
position: relative;
|
|
255
|
+
flex: none;
|
|
256
|
+
width: var(--vol-length);
|
|
257
|
+
min-height: 1.25rem;
|
|
258
|
+
cursor: pointer;
|
|
259
|
+
touch-action: none;
|
|
260
|
+
user-select: none;
|
|
261
|
+
-webkit-user-select: none;
|
|
262
|
+
outline: none;
|
|
263
|
+
border-radius: var(--r-sm);
|
|
264
|
+
}
|
|
265
|
+
.vol--vertical .vol-slab {
|
|
266
|
+
width: 1.25rem;
|
|
267
|
+
height: var(--vol-length);
|
|
268
|
+
min-height: 0;
|
|
269
|
+
}
|
|
270
|
+
@media (pointer: coarse) {
|
|
271
|
+
.vol-slab {
|
|
272
|
+
min-height: var(--touch-target-compact);
|
|
273
|
+
}
|
|
274
|
+
.vol--vertical .vol-slab {
|
|
275
|
+
min-width: var(--touch-target-compact);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
@media (hover: none) and (pointer: coarse) {
|
|
279
|
+
.vol--hide-touch .vol-slab {
|
|
280
|
+
display: none;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
.vol-slab[aria-disabled='true'] {
|
|
284
|
+
cursor: default;
|
|
285
|
+
opacity: 0.45;
|
|
286
|
+
}
|
|
287
|
+
.vol-slab:focus-visible {
|
|
288
|
+
outline: var(--focus-ring);
|
|
289
|
+
outline-offset: var(--focus-ring-offset);
|
|
290
|
+
}
|
|
291
|
+
.vol-track {
|
|
292
|
+
position: absolute;
|
|
293
|
+
inset-inline: 0;
|
|
294
|
+
top: 50%;
|
|
295
|
+
height: var(--vol-track);
|
|
296
|
+
margin-top: calc(var(--vol-track) / -2);
|
|
297
|
+
border-radius: var(--r-pill);
|
|
298
|
+
background: var(--bg-elevated-2);
|
|
299
|
+
overflow: hidden;
|
|
300
|
+
}
|
|
301
|
+
.vol--vertical .vol-track {
|
|
302
|
+
inset-inline: auto;
|
|
303
|
+
inset-block: 0;
|
|
304
|
+
left: 50%;
|
|
305
|
+
top: auto;
|
|
306
|
+
width: var(--vol-track);
|
|
307
|
+
height: auto;
|
|
308
|
+
margin-top: 0;
|
|
309
|
+
margin-left: calc(var(--vol-track) / -2);
|
|
310
|
+
}
|
|
311
|
+
.vol-fill {
|
|
312
|
+
position: absolute;
|
|
313
|
+
inset-block: 0;
|
|
314
|
+
inset-inline-start: 0;
|
|
315
|
+
width: calc(var(--pct) * 1%);
|
|
316
|
+
background: var(--vol-accent);
|
|
317
|
+
}
|
|
318
|
+
.vol--vertical .vol-fill {
|
|
319
|
+
inset-inline: 0;
|
|
320
|
+
inset-block: auto 0;
|
|
321
|
+
width: auto;
|
|
322
|
+
height: calc(var(--pct) * 1%);
|
|
323
|
+
}
|
|
324
|
+
.vol-anchor {
|
|
325
|
+
position: absolute;
|
|
326
|
+
top: 50%;
|
|
327
|
+
inset-inline-start: calc(var(--pct) * 1%);
|
|
328
|
+
width: 0;
|
|
329
|
+
height: 0;
|
|
330
|
+
pointer-events: none;
|
|
331
|
+
}
|
|
332
|
+
.vol--vertical .vol-anchor {
|
|
333
|
+
top: auto;
|
|
334
|
+
bottom: calc(var(--pct) * 1%);
|
|
335
|
+
inset-inline-start: 50%;
|
|
336
|
+
}
|
|
337
|
+
.vol-thumb {
|
|
338
|
+
position: absolute;
|
|
339
|
+
left: 0;
|
|
340
|
+
top: 0;
|
|
341
|
+
width: var(--vol-thumb);
|
|
342
|
+
height: var(--vol-thumb);
|
|
343
|
+
border-radius: 50%;
|
|
344
|
+
background: var(--vol-accent);
|
|
345
|
+
border: 2px solid var(--bg);
|
|
346
|
+
box-shadow: var(--shadow-sm);
|
|
347
|
+
transform: translate(-50%, -50%) scale(0.6);
|
|
348
|
+
opacity: 0;
|
|
349
|
+
transition:
|
|
350
|
+
opacity 120ms var(--ease),
|
|
351
|
+
transform 120ms var(--ease);
|
|
352
|
+
}
|
|
353
|
+
.vol-slab:hover .vol-thumb,
|
|
354
|
+
.vol-slab:focus-visible .vol-thumb,
|
|
355
|
+
.vol--dragging .vol-thumb {
|
|
356
|
+
opacity: 1;
|
|
357
|
+
transform: translate(-50%, -50%) scale(1);
|
|
358
|
+
}
|
|
359
|
+
.vol--muted .vol-fill {
|
|
360
|
+
background: var(--text-faint);
|
|
361
|
+
}
|
|
362
|
+
@media (prefers-reduced-motion: reduce) {
|
|
363
|
+
.vol-thumb {
|
|
364
|
+
transition: none;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
</style>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact volume control: a mute toggle whose glyph tracks the level, and a
|
|
3
|
+
* short slider. `oninput` fires on every change, `onchange` once per
|
|
4
|
+
* release or keyboard/wheel step. The slider hides on touch devices by
|
|
5
|
+
* default (`hideOnTouch`), where the OS owns volume; the mute button stays.
|
|
6
|
+
*/
|
|
7
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
8
|
+
type Own = {
|
|
9
|
+
/** Level 0–1. */
|
|
10
|
+
value?: number;
|
|
11
|
+
muted?: boolean;
|
|
12
|
+
orientation?: 'horizontal' | 'vertical';
|
|
13
|
+
size?: 'sm' | 'md';
|
|
14
|
+
/** Level per arrow key or wheel notch. */
|
|
15
|
+
step?: number;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
hideOnTouch?: boolean;
|
|
18
|
+
label?: string;
|
|
19
|
+
muteLabel?: string;
|
|
20
|
+
unmuteLabel?: string;
|
|
21
|
+
oninput?: (value: number) => void;
|
|
22
|
+
onchange?: (value: number) => void;
|
|
23
|
+
onmutechange?: (muted: boolean) => void;
|
|
24
|
+
class?: string;
|
|
25
|
+
style?: string;
|
|
26
|
+
};
|
|
27
|
+
type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own;
|
|
28
|
+
declare const VolumeSlider: import("svelte").Component<$$ComponentProps, {}, "muted" | "value">;
|
|
29
|
+
type VolumeSlider = ReturnType<typeof VolumeSlider>;
|
|
30
|
+
export default VolumeSlider;
|
|
@@ -157,8 +157,9 @@ export function caretRect(el) {
|
|
|
157
157
|
}
|
|
158
158
|
mirror.textContent = el.value.slice(0, caret);
|
|
159
159
|
const marker = doc.createElement('span');
|
|
160
|
-
marker.textContent = el.value
|
|
160
|
+
marker.textContent = el.value[caret] ?? '.';
|
|
161
161
|
mirror.appendChild(marker);
|
|
162
|
+
mirror.appendChild(doc.createTextNode(el.value.slice(caret + 1)));
|
|
162
163
|
doc.body.appendChild(mirror);
|
|
163
164
|
const m = mirror.getBoundingClientRect();
|
|
164
165
|
const s = marker.getBoundingClientRect();
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
loadMoreLabel = 'Load older',
|
|
18
18
|
follow = true,
|
|
19
19
|
jumpLabel = 'Jump to latest',
|
|
20
|
+
label = 'Messages',
|
|
20
21
|
class: klass = '',
|
|
21
22
|
style: styleProp = ''
|
|
22
23
|
}: {
|
|
@@ -32,6 +33,8 @@
|
|
|
32
33
|
/** Stick to the bottom while the user is at the bottom. */
|
|
33
34
|
follow?: boolean;
|
|
34
35
|
jumpLabel?: string;
|
|
36
|
+
/** Accessible name of the message log. */
|
|
37
|
+
label?: string;
|
|
35
38
|
class?: string;
|
|
36
39
|
style?: string;
|
|
37
40
|
} = $props();
|
|
@@ -41,9 +44,10 @@
|
|
|
41
44
|
let scroller = $state<HTMLElement | null>(null);
|
|
42
45
|
let stuck = $state(true);
|
|
43
46
|
let pending = $state(false);
|
|
47
|
+
// Keeps screen readers from announcing a history load or swap as new messages.
|
|
48
|
+
let busy = $state(false);
|
|
44
49
|
let prevFirst: string | number | undefined;
|
|
45
50
|
let prevLast: string | number | undefined;
|
|
46
|
-
let prevLength = 0;
|
|
47
51
|
|
|
48
52
|
const atBottom = (el: HTMLElement) => el.scrollHeight - el.scrollTop - el.clientHeight <= THRESHOLD;
|
|
49
53
|
|
|
@@ -64,17 +68,26 @@
|
|
|
64
68
|
const el = scroller;
|
|
65
69
|
const first = items.length ? key(items[0]) : undefined;
|
|
66
70
|
const last = items.length ? key(items[items.length - 1]) : undefined;
|
|
67
|
-
const length = items.length;
|
|
68
71
|
if (!el) return;
|
|
69
|
-
const
|
|
70
|
-
const
|
|
72
|
+
const keys = prevFirst === undefined ? null : new Set(items.map(key));
|
|
73
|
+
const kept = (k: string | number | undefined) => k !== undefined && keys !== null && keys.has(k);
|
|
74
|
+
const prepended = first !== prevFirst && last === prevLast && kept(prevFirst);
|
|
75
|
+
const reset = prevLast !== undefined && !kept(prevLast) && !kept(prevFirst);
|
|
76
|
+
const appended = !reset && last !== undefined && last !== prevLast;
|
|
71
77
|
prevFirst = first;
|
|
72
78
|
prevLast = last;
|
|
73
|
-
prevLength = length;
|
|
74
79
|
const fromBottom = el.scrollHeight - el.scrollTop;
|
|
80
|
+
if (prepended || reset) {
|
|
81
|
+
busy = true;
|
|
82
|
+
requestAnimationFrame(() => requestAnimationFrame(() => (busy = false)));
|
|
83
|
+
}
|
|
75
84
|
tick().then(() => {
|
|
76
85
|
if (prepended) {
|
|
77
86
|
el.scrollTop = el.scrollHeight - fromBottom;
|
|
87
|
+
} else if (reset) {
|
|
88
|
+
pending = false;
|
|
89
|
+
if (untrack(() => follow)) scrollToBottom();
|
|
90
|
+
else el.scrollTop = 0;
|
|
78
91
|
} else if (appended) {
|
|
79
92
|
if (untrack(() => follow && stuck)) scrollToBottom();
|
|
80
93
|
else pending = true;
|
|
@@ -100,7 +113,7 @@
|
|
|
100
113
|
{#if items.length === 0 && empty}
|
|
101
114
|
{@render empty()}
|
|
102
115
|
{/if}
|
|
103
|
-
<div class="feed-items" {@attach pin}>
|
|
116
|
+
<div class="feed-items" role="log" aria-label={label} aria-busy={busy} {@attach pin}>
|
|
104
117
|
{#each items as entry, i (key(entry))}
|
|
105
118
|
{@render item(entry, i)}
|
|
106
119
|
{/each}
|
|
@@ -138,9 +151,10 @@
|
|
|
138
151
|
}
|
|
139
152
|
.jump {
|
|
140
153
|
position: absolute;
|
|
141
|
-
inset-inline
|
|
154
|
+
inset-inline: 0;
|
|
142
155
|
bottom: var(--sp-3);
|
|
143
|
-
|
|
156
|
+
width: fit-content;
|
|
157
|
+
margin-inline: auto;
|
|
144
158
|
z-index: 1;
|
|
145
159
|
border-radius: var(--r-pill);
|
|
146
160
|
box-shadow: var(--shadow-md);
|
|
@@ -87,9 +87,11 @@
|
|
|
87
87
|
/** Replaces a column's header content (e.g. a select-all checkbox). */
|
|
88
88
|
headerSnippets?: Record<string, Snippet<[Column<T>]>>;
|
|
89
89
|
empty?: string | Snippet;
|
|
90
|
+
/** Sticks within the table's own scroller, which also scrolls horizontally,
|
|
91
|
+
* so it only sticks when the table has a bounded height
|
|
92
|
+
* (e.g. `style="max-height: 60vh"`), not to the page. */
|
|
90
93
|
stickyHeader?: boolean;
|
|
91
|
-
/** Distance from the
|
|
92
|
-
* `var(--header-h)` when the page header overlaps. Defaults to 0. */
|
|
94
|
+
/** Distance from the scroller's top for the sticky header. Defaults to 0. */
|
|
93
95
|
stickyOffset?: string;
|
|
94
96
|
/** `fixed` makes declared column widths authoritative (needed for truncate). */
|
|
95
97
|
layout?: 'auto' | 'fixed';
|
|
@@ -171,6 +173,13 @@
|
|
|
171
173
|
|
|
172
174
|
const colCount = $derived(columns.length + (rowActions ? 1 : 0));
|
|
173
175
|
|
|
176
|
+
const NESTED_CONTROL = 'a,button,input,select,textarea,label,summary,[role="button"],[contenteditable]';
|
|
177
|
+
|
|
178
|
+
function fromNestedControl(e: Event & { currentTarget: HTMLElement }): boolean {
|
|
179
|
+
const hit = (e.target as Element).closest(NESTED_CONTROL);
|
|
180
|
+
return !!hit && hit !== e.currentTarget && e.currentTarget.contains(hit);
|
|
181
|
+
}
|
|
182
|
+
|
|
174
183
|
function display(col: Column<T>, row: T): unknown {
|
|
175
184
|
if (col.get) return col.get(row);
|
|
176
185
|
return (row as Record<string, unknown>)[col.key];
|
|
@@ -245,7 +254,7 @@
|
|
|
245
254
|
{:else if col.sortable}
|
|
246
255
|
<button type="button" class="dt-sort" onclick={() => toggleSort(col)}>
|
|
247
256
|
<span>{col.label}</span>
|
|
248
|
-
<span class="dt-arrow" class:active={sortKey === col.key}>
|
|
257
|
+
<span class="dt-arrow" aria-hidden="true" class:active={sortKey === col.key}>
|
|
249
258
|
{sortKey === col.key ? (sortDir === 'asc' ? '↑' : '↓') : '↕'}
|
|
250
259
|
</span>
|
|
251
260
|
</button>
|
|
@@ -280,9 +289,14 @@
|
|
|
280
289
|
class:clickable={!!onrowclick}
|
|
281
290
|
class:toned={!!tone && tone !== 'neutral'}
|
|
282
291
|
tabindex={onrowclick ? 0 : undefined}
|
|
283
|
-
onclick={onrowclick
|
|
292
|
+
onclick={onrowclick
|
|
293
|
+
? (e) => {
|
|
294
|
+
if (!fromNestedControl(e)) onrowclick(row);
|
|
295
|
+
}
|
|
296
|
+
: undefined}
|
|
284
297
|
onkeydown={onrowclick
|
|
285
298
|
? (e) => {
|
|
299
|
+
if (e.target !== e.currentTarget) return;
|
|
286
300
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
287
301
|
e.preventDefault();
|
|
288
302
|
onrowclick(row);
|
|
@@ -432,7 +446,10 @@
|
|
|
432
446
|
tr.clickable:hover,
|
|
433
447
|
tr.clickable:focus-visible {
|
|
434
448
|
background: var(--bg-elevated-2);
|
|
435
|
-
|
|
449
|
+
}
|
|
450
|
+
tr.clickable:focus-visible {
|
|
451
|
+
outline: var(--focus-ring);
|
|
452
|
+
outline-offset: -2px;
|
|
436
453
|
}
|
|
437
454
|
td.truncate {
|
|
438
455
|
white-space: nowrap;
|
|
@@ -38,9 +38,11 @@ declare function $$render<T>(): {
|
|
|
38
38
|
/** Replaces a column's header content (e.g. a select-all checkbox). */
|
|
39
39
|
headerSnippets?: Record<string, Snippet<[Column<T>]>>;
|
|
40
40
|
empty?: string | Snippet;
|
|
41
|
+
/** Sticks within the table's own scroller, which also scrolls horizontally,
|
|
42
|
+
* so it only sticks when the table has a bounded height
|
|
43
|
+
* (e.g. `style="max-height: 60vh"`), not to the page. */
|
|
41
44
|
stickyHeader?: boolean;
|
|
42
|
-
/** Distance from the
|
|
43
|
-
* `var(--header-h)` when the page header overlaps. Defaults to 0. */
|
|
45
|
+
/** Distance from the scroller's top for the sticky header. Defaults to 0. */
|
|
44
46
|
stickyOffset?: string;
|
|
45
47
|
/** `fixed` makes declared column widths authoritative (needed for truncate). */
|
|
46
48
|
layout?: "auto" | "fixed";
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
size = 'md',
|
|
30
30
|
shape = 'square',
|
|
31
31
|
surface = 'base',
|
|
32
|
+
font = 'mono',
|
|
32
33
|
hotkey,
|
|
33
34
|
showHotkey = false,
|
|
34
35
|
grow = false,
|
|
@@ -56,6 +57,8 @@
|
|
|
56
57
|
size?: ControlSize;
|
|
57
58
|
shape?: 'square' | 'pill';
|
|
58
59
|
surface?: 'base' | 'raised' | 'sunken';
|
|
60
|
+
/** `sans` renders the typed text and placeholder in `--font-sans`; chips stay mono. */
|
|
61
|
+
font?: 'mono' | 'sans';
|
|
59
62
|
hotkey?: string;
|
|
60
63
|
showHotkey?: boolean;
|
|
61
64
|
grow?: boolean;
|
|
@@ -81,6 +84,7 @@
|
|
|
81
84
|
{size}
|
|
82
85
|
{shape}
|
|
83
86
|
{surface}
|
|
87
|
+
{font}
|
|
84
88
|
{hotkey}
|
|
85
89
|
{showHotkey}
|
|
86
90
|
{grow}
|
|
@@ -19,6 +19,8 @@ type Own = {
|
|
|
19
19
|
size?: ControlSize;
|
|
20
20
|
shape?: 'square' | 'pill';
|
|
21
21
|
surface?: 'base' | 'raised' | 'sunken';
|
|
22
|
+
/** `sans` renders the typed text and placeholder in `--font-sans`; chips stay mono. */
|
|
23
|
+
font?: 'mono' | 'sans';
|
|
22
24
|
hotkey?: string;
|
|
23
25
|
showHotkey?: boolean;
|
|
24
26
|
grow?: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export { default as Swatch } from './components/atoms/Swatch.svelte';
|
|
|
30
30
|
export { default as Switch } from './components/atoms/Switch.svelte';
|
|
31
31
|
export { default as Text } from './components/atoms/Text.svelte';
|
|
32
32
|
export { default as Textarea } from './components/atoms/Textarea.svelte';
|
|
33
|
+
export { default as TimeDisplay } from './components/atoms/TimeDisplay.svelte';
|
|
33
34
|
export { default as AppShell } from './components/layouts/AppShell.svelte';
|
|
34
35
|
export { default as AutoGrid } from './components/layouts/AutoGrid.svelte';
|
|
35
36
|
export { default as Cluster } from './components/layouts/Cluster.svelte';
|
|
@@ -41,6 +42,7 @@ export { default as NavSection } from './components/layouts/NavSection.svelte';
|
|
|
41
42
|
export { default as ResizablePanel } from './components/layouts/ResizablePanel.svelte';
|
|
42
43
|
export { type ResizeHandleParams, resizeHandle, } from './components/layouts/resizable-panel-frame.js';
|
|
43
44
|
export { default as Stack } from './components/layouts/Stack.svelte';
|
|
45
|
+
export { default as TabBar, type TabBarItem } from './components/layouts/TabBar.svelte';
|
|
44
46
|
export { default as Toolbar } from './components/layouts/Toolbar.svelte';
|
|
45
47
|
export { type AccordionHeaderContext, type AccordionItem, type AccordionVariant, default as Accordion, } from './components/molecules/Accordion.svelte';
|
|
46
48
|
export { type Attachment, default as AttachmentList, } from './components/molecules/AttachmentList.svelte';
|
|
@@ -79,14 +81,17 @@ export { default as OptionButton } from './components/molecules/OptionButton.sve
|
|
|
79
81
|
export { default as Pagination } from './components/molecules/Pagination.svelte';
|
|
80
82
|
export { default as Popover } from './components/molecules/Popover.svelte';
|
|
81
83
|
export { default as Prose } from './components/molecules/Prose.svelte';
|
|
84
|
+
export { default as PullToRefresh } from './components/molecules/PullToRefresh.svelte';
|
|
82
85
|
export { default as RadialMenu, type RadialArc, } from './components/molecules/RadialMenu.svelte';
|
|
83
86
|
export { default as RadioGroup, type RadioOption, } from './components/molecules/RadioGroup.svelte';
|
|
84
87
|
export { default as SectionHeader } from './components/molecules/SectionHeader.svelte';
|
|
88
|
+
export { default as SeekSlider } from './components/molecules/SeekSlider.svelte';
|
|
85
89
|
export { default as SegmentedControl, type SegmentOption, } from './components/molecules/SegmentedControl.svelte';
|
|
86
90
|
export { default as SelectButton } from './components/molecules/SelectButton.svelte';
|
|
87
91
|
export { default as SortableList, SORTABLE_LIST_MESSAGES, type SortableListMessages, type SortableListMove, type SortableListRowContext, } from './components/molecules/SortableList.svelte';
|
|
88
92
|
export { default as SplitButton } from './components/molecules/SplitButton.svelte';
|
|
89
93
|
export { default as StatusBar } from './components/molecules/StatusBar.svelte';
|
|
94
|
+
export { default as SwipeRow, type SwipeAction } from './components/molecules/SwipeRow.svelte';
|
|
90
95
|
export { default as Tabs, type TabItem } from './components/molecules/Tabs.svelte';
|
|
91
96
|
export { default as ThemePicker } from './components/molecules/ThemePicker.svelte';
|
|
92
97
|
export { default as Timestamp } from './components/molecules/Timestamp.svelte';
|
|
@@ -94,6 +99,7 @@ export { default as Toaster } from './components/molecules/Toaster.svelte';
|
|
|
94
99
|
export { default as Toggle } from './components/molecules/Toggle.svelte';
|
|
95
100
|
export { default as Tooltip } from './components/molecules/Tooltip.svelte';
|
|
96
101
|
export { default as Truncate } from './components/molecules/Truncate.svelte';
|
|
102
|
+
export { default as VolumeSlider } from './components/molecules/VolumeSlider.svelte';
|
|
97
103
|
export { default as WorkingDir } from './components/molecules/WorkingDir.svelte';
|
|
98
104
|
export { default as ConversationFeed } from './components/organisms/ConversationFeed.svelte';
|
|
99
105
|
export { default as ConversationPanel } from './components/organisms/ConversationPanel.svelte';
|
|
@@ -103,6 +109,7 @@ export { formatCount, hasCount } from './count';
|
|
|
103
109
|
export { EMOJI_GROUPS, type EmojiEntry, type EmojiGroup, searchEmoji } from './emoji';
|
|
104
110
|
export { FIELD_KEY, type FieldContext, getFieldContext, setFieldContext, warnUnlabelled, } from './field-context';
|
|
105
111
|
export { escapeHtml, type Highlight, type RenderMarkdownOptions, renderMarkdown, stripAnsi, } from './markdown';
|
|
112
|
+
export { describeMediaTime, formatMediaTime, isLiveDuration } from './media-time';
|
|
106
113
|
export * as filterQuery from './query';
|
|
107
114
|
export { type AndNode, activeToken, compilePredicate, defaultOperator, type ExprNode, type FieldDef, type FieldType, type FilterNode, filters, findField, freeText, type LeafNode, type NotNode, OPERATORS, type Operator, type OperatorId, type OrNode, operatorByCode, operatorById, operatorsFor, parse, type Query, type QueryNode, resolveValues, type Schema, type Suggestion, type SuggestKind, type SuggestState, serialize, serializeFilter, singleQuery, suggest, suggestSingle, type TextNode, toSql, type ValueContext, type ValueOption, type ValueProvider, walk, } from './query';
|
|
108
115
|
export { type OptionSection, sectionOptions } from './select-options';
|