@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.
Files changed (132) hide show
  1. package/README.md +38 -1
  2. package/dist/autoresize.d.ts +2 -0
  3. package/dist/autoresize.js +8 -1
  4. package/dist/components/atoms/Artwork.svelte +12 -5
  5. package/dist/components/atoms/Avatar.svelte +1 -1
  6. package/dist/components/atoms/Badge.svelte +59 -23
  7. package/dist/components/atoms/Button.svelte +130 -96
  8. package/dist/components/atoms/Button.svelte.d.ts +2 -0
  9. package/dist/components/atoms/Card.svelte +10 -4
  10. package/dist/components/atoms/Checkbox.svelte +36 -9
  11. package/dist/components/atoms/Checkbox.svelte.d.ts +2 -0
  12. package/dist/components/atoms/Gauge.svelte +20 -14
  13. package/dist/components/atoms/Heading.svelte +1 -1
  14. package/dist/components/atoms/Icon.svelte +1 -0
  15. package/dist/components/atoms/Input.svelte +67 -13
  16. package/dist/components/atoms/Kbd.svelte +3 -3
  17. package/dist/components/atoms/Link.svelte +7 -2
  18. package/dist/components/atoms/Progress.svelte +4 -2
  19. package/dist/components/atoms/Scrim.svelte +3 -8
  20. package/dist/components/atoms/SegmentedProgress.svelte +5 -4
  21. package/dist/components/atoms/Select.svelte +58 -27
  22. package/dist/components/atoms/Slider.svelte +79 -22
  23. package/dist/components/atoms/Slider.svelte.d.ts +2 -0
  24. package/dist/components/atoms/Swatch.svelte +19 -8
  25. package/dist/components/atoms/Switch.svelte +34 -21
  26. package/dist/components/atoms/Switch.svelte.d.ts +2 -0
  27. package/dist/components/atoms/Text.svelte +1 -1
  28. package/dist/components/atoms/Textarea.svelte +34 -12
  29. package/dist/components/atoms/TimeDisplay.svelte +4 -2
  30. package/dist/components/layouts/AppShell.svelte +26 -15
  31. package/dist/components/layouts/MasterDetail.svelte +23 -21
  32. package/dist/components/layouts/NavBar.svelte +11 -8
  33. package/dist/components/layouts/NavItem.svelte +15 -7
  34. package/dist/components/layouts/NavSection.svelte +1 -1
  35. package/dist/components/layouts/ResizablePanel.svelte +37 -16
  36. package/dist/components/layouts/TabBar.svelte +24 -14
  37. package/dist/components/layouts/TabBar.svelte.d.ts +2 -0
  38. package/dist/components/molecules/Accordion.svelte +4 -0
  39. package/dist/components/molecules/Accordion.svelte.d.ts +2 -0
  40. package/dist/components/molecules/AttachmentList.svelte +14 -11
  41. package/dist/components/molecules/Breadcrumb.svelte +13 -9
  42. package/dist/components/molecules/Callout.svelte +3 -3
  43. package/dist/components/molecules/CapBar.svelte +23 -22
  44. package/dist/components/molecules/Carousel.svelte +27 -17
  45. package/dist/components/molecules/ChatBubble.svelte +2 -2
  46. package/dist/components/molecules/CodeBlock.svelte +2 -2
  47. package/dist/components/molecules/Combobox.svelte +30 -1
  48. package/dist/components/molecules/Combobox.svelte.d.ts +4 -0
  49. package/dist/components/molecules/Composer.svelte +2 -2
  50. package/dist/components/molecules/ConfirmModal.svelte +4 -0
  51. package/dist/components/molecules/ConfirmModal.svelte.d.ts +2 -0
  52. package/dist/components/molecules/CopyButton.svelte +46 -2
  53. package/dist/components/molecules/CopyButton.svelte.d.ts +2 -0
  54. package/dist/components/molecules/Disclosure.svelte +23 -12
  55. package/dist/components/molecules/Disclosure.svelte.d.ts +2 -0
  56. package/dist/components/molecules/Drawer.svelte +43 -23
  57. package/dist/components/molecules/Drawer.svelte.d.ts +2 -0
  58. package/dist/components/molecules/Dropzone.svelte +24 -8
  59. package/dist/components/molecules/EmojiPicker.svelte +27 -8
  60. package/dist/components/molecules/EmojiPicker.svelte.d.ts +2 -0
  61. package/dist/components/molecules/EmptyState.svelte +5 -5
  62. package/dist/components/molecules/Fieldset.svelte +8 -13
  63. package/dist/components/molecules/FileButton.svelte +57 -31
  64. package/dist/components/molecules/FileButton.svelte.d.ts +2 -0
  65. package/dist/components/molecules/FilterInput.svelte +51 -11
  66. package/dist/components/molecules/FontScalePicker.svelte +20 -9
  67. package/dist/components/molecules/GitRef.svelte +9 -7
  68. package/dist/components/molecules/GlyphStack.svelte +46 -14
  69. package/dist/components/molecules/IconButton.svelte +6 -3
  70. package/dist/components/molecules/IconButton.svelte.d.ts +2 -0
  71. package/dist/components/molecules/InputGroup.svelte +13 -12
  72. package/dist/components/molecules/LoadMore.svelte +6 -1
  73. package/dist/components/molecules/LoadMore.svelte.d.ts +2 -0
  74. package/dist/components/molecules/Menu.svelte +33 -7
  75. package/dist/components/molecules/Menu.svelte.d.ts +2 -0
  76. package/dist/components/molecules/Metric.svelte +11 -9
  77. package/dist/components/molecules/Modal.svelte +76 -26
  78. package/dist/components/molecules/Modal.svelte.d.ts +2 -0
  79. package/dist/components/molecules/OptionButton.svelte +28 -10
  80. package/dist/components/molecules/OptionButton.svelte.d.ts +2 -0
  81. package/dist/components/molecules/Pagination.svelte +26 -9
  82. package/dist/components/molecules/Pagination.svelte.d.ts +2 -0
  83. package/dist/components/molecules/Popover.svelte +107 -51
  84. package/dist/components/molecules/Popover.svelte.d.ts +2 -0
  85. package/dist/components/molecules/Prose.svelte +1 -1
  86. package/dist/components/molecules/PullToRefresh.svelte +14 -10
  87. package/dist/components/molecules/PullToRefresh.svelte.d.ts +2 -0
  88. package/dist/components/molecules/RadialMenu.svelte +52 -19
  89. package/dist/components/molecules/RadialMenu.svelte.d.ts +2 -0
  90. package/dist/components/molecules/RadioGroup.svelte +38 -23
  91. package/dist/components/molecules/RadioGroup.svelte.d.ts +2 -0
  92. package/dist/components/molecules/RichSelect.svelte +583 -0
  93. package/dist/components/molecules/RichSelect.svelte.d.ts +68 -0
  94. package/dist/components/molecules/SectionHeader.svelte +13 -8
  95. package/dist/components/molecules/SeekSlider.svelte +38 -27
  96. package/dist/components/molecules/SeekSlider.svelte.d.ts +2 -0
  97. package/dist/components/molecules/SegmentedControl.svelte +139 -86
  98. package/dist/components/molecules/SegmentedControl.svelte.d.ts +2 -0
  99. package/dist/components/molecules/SelectButton.svelte +21 -6
  100. package/dist/components/molecules/SelectButton.svelte.d.ts +2 -0
  101. package/dist/components/molecules/SortableList.svelte +10 -11
  102. package/dist/components/molecules/SoundSettings.svelte +161 -0
  103. package/dist/components/molecules/SoundSettings.svelte.d.ts +17 -0
  104. package/dist/components/molecules/SplitButton.svelte +30 -3
  105. package/dist/components/molecules/SplitButton.svelte.d.ts +2 -0
  106. package/dist/components/molecules/SwipeRow.svelte +18 -15
  107. package/dist/components/molecules/SwipeRow.svelte.d.ts +2 -0
  108. package/dist/components/molecules/Tabs.svelte +92 -10
  109. package/dist/components/molecules/Tabs.svelte.d.ts +2 -0
  110. package/dist/components/molecules/ThemePicker.svelte +25 -10
  111. package/dist/components/molecules/Timestamp.svelte +12 -7
  112. package/dist/components/molecules/Toaster.svelte +65 -14
  113. package/dist/components/molecules/Toaster.svelte.d.ts +2 -0
  114. package/dist/components/molecules/Toggle.svelte +36 -13
  115. package/dist/components/molecules/Toggle.svelte.d.ts +2 -0
  116. package/dist/components/molecules/Tooltip.svelte +41 -10
  117. package/dist/components/molecules/Tooltip.svelte.d.ts +1 -1
  118. package/dist/components/molecules/VolumeSlider.svelte +34 -14
  119. package/dist/components/molecules/VolumeSlider.svelte.d.ts +2 -0
  120. package/dist/components/molecules/WorkingDir.svelte +1 -0
  121. package/dist/components/molecules/rich-select-keyboard.d.ts +82 -0
  122. package/dist/components/molecules/rich-select-keyboard.js +114 -0
  123. package/dist/components/organisms/ConversationFeed.svelte +24 -1
  124. package/dist/components/organisms/DataTable.svelte +37 -14
  125. package/dist/floating.js +26 -0
  126. package/dist/index.d.ts +2 -0
  127. package/dist/index.js +2 -0
  128. package/dist/sound-synth.js +17 -12
  129. package/dist/stores/toast.svelte.d.ts +9 -0
  130. package/dist/stores/toast.svelte.js +45 -9
  131. package/dist/styles/reset.css +3 -3
  132. 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 class="dt-arrow" aria-hidden="true" class:active={sortKey === col.key}>
