@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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{ type: 'open', index?: number }
|
|
3
|
+
* | { type: 'close' }
|
|
4
|
+
* | { type: 'select' }
|
|
5
|
+
* | { type: 'move', index: number }
|
|
6
|
+
* | { type: 'change', index: number }
|
|
7
|
+
* | { type: 'reorder', from: number, to: number }
|
|
8
|
+
* | { type: 'trailing' }
|
|
9
|
+
* | { type: 'type', char: string }} RichSelectAction
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Map a keydown on the trigger to what the select does with it. Closed, the
|
|
14
|
+
* arrows step the value like a native select and Enter/Space/Alt+ArrowDown open.
|
|
15
|
+
* Open, the arrows and Home/End move the active option, Enter/Space select,
|
|
16
|
+
* Escape closes, Alt+arrows reorder (when `reorder` is set), and Tab or
|
|
17
|
+
* ArrowRight reach the active option's trailing actions (when it has any).
|
|
18
|
+
* Printable characters feed the typeahead in both states. `undefined` leaves
|
|
19
|
+
* the key alone.
|
|
20
|
+
*
|
|
21
|
+
* @param {{ key: string, altKey?: boolean, ctrlKey?: boolean, metaKey?: boolean, shiftKey?: boolean }} e
|
|
22
|
+
* @param {{ open: boolean, count: number, index: number, reorder?: boolean, trailing?: boolean }} state
|
|
23
|
+
* @returns {RichSelectAction | undefined}
|
|
24
|
+
*/
|
|
25
|
+
export function richSelectAction(e, { open, count, index, reorder = false, trailing = false }) {
|
|
26
|
+
const { key } = e;
|
|
27
|
+
if (e.ctrlKey || e.metaKey) return undefined;
|
|
28
|
+
const last = count - 1;
|
|
29
|
+
const cur = index >= 0 && index <= last ? index : -1;
|
|
30
|
+
if (!open) {
|
|
31
|
+
if (e.altKey) return key === 'ArrowDown' ? { type: 'open' } : undefined;
|
|
32
|
+
if (key === 'Enter' || key === ' ') return { type: 'open' };
|
|
33
|
+
if (count <= 0) return undefined;
|
|
34
|
+
if (key === 'ArrowDown')
|
|
35
|
+
return { type: 'change', index: cur < 0 ? 0 : Math.min(cur + 1, last) };
|
|
36
|
+
if (key === 'ArrowUp') return { type: 'change', index: cur < 0 ? last : Math.max(cur - 1, 0) };
|
|
37
|
+
if (key === 'Home') return { type: 'change', index: 0 };
|
|
38
|
+
if (key === 'End') return { type: 'change', index: last };
|
|
39
|
+
return printable(e) ? { type: 'type', char: key } : undefined;
|
|
40
|
+
}
|
|
41
|
+
if (key === 'Escape') return { type: 'close' };
|
|
42
|
+
if (e.altKey) {
|
|
43
|
+
if (key === 'ArrowUp' && reorder && cur > 0) return { type: 'reorder', from: cur, to: cur - 1 };
|
|
44
|
+
if (key === 'ArrowDown' && reorder && cur >= 0 && cur < last)
|
|
45
|
+
return { type: 'reorder', from: cur, to: cur + 1 };
|
|
46
|
+
if (key === 'ArrowUp' && !reorder) return { type: 'select' };
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
if ((key === 'Tab' && !e.shiftKey) || key === 'ArrowRight')
|
|
50
|
+
return trailing && cur >= 0 ? { type: 'trailing' } : undefined;
|
|
51
|
+
if (key === 'Tab') return { type: 'close' };
|
|
52
|
+
if (key === 'Enter' || key === ' ') return cur >= 0 ? { type: 'select' } : { type: 'close' };
|
|
53
|
+
if (count <= 0) return undefined;
|
|
54
|
+
if (key === 'ArrowDown') return { type: 'move', index: cur < 0 ? 0 : Math.min(cur + 1, last) };
|
|
55
|
+
if (key === 'ArrowUp') return { type: 'move', index: cur < 0 ? last : Math.max(cur - 1, 0) };
|
|
56
|
+
if (key === 'Home') return { type: 'move', index: 0 };
|
|
57
|
+
if (key === 'End') return { type: 'move', index: last };
|
|
58
|
+
return printable(e) ? { type: 'type', char: key } : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** @param {{ key: string, altKey?: boolean }} e */
|
|
62
|
+
function printable(e) {
|
|
63
|
+
return e.key.length === 1 && !e.altKey && e.key !== ' ';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Index of the first label, searching forward from `from` and wrapping, that
|
|
68
|
+
* starts with `query` (case- and accent-insensitive). A query of one repeated
|
|
69
|
+
* character cycles through the labels starting with it, as native selects do.
|
|
70
|
+
*
|
|
71
|
+
* @param {readonly string[]} labels
|
|
72
|
+
* @param {string} query
|
|
73
|
+
* @param {number} from index of the current option, or -1
|
|
74
|
+
* @returns {number | undefined}
|
|
75
|
+
*/
|
|
76
|
+
export function typeaheadIndex(labels, query, from) {
|
|
77
|
+
const n = labels.length;
|
|
78
|
+
if (n === 0 || query === '') return undefined;
|
|
79
|
+
const fold = (/** @type {string} */ s) => s.normalize('NFD').replace(/\p{M}/gu, '').toLowerCase();
|
|
80
|
+
const q = fold(query);
|
|
81
|
+
const repeated = [...q].every((c) => c === q[0]);
|
|
82
|
+
const needle = repeated ? q[0] : q;
|
|
83
|
+
const start = repeated ? from + 1 : Math.max(from, 0);
|
|
84
|
+
for (let i = 0; i < n; i += 1) {
|
|
85
|
+
const at = (((start + i) % n) + n) % n;
|
|
86
|
+
if (fold(labels[at]).startsWith(needle)) return at;
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Accumulates typed characters into a query that resets after `timeout` ms of
|
|
93
|
+
* silence.
|
|
94
|
+
*
|
|
95
|
+
* @param {number} [timeout]
|
|
96
|
+
* @param {() => number} [now]
|
|
97
|
+
*/
|
|
98
|
+
export function createTypeahead(timeout = 500, now = () => Date.now()) {
|
|
99
|
+
let query = '';
|
|
100
|
+
let at = -Infinity;
|
|
101
|
+
return {
|
|
102
|
+
/** @param {string} char */
|
|
103
|
+
push(char) {
|
|
104
|
+
const t = now();
|
|
105
|
+
query = t - at > timeout ? char : query + char;
|
|
106
|
+
at = t;
|
|
107
|
+
return query;
|
|
108
|
+
},
|
|
109
|
+
reset() {
|
|
110
|
+
query = '';
|
|
111
|
+
at = -Infinity;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -64,6 +64,25 @@
|
|
|
64
64
|
if (stuck) pending = false;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// Keyboard focus at the scroll edge would cut its ring: bring it inside the scroll padding.
|
|
68
|
+
function onfocusin(e: FocusEvent) {
|
|
69
|
+
const el = scroller;
|
|
70
|
+
const target = e.target;
|
|
71
|
+
if (!el || !(target instanceof Element) || target === el) return;
|
|
72
|
+
try {
|
|
73
|
+
if (!target.matches(':focus-visible')) return;
|
|
74
|
+
} catch {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const pad = Number.parseFloat(getComputedStyle(el).scrollPaddingTop) || 0;
|
|
78
|
+
const box = el.getBoundingClientRect();
|
|
79
|
+
const r = target.getBoundingClientRect();
|
|
80
|
+
const top = box.top + el.clientTop;
|
|
81
|
+
const bottom = top + el.clientHeight;
|
|
82
|
+
if (r.top - pad < top) el.scrollTop -= top - (r.top - pad);
|
|
83
|
+
else if (r.bottom + pad > bottom) el.scrollTop += r.bottom + pad - bottom;
|
|
84
|
+
}
|
|
85
|
+
|
|
67
86
|
$effect.pre(() => {
|
|
68
87
|
const el = scroller;
|
|
69
88
|
const first = items.length ? key(items[0]) : undefined;
|
|
@@ -106,7 +125,7 @@
|
|
|
106
125
|
</script>
|
|
107
126
|
|
|
108
127
|
<div data-tsu="ConversationFeed" class="feed-wrap {klass}" style={styleProp}>
|
|
109
|
-
<div class="feed" bind:this={scroller} {onscroll}>
|
|
128
|
+
<div class="feed" bind:this={scroller} {onscroll} {onfocusin}>
|
|
110
129
|
{#if loadMore}
|
|
111
130
|
<LoadMore pill state={loadMore} onload={onloadmore} label={loadMoreLabel} />
|
|
112
131
|
{/if}
|
|
@@ -143,6 +162,10 @@
|
|
|
143
162
|
flex-direction: column;
|
|
144
163
|
gap: var(--sp-3);
|
|
145
164
|
padding: var(--sp-3);
|
|
165
|
+
scroll-padding-block: var(--sp-2);
|
|
166
|
+
}
|
|
167
|
+
.feed > * {
|
|
168
|
+
flex-shrink: 0;
|
|
146
169
|
}
|
|
147
170
|
.feed-items {
|
|
148
171
|
display: flex;
|
|
@@ -254,8 +254,13 @@
|
|
|
254
254
|
{:else if col.sortable}
|
|
255
255
|
<button type="button" class="dt-sort" onclick={() => toggleSort(col)}>
|
|
256
256
|
<span>{col.label}</span>
|
|
257
|
-
<span
|
|
258
|
-
|
|
257
|
+
<span
|
|
258
|
+
class="dt-arrow"
|
|
259
|
+
aria-hidden="true"
|
|
260
|
+
class:active={sortKey === col.key}
|
|
261
|
+
class:desc={sortKey === col.key && sortDir === 'desc'}
|
|
262
|
+
>
|
|
263
|
+
{sortKey === col.key ? '↑' : '↕'}
|
|
259
264
|
</span>
|
|
260
265
|
</button>
|
|
261
266
|
{:else}
|
|
@@ -425,31 +430,47 @@
|
|
|
425
430
|
color: inherit;
|
|
426
431
|
cursor: pointer;
|
|
427
432
|
}
|
|
428
|
-
.dt-sort:hover {
|
|
429
|
-
color: var(--text);
|
|
430
|
-
}
|
|
431
433
|
.dt-arrow {
|
|
434
|
+
display: inline-block;
|
|
432
435
|
opacity: 0.4;
|
|
433
436
|
font-size: 0.9em;
|
|
437
|
+
transition:
|
|
438
|
+
rotate var(--dur-base) var(--ease-out),
|
|
439
|
+
opacity var(--dur-fast) var(--ease);
|
|
434
440
|
}
|
|
435
441
|
.dt-arrow.active {
|
|
436
442
|
opacity: 1;
|
|
437
443
|
color: var(--accent);
|
|
438
444
|
}
|
|
445
|
+
.dt-arrow.desc {
|
|
446
|
+
rotate: 180deg;
|
|
447
|
+
}
|
|
439
448
|
tbody tr:last-child td {
|
|
440
449
|
border-bottom: none;
|
|
441
450
|
}
|
|
442
451
|
tr.clickable {
|
|
443
452
|
cursor: pointer;
|
|
444
|
-
transition: background
|
|
453
|
+
transition: background var(--dur-fast) var(--ease);
|
|
445
454
|
}
|
|
446
|
-
tr.clickable:hover,
|
|
447
455
|
tr.clickable:focus-visible {
|
|
448
456
|
background: var(--bg-elevated-2);
|
|
449
|
-
}
|
|
450
|
-
tr.clickable:focus-visible {
|
|
451
457
|
outline: var(--focus-ring);
|
|
452
|
-
outline-offset: -
|
|
458
|
+
outline-offset: var(--focus-ring-offset-inset);
|
|
459
|
+
}
|
|
460
|
+
@media (hover: hover) and (pointer: fine) {
|
|
461
|
+
.dt-sort:hover {
|
|
462
|
+
color: var(--text);
|
|
463
|
+
}
|
|
464
|
+
tr.clickable:hover {
|
|
465
|
+
background: var(--bg-elevated-2);
|
|
466
|
+
transition-duration: var(--dur-instant);
|
|
467
|
+
}
|
|
468
|
+
tr:hover .dt-actions {
|
|
469
|
+
opacity: 1;
|
|
470
|
+
}
|
|
471
|
+
.dt-more-btn:hover {
|
|
472
|
+
background: var(--bg-elevated-2);
|
|
473
|
+
}
|
|
453
474
|
}
|
|
454
475
|
td.truncate {
|
|
455
476
|
white-space: nowrap;
|
|
@@ -487,7 +508,7 @@
|
|
|
487
508
|
}
|
|
488
509
|
.dt-actions {
|
|
489
510
|
opacity: 0;
|
|
490
|
-
transition: opacity
|
|
511
|
+
transition: opacity var(--dur-fast) var(--ease);
|
|
491
512
|
}
|
|
492
513
|
.dt-actions-inner {
|
|
493
514
|
display: inline-flex;
|
|
@@ -495,7 +516,6 @@
|
|
|
495
516
|
gap: var(--sp-1);
|
|
496
517
|
white-space: nowrap;
|
|
497
518
|
}
|
|
498
|
-
tr:hover .dt-actions,
|
|
499
519
|
tr:focus-within .dt-actions {
|
|
500
520
|
opacity: 1;
|
|
501
521
|
}
|
|
@@ -503,6 +523,10 @@
|
|
|
503
523
|
.dt-actions {
|
|
504
524
|
opacity: 1;
|
|
505
525
|
}
|
|
526
|
+
/* The last row's checkbox slab would otherwise add a scroll range. */
|
|
527
|
+
tbody tr:last-child td :global(.checkbox::before) {
|
|
528
|
+
inset-block-end: 0;
|
|
529
|
+
}
|
|
506
530
|
/* Sort slab: inline-end stays at 0 so it never reaches the next header
|
|
507
531
|
cell, and the inline-start reach stays inside the cell's own padding. */
|
|
508
532
|
.dt-sort {
|
|
@@ -664,9 +688,8 @@
|
|
|
664
688
|
font: inherit;
|
|
665
689
|
color: var(--accent);
|
|
666
690
|
cursor: pointer;
|
|
667
|
-
border-radius: var(--r-md
|
|
691
|
+
border-radius: var(--r-md);
|
|
668
692
|
}
|
|
669
|
-
.dt-more-btn:hover,
|
|
670
693
|
.dt-more-btn:focus-visible {
|
|
671
694
|
background: var(--bg-elevated-2);
|
|
672
695
|
}
|
package/dist/floating.js
CHANGED
|
@@ -56,7 +56,33 @@ export function placeAt(t, floating, placement = 'bottom-start', gap = 6) {
|
|
|
56
56
|
left = clamp(left, gap, vw - f.width - gap);
|
|
57
57
|
floating.style.top = `${Math.round(top)}px`;
|
|
58
58
|
floating.style.left = `${Math.round(left)}px`;
|
|
59
|
+
const resolved = side === 'top' || side === 'bottom'
|
|
60
|
+
? top + f.height / 2 < t.top + t.height / 2
|
|
61
|
+
? 'top'
|
|
62
|
+
: 'bottom'
|
|
63
|
+
: left + f.width / 2 < t.left + t.width / 2
|
|
64
|
+
? 'left'
|
|
65
|
+
: 'right';
|
|
66
|
+
const cx = Math.round(clamp(t.left + t.width / 2 - left, 0, f.width));
|
|
67
|
+
const cy = Math.round(clamp(t.top + t.height / 2 - top, 0, f.height));
|
|
68
|
+
floating.dataset.side = resolved;
|
|
69
|
+
floating.style.setProperty('--tsu-origin', ORIGIN[resolved](cx, cy));
|
|
70
|
+
floating.style.setProperty('--tsu-shift', SHIFT[resolved]);
|
|
59
71
|
}
|
|
72
|
+
// Enter motion grows out of the trigger: the origin sits on the trigger-facing
|
|
73
|
+
// edge, level with the trigger centre, and the panel starts shifted towards it.
|
|
74
|
+
const ORIGIN = {
|
|
75
|
+
top: (cx) => `${cx}px 100%`,
|
|
76
|
+
bottom: (cx) => `${cx}px 0`,
|
|
77
|
+
left: (_cx, cy) => `100% ${cy}px`,
|
|
78
|
+
right: (_cx, cy) => `0 ${cy}px`,
|
|
79
|
+
};
|
|
80
|
+
const SHIFT = {
|
|
81
|
+
top: '0 var(--shift-sm)',
|
|
82
|
+
bottom: '0 calc(-1 * var(--shift-sm))',
|
|
83
|
+
left: 'var(--shift-sm) 0',
|
|
84
|
+
right: 'calc(-1 * var(--shift-sm)) 0',
|
|
85
|
+
};
|
|
60
86
|
/** Angles (degrees) of `count` petals spread evenly from `from` to `to`. */
|
|
61
87
|
export function fanAngles(count, from, to) {
|
|
62
88
|
if (count <= 1)
|
package/dist/index.d.ts
CHANGED
|
@@ -85,11 +85,13 @@ export { default as Prose } from './components/molecules/Prose.svelte';
|
|
|
85
85
|
export { default as PullToRefresh } from './components/molecules/PullToRefresh.svelte';
|
|
86
86
|
export { default as RadialMenu, type RadialArc, } from './components/molecules/RadialMenu.svelte';
|
|
87
87
|
export { default as RadioGroup, type RadioOption, } from './components/molecules/RadioGroup.svelte';
|
|
88
|
+
export { default as RichSelect, type RichSelectOptionContext, } from './components/molecules/RichSelect.svelte';
|
|
88
89
|
export { default as SectionHeader } from './components/molecules/SectionHeader.svelte';
|
|
89
90
|
export { default as SeekSlider } from './components/molecules/SeekSlider.svelte';
|
|
90
91
|
export { default as SegmentedControl, type SegmentOption, } from './components/molecules/SegmentedControl.svelte';
|
|
91
92
|
export { default as SelectButton } from './components/molecules/SelectButton.svelte';
|
|
92
93
|
export { default as SortableList, SORTABLE_LIST_MESSAGES, type SortableListMessages, type SortableListMove, type SortableListRowContext, } from './components/molecules/SortableList.svelte';
|
|
94
|
+
export { default as SoundSettings } from './components/molecules/SoundSettings.svelte';
|
|
93
95
|
export { default as SplitButton } from './components/molecules/SplitButton.svelte';
|
|
94
96
|
export { default as StatusBar } from './components/molecules/StatusBar.svelte';
|
|
95
97
|
export { default as SwipeRow, type SwipeAction } from './components/molecules/SwipeRow.svelte';
|
package/dist/index.js
CHANGED
|
@@ -92,11 +92,13 @@ export { default as Prose } from './components/molecules/Prose.svelte';
|
|
|
92
92
|
export { default as PullToRefresh } from './components/molecules/PullToRefresh.svelte';
|
|
93
93
|
export { default as RadialMenu, } from './components/molecules/RadialMenu.svelte';
|
|
94
94
|
export { default as RadioGroup, } from './components/molecules/RadioGroup.svelte';
|
|
95
|
+
export { default as RichSelect, } from './components/molecules/RichSelect.svelte';
|
|
95
96
|
export { default as SectionHeader } from './components/molecules/SectionHeader.svelte';
|
|
96
97
|
export { default as SeekSlider } from './components/molecules/SeekSlider.svelte';
|
|
97
98
|
export { default as SegmentedControl, } from './components/molecules/SegmentedControl.svelte';
|
|
98
99
|
export { default as SelectButton } from './components/molecules/SelectButton.svelte';
|
|
99
100
|
export { default as SortableList, SORTABLE_LIST_MESSAGES, } from './components/molecules/SortableList.svelte';
|
|
101
|
+
export { default as SoundSettings } from './components/molecules/SoundSettings.svelte';
|
|
100
102
|
export { default as SplitButton } from './components/molecules/SplitButton.svelte';
|
|
101
103
|
export { default as StatusBar } from './components/molecules/StatusBar.svelte';
|
|
102
104
|
export { default as SwipeRow } from './components/molecules/SwipeRow.svelte';
|
package/dist/sound-synth.js
CHANGED
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
const PALETTE = {
|
|
2
2
|
click: [
|
|
3
|
-
['n',
|
|
4
|
-
['sine', 620, 380, 0, 0.04, 0.
|
|
3
|
+
['n', 3000, 0, 0, 0.012, 0.18],
|
|
4
|
+
['sine', 620, 380, 0, 0.04, 0.22],
|
|
5
5
|
],
|
|
6
6
|
'toggle-on': [
|
|
7
|
-
['n',
|
|
8
|
-
['
|
|
7
|
+
['n', 2600, 0, 0, 0.012, 0.3],
|
|
8
|
+
['sine', 520, 520, 0, 0.03, 0.14],
|
|
9
|
+
['triangle', 660, 880, 0.035, 0.05, 0.3],
|
|
9
10
|
],
|
|
10
11
|
'toggle-off': [
|
|
11
|
-
['n',
|
|
12
|
-
['
|
|
12
|
+
['n', 2600, 0, 0, 0.012, 0.3],
|
|
13
|
+
['sine', 520, 520, 0, 0.03, 0.14],
|
|
14
|
+
['triangle', 880, 620, 0.035, 0.05, 0.3],
|
|
13
15
|
],
|
|
14
16
|
open: [['sine', 420, 760, 0, 0.09, 0.25]],
|
|
15
17
|
close: [['sine', 700, 380, 0, 0.08, 0.25]],
|
|
16
18
|
success: [
|
|
17
|
-
['sine',
|
|
18
|
-
['sine',
|
|
19
|
+
['sine', 698, 698, 0, 0.055, 0.25],
|
|
20
|
+
['sine', 1047, 1047, 0.058, 0.085, 0.2],
|
|
19
21
|
],
|
|
20
22
|
error: [
|
|
21
|
-
['triangle', 330, 300, 0, 0.
|
|
22
|
-
['triangle', 262, 240, 0.
|
|
23
|
+
['triangle', 330, 300, 0, 0.065, 0.28],
|
|
24
|
+
['triangle', 262, 240, 0.06, 0.08, 0.28],
|
|
25
|
+
],
|
|
26
|
+
tick: [
|
|
27
|
+
['n', 3800, 0, 0, 0.008, 0.3],
|
|
28
|
+
['sine', 2400, 1900, 0, 0.012, 0.14],
|
|
23
29
|
],
|
|
24
|
-
tick: [['n', 5200, 0, 0, 0.006, 0.25]],
|
|
25
30
|
};
|
|
26
31
|
let ctx;
|
|
27
32
|
let out;
|
|
@@ -51,7 +56,7 @@ export function play(name, volume) {
|
|
|
51
56
|
const env = c.createGain();
|
|
52
57
|
const s = t + at;
|
|
53
58
|
env.gain.setValueAtTime(0.0001, s);
|
|
54
|
-
env.gain.exponentialRampToValueAtTime(g, s + 0.
|
|
59
|
+
env.gain.exponentialRampToValueAtTime(g, s + 0.004);
|
|
55
60
|
env.gain.exponentialRampToValueAtTime(0.0001, s + d);
|
|
56
61
|
env.connect(out);
|
|
57
62
|
let src;
|
|
@@ -13,6 +13,8 @@ export interface Toast {
|
|
|
13
13
|
action?: ToastAction;
|
|
14
14
|
/** True while `action.run` is in flight; the Toaster shows the button loading. */
|
|
15
15
|
pending: boolean;
|
|
16
|
+
/** Dismissed: the Toaster plays the exit while the store holds it for `TOAST_EXIT_MS`. */
|
|
17
|
+
leaving: boolean;
|
|
16
18
|
}
|
|
17
19
|
export interface ToastOptions {
|
|
18
20
|
tone?: ToastToneInput;
|
|
@@ -22,14 +24,21 @@ export interface ToastOptions {
|
|
|
22
24
|
export declare const TOAST_MS = 4000;
|
|
23
25
|
/** Toasts with an action linger longer: the action is only reachable while visible. */
|
|
24
26
|
export declare const ACTION_TOAST_MS = 7000;
|
|
27
|
+
/** Matches `--dur-exit-slow`; the removal never waits on `transitionend` alone. */
|
|
28
|
+
export declare const TOAST_EXIT_MS = 200;
|
|
25
29
|
declare class Toasts {
|
|
26
30
|
#private;
|
|
27
31
|
items: Toast[];
|
|
28
32
|
show(message: string, opts?: ToastOptions): number;
|
|
33
|
+
/** Hold the auto-dismiss (pointer over the toast); `resume` restarts the remaining time. */
|
|
34
|
+
pause(id: number): void;
|
|
35
|
+
resume(id: number): void;
|
|
29
36
|
ok(message: string, duration?: number, action?: ToastAction): number;
|
|
30
37
|
error(message: string, duration?: number, action?: ToastAction): number;
|
|
31
38
|
info(message: string, duration?: number, action?: ToastAction): number;
|
|
32
39
|
dismiss(id: number): void;
|
|
40
|
+
/** Drop a toast at once, skipping the exit. */
|
|
41
|
+
remove(id: number): void;
|
|
33
42
|
/** Run a toast's action once: the toast stays (button loading) until run settles, then dismisses. */
|
|
34
43
|
act(id: number): Promise<void>;
|
|
35
44
|
}
|
|
@@ -5,22 +5,54 @@
|
|
|
5
5
|
export const TOAST_MS = 4000;
|
|
6
6
|
/** Toasts with an action linger longer: the action is only reachable while visible. */
|
|
7
7
|
export const ACTION_TOAST_MS = 7000;
|
|
8
|
+
/** Matches `--dur-exit-slow`; the removal never waits on `transitionend` alone. */
|
|
9
|
+
export const TOAST_EXIT_MS = 200;
|
|
8
10
|
let seq = 0;
|
|
9
11
|
class Toasts {
|
|
10
12
|
items = $state([]);
|
|
11
13
|
#timers = new Map();
|
|
14
|
+
#due = new Map();
|
|
15
|
+
#paused = new Map();
|
|
16
|
+
#arm(id, ms) {
|
|
17
|
+
this.#due.set(id, Date.now() + ms);
|
|
18
|
+
this.#timers.set(id, setTimeout(() => this.dismiss(id), ms));
|
|
19
|
+
}
|
|
20
|
+
#disarm(id) {
|
|
21
|
+
const timer = this.#timers.get(id);
|
|
22
|
+
if (timer)
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
this.#timers.delete(id);
|
|
25
|
+
this.#due.delete(id);
|
|
26
|
+
this.#paused.delete(id);
|
|
27
|
+
}
|
|
12
28
|
show(message, opts = {}) {
|
|
13
29
|
const id = ++seq;
|
|
14
30
|
const duration = opts.duration ?? (opts.action ? ACTION_TOAST_MS : TOAST_MS);
|
|
15
31
|
const tone = opts.tone === 'danger' ? 'error' : (opts.tone ?? 'neutral');
|
|
16
32
|
this.items = [
|
|
17
33
|
...this.items,
|
|
18
|
-
{ id, message, tone, duration, action: opts.action, pending: false },
|
|
34
|
+
{ id, message, tone, duration, action: opts.action, pending: false, leaving: false },
|
|
19
35
|
];
|
|
20
36
|
if (duration > 0)
|
|
21
|
-
this.#
|
|
37
|
+
this.#arm(id, duration);
|
|
22
38
|
return id;
|
|
23
39
|
}
|
|
40
|
+
/** Hold the auto-dismiss (pointer over the toast); `resume` restarts the remaining time. */
|
|
41
|
+
pause(id) {
|
|
42
|
+
const due = this.#due.get(id);
|
|
43
|
+
if (due === undefined)
|
|
44
|
+
return;
|
|
45
|
+
const left = Math.max(0, due - Date.now());
|
|
46
|
+
this.#disarm(id);
|
|
47
|
+
this.#paused.set(id, left);
|
|
48
|
+
}
|
|
49
|
+
resume(id) {
|
|
50
|
+
const left = this.#paused.get(id);
|
|
51
|
+
this.#paused.delete(id);
|
|
52
|
+
if (left === undefined || !this.items.some((t) => t.id === id && !t.leaving))
|
|
53
|
+
return;
|
|
54
|
+
this.#arm(id, left);
|
|
55
|
+
}
|
|
24
56
|
ok(message, duration, action) {
|
|
25
57
|
return this.show(message, { tone: 'ok', duration, action });
|
|
26
58
|
}
|
|
@@ -31,10 +63,16 @@ class Toasts {
|
|
|
31
63
|
return this.show(message, { tone: 'info', duration, action });
|
|
32
64
|
}
|
|
33
65
|
dismiss(id) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
66
|
+
this.#disarm(id);
|
|
67
|
+
const t = this.items.find((x) => x.id === id);
|
|
68
|
+
if (!t || t.leaving)
|
|
69
|
+
return;
|
|
70
|
+
this.items = this.items.map((x) => (x.id === id ? { ...x, leaving: true } : x));
|
|
71
|
+
setTimeout(() => this.remove(id), TOAST_EXIT_MS);
|
|
72
|
+
}
|
|
73
|
+
/** Drop a toast at once, skipping the exit. */
|
|
74
|
+
remove(id) {
|
|
75
|
+
this.#disarm(id);
|
|
38
76
|
this.items = this.items.filter((t) => t.id !== id);
|
|
39
77
|
}
|
|
40
78
|
/** Run a toast's action once: the toast stays (button loading) until run settles, then dismisses. */
|
|
@@ -42,9 +80,7 @@ class Toasts {
|
|
|
42
80
|
const t = this.items.find((x) => x.id === id);
|
|
43
81
|
if (!t?.action || t.pending)
|
|
44
82
|
return;
|
|
45
|
-
|
|
46
|
-
if (timer)
|
|
47
|
-
clearTimeout(timer);
|
|
83
|
+
this.#disarm(id);
|
|
48
84
|
this.items = this.items.map((x) => (x.id === id ? { ...x, pending: true } : x));
|
|
49
85
|
try {
|
|
50
86
|
await t.action.run();
|
package/dist/styles/reset.css
CHANGED
|
@@ -51,9 +51,6 @@ a {
|
|
|
51
51
|
color: var(--link);
|
|
52
52
|
text-decoration: none;
|
|
53
53
|
}
|
|
54
|
-
a:hover {
|
|
55
|
-
text-decoration: underline;
|
|
56
|
-
}
|
|
57
54
|
|
|
58
55
|
button {
|
|
59
56
|
font: inherit;
|
|
@@ -96,6 +93,9 @@ pre {
|
|
|
96
93
|
/* Slim scrollbars for mice only. On touch, a custom scrollbar replaces the
|
|
97
94
|
native overlay bars with classic space-taking ones. */
|
|
98
95
|
@media (hover: hover) and (pointer: fine) {
|
|
96
|
+
a:hover {
|
|
97
|
+
text-decoration: underline;
|
|
98
|
+
}
|
|
99
99
|
::-webkit-scrollbar {
|
|
100
100
|
width: 8px;
|
|
101
101
|
height: 8px;
|
package/package.json
CHANGED