@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
package/README.md CHANGED
@@ -305,7 +305,7 @@ controlled mode, `chevron` start/end/false, `size="compact"` for strip rows, nat
305
305
  leading/trailing controls fused into one bordered unit, see below),
306
306
  Dropzone, CodeBlock, Callout, EmptyState, ConfirmModal, Pagination, Toaster,
307
307
  ThemePicker (popover grid of 2×2 palette swatches: bg · surface · text · accent per theme),
308
- EmojiPicker (popover with a searchable EN+FR glyph catalogue, grouped tabs, roving-tabindex grid; pass `groups` to swap the catalogue), FontScalePicker (popover with a stepped slider across the five text sizes), SectionHeader, KeyValue, LoadMore,
308
+ EmojiPicker (popover with a searchable EN+FR glyph catalogue, grouped tabs, roving-tabindex grid; pass `groups` to swap the catalogue), FontScalePicker (popover with a stepped slider across the five text sizes), SoundSettings (popover with the sound switch, volume and preview; see [Sound](#sound-opt-in)), SectionHeader, KeyValue, LoadMore,
309
309
  GitRef (branch chip + PR link tinted by state + `+N −N` diff; `collapse`
310
310
  auto/never/glyph degrades to icons inside a narrow `.cq` container),
311
311
  CapBar (consumption track with a draggable, keyboard-steppable cap handle that
@@ -1043,6 +1043,43 @@ wider than its container switches to the `scroll` layout until it fits again
1043
1043
  forks `variables.css` must keep them (Button `control`, Select `sm`, every `box`)
1044
1044
  or those controls render with unresolved sizes.
1045
1045
 
1046
+ ## Sound (opt-in)
1047
+
1048
+ UI sound effects are **off by default** and nothing is loaded while they stay off:
1049
+ no audio code, no `AudioContext`, no listeners. The user opts in with
1050
+ `<SoundSettings />` (a speaker popover with an on/off switch, a volume slider and a
1051
+ preview button; `size="sm"` for toolbars) or your own UI bound to the `sound` store.
1052
+ The choice persists in `localStorage` under `tsumikit-sound`.
1053
+
1054
+ ```svelte
1055
+ <script>
1056
+ import { Button, SoundSettings, sound } from '@dorsk/tsumikit';
1057
+ </script>
1058
+
1059
+ <SoundSettings size="sm" />
1060
+ <Button sound={false}>Silent button</Button>
1061
+ <Button onclick={() => sound.play('success')}>Play success</Button>
1062
+ ```
1063
+
1064
+ - **Palette**: `click` (buttons, selections, menu items), `toggle-on` / `toggle-off`
1065
+ (Switch, Checkbox, Toggle), `open` / `close` (Modal, Drawer, Popover, Menu,
1066
+ Accordion…), `success` / `error` (ok and error toasts, CopyButton), `tick` (slider
1067
+ steps, pull-to-refresh threshold). Every sound is a short soft synth tone, under
1068
+ 120 ms for interactions and under 160 ms for `success` / `error`; nothing is hover
1069
+ driven.
1070
+ - **Per instance**: every hooked component takes `sound={false}` to stay silent.
1071
+ - **Your events**: `sound.play('success' | 'error' | …)` goes through the same gates
1072
+ (enabled, volume, hidden tab, user activation), so an app can fire feedback on its
1073
+ own saves and failures.
1074
+ - **Reduced motion**: with `prefers-reduced-motion: reduce` only `success` and
1075
+ `error` play; decorative sounds are muted. `sound.muted` tells why nothing plays
1076
+ (`'off' | 'reduced-motion' | null`) and `SoundSettings` shows it as a hint.
1077
+ - **Weight**: the synth is a separate lazy chunk of about 0.7 KB gzipped, imported
1078
+ on the first play (or when the setting is turned on, so a gesture can unlock audio
1079
+ on Safari). What components import is a ~0.3 KB gate plus the store.
1080
+ - Calls within one frame coalesce (the highest priority wins: error > success >
1081
+ open/close > toggle > click > tick) and the same sound plays at most once per 50 ms.
1082
+
1046
1083
  ## Built on the platform
1047
1084
 
1048
1085
  Interactive components lean on modern web features rather than reimplementing
@@ -9,6 +9,8 @@
9
9
  export declare function rowsHeight(node: HTMLTextAreaElement, rows: number): number;
10
10
  /** The `rows` attribute as a height floor; a single row has none. */
11
11
  export declare function rowsFloor(node: HTMLTextAreaElement): number;
12
+ /** Vertical border a border-box `height` must cover on top of scrollHeight. */
13
+ export declare function borderBlock(node: HTMLTextAreaElement): number;
12
14
  export declare function autoresize(node: HTMLTextAreaElement, _value?: string): {
13
15
  update(): void;
14
16
  destroy(): void;
@@ -21,6 +21,13 @@ export function rowsFloor(node) {
21
21
  const rows = node.rows || 0;
22
22
  return rows <= 1 ? 0 : rowsHeight(node, rows);
23
23
  }
24
+ /** Vertical border a border-box `height` must cover on top of scrollHeight. */
25
+ export function borderBlock(node) {
26
+ const cs = getComputedStyle(node);
27
+ if (cs.boxSizing !== 'border-box')
28
+ return 0;
29
+ return (parseFloat(cs.borderTopWidth) || 0) + (parseFloat(cs.borderBottomWidth) || 0);
30
+ }
24
31
  export function autoresize(node, _value) {
25
32
  const resize = () => {
26
33
  // A manual drag handle may set `min-height` as a user-chosen floor; grow
@@ -28,7 +35,7 @@ export function autoresize(node, _value) {
28
35
  // bound, so dragging shorter than the text can't shrink past it.
29
36
  const floor = Math.max(parseFloat(node.style.minHeight) || 0, rowsFloor(node));
30
37
  node.style.height = 'auto';
31
- node.style.height = `${Math.max(node.scrollHeight, floor)}px`;
38
+ node.style.height = `${Math.max(node.scrollHeight + borderBlock(node), floor)}px`;
32
39
  };
33
40
  resize();
34
41
  node.addEventListener('input', resize);
@@ -107,8 +107,9 @@
107
107
  color: var(--text-muted);
108
108
  container-type: inline-size;
109
109
  transition:
110
- border-color 0.12s var(--ease),
111
- box-shadow 0.12s var(--ease);
110
+ border-color var(--dur-fast) var(--ease),
111
+ box-shadow var(--dur-fast) var(--ease),
112
+ scale var(--dur-fast) var(--ease-out);
112
113
  }
113
114
  .r-sm {
114
115
  border-radius: var(--r-sm);
@@ -122,9 +123,15 @@
122
123
  .r-pill {
123
124
  border-radius: var(--r-pill);
124
125
  }
125
- .hover:hover {
126
- border-color: var(--border-strong);
127
- box-shadow: var(--shadow-md);
126
+ @media (hover: hover) and (pointer: fine) {
127
+ .hover:hover {
128
+ border-color: var(--border-strong);
129
+ box-shadow: var(--shadow-md);
130
+ }
131
+ }
132
+ .hover:active {
133
+ scale: var(--press-scale-lg);
134
+ transition-duration: var(--dur-press);
128
135
  }
129
136
  .img {
130
137
  position: absolute;
@@ -80,7 +80,7 @@
80
80
  border-radius: var(--r-pill);
81
81
  font-weight: var(--fw-semibold);
82
82
  font-size: calc(var(--avatar-size) * 0.46);
83
- line-height: 1;
83
+ line-height: var(--lh-none);
84
84
  background: var(--avatar-bg, var(--bg-elevated-2));
85
85
  color: var(--avatar-fg, var(--text-muted));
86
86
  user-select: none;
@@ -179,15 +179,20 @@
179
179
  display: flex;
180
180
  width: 100%;
181
181
  }
182
+ /* Chip heights sit on the 4px grid (md 24 / sm 20 / xs 16) through a
183
+ min-height, so the leading never decides the box. */
182
184
  .badge {
185
+ --badge-h: var(--chip-md);
183
186
  display: inline-flex;
184
187
  align-items: center;
185
188
  gap: var(--sp-1);
186
- padding: 0.15rem var(--sp-2);
189
+ min-height: var(--badge-h);
190
+ padding: 0 var(--sp-2);
187
191
  border-radius: var(--badge-radius, var(--r-pill));
188
192
  font-size: var(--badge-fs, var(--fs-xs));
189
193
  font-weight: var(--badge-fw, var(--fw-medium));
190
- line-height: 1.4;
194
+ line-height: var(--lh-snug);
195
+ --icon-size: var(--icon-xs);
191
196
  background: var(--badge-bg, var(--bg-elevated-2));
192
197
  color: var(--badge-fg, var(--text-muted));
193
198
  border: 1px solid var(--badge-border, var(--border));
@@ -197,13 +202,15 @@
197
202
  .badge-sm,
198
203
  .badge-xs {
199
204
  font-size: var(--badge-fs, var(--fs-xs));
200
- gap: 0.15rem;
205
+ gap: var(--sp-0-5);
201
206
  }
202
207
  .badge-sm {
203
- padding: 0 0.4rem;
208
+ --badge-h: var(--chip-sm);
209
+ padding: 0 var(--sp-1-5);
204
210
  }
205
211
  .badge-xs {
206
- padding: 0.05rem var(--sp-2);
212
+ --badge-h: var(--chip-xs);
213
+ line-height: var(--lh-none);
207
214
  }
208
215
  .toned {
209
216
  color: var(--badge-fg, var(--badge-tone));
@@ -211,6 +218,7 @@
211
218
  background: var(--badge-bg, color-mix(in srgb, var(--badge-tone) 12%, transparent));
212
219
  }
213
220
  .text {
221
+ min-height: 0;
214
222
  padding: 0;
215
223
  border-color: transparent;
216
224
  background: none;
@@ -233,7 +241,7 @@
233
241
  .numeric {
234
242
  font-variant-numeric: tabular-nums;
235
243
  min-width: var(--badge-min-size, 1.5em);
236
- min-height: var(--badge-min-size, auto);
244
+ min-height: var(--badge-min-size, var(--badge-h));
237
245
  justify-content: center;
238
246
  }
239
247
  .truncate {
@@ -255,25 +263,36 @@
255
263
  .interactive {
256
264
  cursor: pointer;
257
265
  transition:
258
- border-color 0.12s var(--ease),
259
- color 0.12s var(--ease);
266
+ border-color var(--dur-fast) var(--ease),
267
+ color var(--dur-fast) var(--ease),
268
+ scale var(--dur-fast) var(--ease-out);
260
269
  }
261
- .interactive:hover {
262
- border-color: var(--border-strong);
263
- color: var(--text);
270
+ @media (hover: hover) and (pointer: fine) {
271
+ .interactive:hover {
272
+ border-color: var(--border-strong);
273
+ color: var(--text);
274
+ }
275
+ .interactive.active:hover {
276
+ color: var(--text-on-accent);
277
+ border-color: var(--badge-tone, var(--accent));
278
+ filter: brightness(1.08);
279
+ }
264
280
  }
265
- .interactive.active:hover {
266
- color: var(--text-on-accent);
267
- border-color: var(--badge-tone, var(--accent));
268
- filter: brightness(1.08);
281
+ .interactive:active:not(:disabled, [aria-disabled='true']) {
282
+ scale: var(--press-scale);
283
+ transition-duration: var(--dur-press);
269
284
  }
270
285
  .interactive:focus-visible {
271
- outline: 2px solid var(--badge-tone, var(--accent));
272
- outline-offset: 2px;
286
+ outline: var(--focus-ring-width) solid var(--badge-tone, var(--accent));
287
+ outline-offset: var(--focus-ring-offset);
273
288
  }
274
289
  .removed {
275
290
  opacity: 0.55;
276
291
  text-decoration: line-through;
292
+ transition:
293
+ opacity var(--dur-fast) var(--ease),
294
+ color var(--dur-fast) var(--ease),
295
+ text-decoration-color var(--dur-fast) var(--ease);
277
296
  }
278
297
  .removed .action {
279
298
  text-decoration: none;
@@ -282,19 +301,36 @@
282
301
  display: inline-flex;
283
302
  align-items: center;
284
303
  justify-content: center;
285
- margin: 0 -0.15rem 0 0;
286
- padding: 0 0.1rem;
304
+ margin: 0 calc(-1 * var(--sp-0-5)) 0 0;
305
+ padding: 0 var(--sp-0-5);
287
306
  border: 0;
307
+ border-radius: var(--r-xs);
288
308
  background: none;
289
309
  color: inherit;
290
310
  font: inherit;
291
- line-height: 1;
311
+ line-height: var(--lh-none);
292
312
  cursor: pointer;
293
313
  opacity: 0.6;
294
- transition: opacity 0.12s var(--ease);
314
+ transition:
315
+ opacity var(--dur-fast) var(--ease),
316
+ background var(--dur-fast) var(--ease),
317
+ scale var(--dur-fast) var(--ease-out);
318
+ }
319
+ @media (hover: hover) and (pointer: fine) {
320
+ .action:hover {
321
+ opacity: 1;
322
+ background: color-mix(in srgb, currentColor 12%, transparent);
323
+ transition-duration: var(--dur-instant);
324
+ }
325
+ }
326
+ .action:active {
327
+ scale: var(--press-scale-sm);
328
+ transition-duration: var(--dur-press);
295
329
  }
296
- .action:hover {
297
- opacity: 1;
330
+ /* The ring sits inside: a truncating badge clips its box. */
331
+ .action:focus-visible {
332
+ outline: var(--focus-ring);
333
+ outline-offset: var(--focus-ring-offset-inset);
298
334
  }
299
335
  @media (pointer: coarse) {
300
336
  .action {