258
- {sortKey === col.key ? (sortDir === 'asc' ? '↑' : '↓') : '↕'}
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 0.12s var(--ease);
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: -2px;
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 0.12s var(--ease);
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, 6px);
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';
@@ -1,27 +1,32 @@
1
1
  const PALETTE = {
2
2
  click: [
3
- ['n', 3200, 0, 0, 0.012, 0.25],
4
- ['sine', 620, 380, 0, 0.04, 0.18],
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', 3000, 0, 0, 0.008, 0.3],
8
- ['triangle', 660, 880, 0.045, 0.05, 0.3],
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', 3000, 0, 0, 0.008, 0.3],
12
- ['triangle', 880, 620, 0.045, 0.05, 0.3],
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', 784, 784, 0, 0.07, 0.25],
18
- ['sine', 1175, 1175, 0.06, 0.1, 0.22],
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.07, 0.28],
22
- ['triangle', 262, 240, 0.075, 0.1, 0.28],
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.003);
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.#timers.set(id, setTimeout(() => this.dismiss(id), duration));
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
- const timer = this.#timers.get(id);
35
- if (timer)
36
- clearTimeout(timer);
37
- this.#timers.delete(id);
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
- const timer = this.#timers.get(id);
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();
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dorsk/tsumikit",
3
- "version": "0.74.0",
3
+ "version": "0.76.0",
4
4
  "description": "Minimal, dependency-free Svelte 5 + pure-CSS UI kit. Token-driven atoms, molecules & layouts with theming out of the box.",
5
5
  "type": "module",
6
6
  "license": "MIT",