@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
@@ -8,6 +8,7 @@
8
8
  import Icon from '../atoms/Icon.svelte';
9
9
  import type { IconName } from '../atoms/Icon.svelte';
10
10
  import { formatCount, hasCount } from '../../count';
11
+ import { playSound } from '../../sound';
11
12
 
12
13
  export interface TabBarItem {
13
14
  id: string;
@@ -27,6 +28,7 @@
27
28
  onselect,
28
29
  label = 'Primary',
29
30
  placement = 'bar',
31
+ sound = true,
30
32
  class: klass = '',
31
33
  ...rest
32
34
  }: {
@@ -38,6 +40,8 @@
38
40
  label?: string;
39
41
  /** 'bar' = fixed to the bottom edge; 'inline' = flows with its parent. */
40
42
  placement?: 'bar' | 'inline';
43
+ /** Play UI sounds when enabled globally; false silences this instance. */
44
+ sound?: boolean;
41
45
  class?: string;
42
46
  [key: string]: unknown;
43
47
  } = $props();
@@ -60,6 +64,7 @@
60
64
  function select(item: TabBarItem) {
61
65
  if (item.id === value) return;
62
66
  value = item.id;
67
+ playSound('click', sound);
63
68
  onselect?.(item.id, item);
64
69
  }
65
70
 
@@ -82,7 +87,7 @@
82
87
  {#snippet glyph(item: TabBarItem)}
83
88
  <span class="tabbar-glyph">
84
89
  {#if item.icon}
85
- <Icon name={item.icon} size={22} />
90
+ <Icon name={item.icon} />
86
91
  {/if}
87
92
  {#if badgeText(item.badge)}
88
93
  <Badge class="tabbar-badge" tone="accent" size="xs" border={false} active numeric={typeof item.badge === 'number'}>
@@ -167,7 +172,7 @@
167
172
  flex-direction: column;
168
173
  align-items: center;
169
174
  justify-content: center;
170
- gap: 2px;
175
+ gap: var(--sp-0-5);
171
176
  padding: var(--sp-1) var(--sp-2);
172
177
  border: none;
173
178
  border-radius: 0;
@@ -179,15 +184,17 @@
179
184
  text-decoration: none;
180
185
  cursor: pointer;
181
186
  -webkit-tap-highlight-color: transparent;
182
- transition: color 0.12s var(--ease);
187
+ transition: color var(--dur-fast) var(--ease);
183
188
  }
184
- .tabbar-item:hover {
185
- color: var(--text);
186
- text-decoration: none;
189
+ @media (hover: hover) and (pointer: fine) {
190
+ .tabbar-item:hover {
191
+ color: var(--text);
192
+ text-decoration: none;
193
+ }
187
194
  }
188
195
  .tabbar-item:focus-visible {
189
- outline: 2px solid var(--accent);
190
- outline-offset: -2px;
196
+ outline: var(--focus-ring);
197
+ outline-offset: var(--focus-ring-offset-inset);
191
198
  }
192
199
  .tabbar-item.current {
193
200
  color: var(--accent);
@@ -197,6 +204,13 @@
197
204
  display: inline-flex;
198
205
  flex: none;
199
206
  align-items: center;
207
+ font-size: var(--icon-xl);
208
+ --icon-size: var(--icon-xl);
209
+ transition: scale var(--dur-fast) var(--ease-out);
210
+ }
211
+ .tabbar-item:active .tabbar-glyph {
212
+ scale: var(--press-scale-sm);
213
+ transition-duration: var(--dur-press);
200
214
  }
201
215
  .tabbar-label {
202
216
  max-width: 100%;
@@ -209,11 +223,7 @@
209
223
  top: -0.4rem;
210
224
  left: calc(100% - 2px);
211
225
  max-width: none;
212
- padding-inline: 0.3rem;
213
- }
214
- @media (prefers-reduced-motion: reduce) {
215
- .tabbar-item {
216
- transition: none;
217
- }
226
+ padding-inline: var(--sp-1-5);
227
+ font-size: var(--fs-xs);
218
228
  }
219
229
  </style>
@@ -19,6 +19,8 @@ type $$ComponentProps = {
19
19
  label?: string;
20
20
  /** 'bar' = fixed to the bottom edge; 'inline' = flows with its parent. */
21
21
  placement?: 'bar' | 'inline';
22
+ /** Play UI sounds when enabled globally; false silences this instance. */
23
+ sound?: boolean;
22
24
  class?: string;
23
25
  [key: string]: unknown;
24
26
  };
@@ -37,6 +37,7 @@
37
37
  variant = 'default',
38
38
  size = 'default',
39
39
  onchange,
40
+ sound = true,
40
41
  class: klass = '',
41
42
  style: styleProp = '',
42
43
  ...rest
@@ -52,6 +53,8 @@
52
53
  /** Header/panel density of every item; forwarded to `Disclosure`. */
53
54
  size?: DisclosureSize;
54
55
  onchange?: (id: string, open: boolean) => void;
56
+ /** Play UI sounds when enabled globally; false silences every item. */
57
+ sound?: boolean;
55
58
  class?: string;
56
59
  style?: string;
57
60
  };
@@ -105,6 +108,7 @@
105
108
  disabled={item.disabled}
106
109
  {chevron}
107
110
  {size}
111
+ {sound}
108
112
  >
109
113
  {#snippet header(ctx)}
110
114
  {#if item.summary}
@@ -31,6 +31,8 @@ type Own = {
31
31
  /** Header/panel density of every item; forwarded to `Disclosure`. */
32
32
  size?: DisclosureSize;
33
33
  onchange?: (id: string, open: boolean) => void;
34
+ /** Play UI sounds when enabled globally; false silences every item. */
35
+ sound?: boolean;
34
36
  class?: string;
35
37
  style?: string;
36
38
  };
@@ -128,7 +128,7 @@
128
128
  {#if src}
129
129
  <img class="thumb" {src} alt="" />
130
130
  {:else}
131
- <Icon name={isImage(f) ? 'image' : 'file'} size={18} />
131
+ <Icon name={isImage(f) ? 'image' : 'file'} style="font-size: var(--icon-lg)" />
132
132
  {/if}
133
133
  {#if numbered}<span class="num tile-num">{i + 1}</span>{/if}
134
134
  </span>
@@ -149,7 +149,7 @@
149
149
  onremove?.(i);
150
150
  }}
151
151
  >
152
- <Icon name="trash" size={16} />
152
+ <Icon name="trash" style="font-size: var(--control-icon-sm)" />
153
153
  </Button>
154
154
  {/if}
155
155
  </div>
@@ -223,7 +223,7 @@
223
223
  width: 100%;
224
224
  height: 100%;
225
225
  object-fit: cover;
226
- border-radius: calc(var(--r-md) - 1px);
226
+ border-radius: calc(var(--r-md) - var(--sp-px));
227
227
  }
228
228
  .detail {
229
229
  display: flex;
@@ -247,7 +247,7 @@
247
247
  overflow-wrap: anywhere;
248
248
  }
249
249
  .chip :global(.action) {
250
- font-size: 1.4em;
250
+ font-size: var(--fs-md);
251
251
  }
252
252
  .chip :global(.clip) {
253
253
  display: inline-flex;
@@ -278,13 +278,16 @@
278
278
  color: inherit;
279
279
  cursor: pointer;
280
280
  }
281
- .open:hover {
282
- text-decoration: underline;
281
+ @media (hover: hover) and (pointer: fine) {
282
+ .open:hover {
283
+ text-decoration: underline;
284
+ }
283
285
  }
286
+ /* The clip span hides overflow, so the ring sits inside the name. */
284
287
  .open:focus-visible {
285
288
  outline: var(--focus-ring);
286
- outline-offset: var(--focus-ring-offset);
287
- border-radius: var(--r-sm);
289
+ outline-offset: var(--focus-ring-offset-inset);
290
+ border-radius: var(--r-xs);
288
291
  }
289
292
  .mini {
290
293
  flex: none;
@@ -295,9 +298,9 @@
295
298
  }
296
299
  .num {
297
300
  flex: none;
298
- font-size: 0.8em;
299
- font-weight: 600;
300
- line-height: 1;
301
+ font-size: var(--fs-2xs);
302
+ font-weight: var(--fw-semibold);
303
+ line-height: var(--lh-none);
301
304
  color: var(--text-muted);
302
305
  }
303
306
  .face {
@@ -72,10 +72,7 @@
72
72
  {/if}
73
73
  {#if i < shown.length - 1}
74
74
  <span class="sep" aria-hidden="true">
75
- {#if separator}{@render separator()}{:else if char}{char}{:else}<Icon
76
- name="chevron-right"
77
- size={size === 'md' ? 16 : 14}
78
- />{/if}
75
+ {#if separator}{@render separator()}{:else if char}{char}{:else}<Icon name="chevron-right" />{/if}
79
76
  </span>
80
77
  {/if}
81
78
  </li>
@@ -127,11 +124,14 @@
127
124
  overflow: hidden;
128
125
  text-overflow: ellipsis;
129
126
  max-width: 16ch;
130
- transition: color 0.12s var(--ease);
127
+ transition: color var(--dur-fast) ease;
131
128
  }
132
- .link:hover {
133
- color: var(--text);
134
- text-decoration: underline;
129
+ @media (hover: hover) and (pointer: fine) {
130
+ .link:hover {
131
+ color: var(--text);
132
+ text-decoration: underline;
133
+ transition-duration: var(--dur-instant);
134
+ }
135
135
  }
136
136
  .current {
137
137
  color: var(--text);
@@ -143,7 +143,7 @@
143
143
  }
144
144
  @media (hover: none) and (pointer: coarse) {
145
145
  .link {
146
- min-height: 40px;
146
+ min-height: var(--control-md);
147
147
  align-content: center;
148
148
  }
149
149
  }
@@ -154,5 +154,9 @@
154
154
  display: inline-flex;
155
155
  align-items: center;
156
156
  color: var(--text-subtle, var(--text-muted));
157
+ font-size: var(--icon-sm);
158
+ }
159
+ .crumbs--md .sep {
160
+ font-size: var(--icon-md);
157
161
  }
158
162
  </style>
@@ -111,8 +111,8 @@
111
111
  flex: none;
112
112
  align-items: center;
113
113
  color: var(--callout-tone);
114
- font-size: 1.125rem;
115
- line-height: 1;
114
+ font-size: var(--fs-lg);
115
+ line-height: var(--lh-none);
116
116
  min-height: 1.4em;
117
117
  }
118
118
  .callout-body {
@@ -122,7 +122,7 @@
122
122
  flex-direction: column;
123
123
  gap: var(--sp-1);
124
124
  font-size: var(--fs-sm);
125
- line-height: 1.5;
125
+ line-height: var(--lh-normal);
126
126
  color: var(--text);
127
127
  }
128
128
  .callout-title {
@@ -207,7 +207,7 @@
207
207
  onpointerup={pointerUp}
208
208
  onpointercancel={pointerUp}
209
209
  >
210
- <div class="fill"></div>
210
+ <div class="fill-clip"><div class="fill"></div></div>
211
211
  {#if markerPct !== null}
212
212
  <div class="marker marker-{markerTone}" title={markerLabel} aria-hidden="true"></div>
213
213
  {#if markerLabel}
@@ -287,14 +287,20 @@
287
287
  box-shadow: inset 0 0 0 1px var(--border);
288
288
  pointer-events: none;
289
289
  }
290
+ .fill-clip {
291
+ position: absolute;
292
+ inset: 0;
293
+ border-radius: var(--r-pill);
294
+ overflow: hidden;
295
+ z-index: 1;
296
+ }
290
297
  .fill {
291
298
  position: absolute;
292
- inset: 0 auto 0 0;
293
- width: var(--pct);
299
+ inset: 0;
294
300
  border-radius: var(--r-pill);
295
301
  background: var(--fill);
296
- transition: width 0.2s var(--ease);
297
- z-index: 1;
302
+ translate: calc(var(--pct) - 100%) 0;
303
+ transition: translate var(--dur-base) var(--ease-out);
298
304
  }
299
305
  .uncapped .track::after {
300
306
  display: none;
@@ -306,7 +312,7 @@
306
312
  width: 2px;
307
313
  height: calc(var(--track-h) + 6px);
308
314
  transform: translate(-50%, -50%);
309
- border-radius: 1px;
315
+ border-radius: var(--r-xs);
310
316
  background: var(--marker-color, var(--text-muted));
311
317
  pointer-events: none;
312
318
  z-index: 2;
@@ -338,7 +344,7 @@
338
344
  width: 3px;
339
345
  height: 16px;
340
346
  transform: translate(-50%, -50%);
341
- border-radius: 2px;
347
+ border-radius: var(--r-xs);
342
348
  background: var(--text);
343
349
  box-shadow: 0 0 0 2px var(--surface);
344
350
  cursor: ew-resize;
@@ -348,36 +354,37 @@
348
354
  opacity: 0.4;
349
355
  }
350
356
  .handle:focus-visible {
351
- outline: 2px solid var(--accent);
352
- outline-offset: 2px;
357
+ outline: var(--focus-ring);
358
+ outline-offset: var(--focus-ring-offset);
353
359
  }
354
360
  .bubble {
355
361
  position: absolute;
356
- bottom: calc(100% + 8px);
357
- left: var(--cap);
358
- transform: translateX(-50%);
359
- padding: 1px var(--sp-1);
362
+ bottom: calc(100% + var(--sp-2));
363
+ left: clamp(0%, var(--cap), 100%);
364
+ translate: calc(-1 * clamp(0%, var(--cap), 100%)) 0;
365
+ padding: var(--sp-px) var(--sp-1);
360
366
  border: 1px solid var(--border-strong);
361
367
  border-radius: var(--r-sm);
362
368
  background: var(--bg-elevated-2);
363
369
  box-shadow: var(--shadow-sm);
364
370
  color: var(--text);
365
371
  font-size: var(--fs-xs);
366
- line-height: 1.4;
372
+ line-height: var(--lh-snug);
367
373
  font-variant-numeric: tabular-nums;
368
374
  white-space: nowrap;
369
375
  pointer-events: none;
370
376
  opacity: 0;
371
377
  visibility: hidden;
372
378
  transition:
373
- opacity 0.12s var(--ease),
374
- visibility 0.12s;
379
+ opacity var(--dur-exit) var(--ease),
380
+ visibility var(--dur-exit);
375
381
  z-index: 3;
376
382
  }
377
383
  .dragging .bubble,
378
384
  .keying .bubble {
379
385
  opacity: 1;
380
386
  visibility: visible;
387
+ transition-duration: var(--dur-fast);
381
388
  }
382
389
  .readonly .track,
383
390
  .readonly .handle {
@@ -419,10 +426,4 @@
419
426
  .cap-bar.stacked .caption {
420
427
  grid-column: 1 / -1;
421
428
  }
422
- @media (prefers-reduced-motion: reduce) {
423
- .fill,
424
- .bubble {
425
- transition: none;
426
- }
427
- }
428
429
  </style>
@@ -143,7 +143,7 @@
143
143
  {onpointerup}
144
144
  {onpointercancel}
145
145
  >
146
- <div class="track" style:transform="translateX(-{index * 100}%)">
146
+ <div class="track" style:translate="{-index * 100}% 0">
147
147
  {#each slides as item, i (i)}
148
148
  <div
149
149
  class="slide {slideClass}"
@@ -222,7 +222,7 @@
222
222
  .track {
223
223
  display: flex;
224
224
  width: 100%;
225
- transition: transform 0.35s var(--ease);
225
+ transition: translate var(--dur-sheet) var(--ease-in-out);
226
226
  }
227
227
  .slide {
228
228
  flex: 0 0 100%;
@@ -240,37 +240,47 @@
240
240
  gap: var(--sp-2);
241
241
  }
242
242
  .dot {
243
- width: 0.5rem;
244
- height: 0.5rem;
243
+ position: relative;
244
+ width: var(--sp-2);
245
+ height: var(--sp-2);
245
246
  padding: 0;
246
247
  border: 0;
247
248
  border-radius: var(--r-pill);
248
249
  background: var(--border);
249
250
  cursor: pointer;
250
251
  transition:
251
- background 0.12s var(--ease),
252
- transform 0.12s var(--ease);
252
+ background var(--dur-fast) var(--ease),
253
+ scale var(--dur-fast) var(--ease);
253
254
  }
254
- .dot:hover {
255
- background: var(--text-faint);
255
+ @media (hover: hover) and (pointer: fine) {
256
+ .dot:hover {
257
+ background: var(--text-faint);
258
+ }
259
+ }
260
+ .dot:active {
261
+ scale: var(--press-scale-sm);
256
262
  }
257
263
  .dot.active {
258
264
  background: var(--accent);
259
- transform: scale(1.25);
265
+ scale: 1.25;
260
266
  }
261
267
  .dot:focus-visible {
262
- outline: 2px solid var(--accent);
263
- outline-offset: 2px;
268
+ outline: var(--focus-ring);
269
+ outline-offset: var(--focus-ring-offset);
270
+ }
271
+ @media (pointer: coarse) {
272
+ .dots {
273
+ gap: var(--sp-4);
274
+ }
275
+ .dot::before {
276
+ content: '';
277
+ position: absolute;
278
+ inset: calc((100% - var(--sp-6)) / 2);
279
+ }
264
280
  }
265
281
  .counter {
266
282
  color: var(--text-muted);
267
283
  font-size: var(--fs-sm);
268
284
  font-variant-numeric: tabular-nums;
269
285
  }
270
- @media (prefers-reduced-motion: reduce) {
271
- .track,
272
- .dot {
273
- transition: none;
274
- }
275
- }
276
286
  </style>
@@ -134,8 +134,8 @@
134
134
  align-self: flex-start;
135
135
  }
136
136
  .selected {
137
- outline: 2px solid var(--accent);
138
- outline-offset: 1px;
137
+ outline: var(--focus-ring);
138
+ outline-offset: var(--focus-ring-offset);
139
139
  }
140
140
  .bubble[data-state='sending'] {
141
141
  opacity: 0.7;
@@ -118,7 +118,7 @@
118
118
  user-select: none;
119
119
  font-family: var(--font-mono);
120
120
  font-size: var(--fs-sm);
121
- line-height: 1.5;
121
+ line-height: var(--lh-normal);
122
122
  }
123
123
  .cb-pre {
124
124
  margin: 0;
@@ -126,7 +126,7 @@
126
126
  flex: 1;
127
127
  font-family: var(--font-mono);
128
128
  font-size: var(--fs-sm);
129
- line-height: 1.5;
129
+ line-height: var(--lh-normal);
130
130
  color: var(--md-text, var(--text));
131
131
  tab-size: 2;
132
132
  }
@@ -7,6 +7,7 @@
7
7
  import { tick, type Snippet } from 'svelte';
8
8
  import type { HTMLAttributes } from 'svelte/elements';
9
9
  import { type AnchorRect, placeAt } from '../../floating';
10
+ import { playSound } from '../../sound';
10
11
  import { caretRect, comboboxAction } from './combobox-keyboard.js';
11
12
 
12
13
  type FieldEl = HTMLInputElement | HTMLTextAreaElement;
@@ -46,6 +47,8 @@
46
47
  option?: Snippet<[T, { active: boolean; index: number }]>;
47
48
  /** Shown instead of rows when `options` is empty; without it the listbox hides. */
48
49
  empty?: string | Snippet;
50
+ /** Play UI sounds when enabled globally; false silences this instance. */
51
+ sound?: boolean;
49
52
  children: Snippet;
50
53
  class?: string;
51
54
  style?: string;
@@ -73,6 +76,7 @@
73
76
  onclose,
74
77
  option,
75
78
  empty,
79
+ sound = true,
76
80
  children,
77
81
  class: klass = '',
78
82
  style: styleProp = '',
@@ -136,6 +140,13 @@
136
140
  placeAt(r, panelEl, placement, gap);
137
141
  }
138
142
 
143
+ let wasVisible: boolean | null = null;
144
+ $effect(() => {
145
+ const v = visible;
146
+ if (wasVisible !== null && v !== wasVisible) playSound(v ? 'open' : 'close', sound);
147
+ wasVisible = v;
148
+ });
149
+
139
150
  $effect(() => {
140
151
  const p = panelEl;
141
152
  if (!visible || !p) return;
@@ -264,7 +275,7 @@
264
275
  box-sizing: border-box;
265
276
  display: flex;
266
277
  flex-direction: column;
267
- gap: 1px;
278
+ gap: var(--sp-px);
268
279
  min-width: var(--cb-min-width, 10rem);
269
280
  max-width: calc(100vw - 2 * var(--sp-3));
270
281
  max-height: var(--cb-max-height, min(16rem, 40vh));
@@ -275,6 +286,21 @@
275
286
  border: 1px solid var(--cb-border, var(--border-strong));
276
287
  border-radius: var(--cb-radius, var(--r-md));
277
288
  box-shadow: var(--shadow-md);
289
+ transform-origin: var(--tsu-origin, top left);
290
+ opacity: 1;
291
+ scale: 1;
292
+ translate: 0;
293
+ transition:
294
+ opacity var(--dur-fast) var(--ease-out),
295
+ scale var(--dur-fast) var(--ease-out),
296
+ translate var(--dur-fast) var(--ease-out);
297
+ }
298
+ @starting-style {
299
+ .cb-panel:popover-open {
300
+ opacity: 0;
301
+ scale: var(--enter-scale);
302
+ translate: var(--tsu-shift, 0 calc(-1 * var(--shift-sm)));
303
+ }
278
304
  }
279
305
  .cb-panel.cb-caret {
280
306
  max-width: min(22rem, calc(100vw - 2 * var(--sp-3)));
@@ -287,9 +313,12 @@
287
313
  overflow: hidden;
288
314
  text-overflow: ellipsis;
289
315
  white-space: nowrap;
316
+ transition: background-color var(--dur-fast) ease;
290
317
  }
318
+ /* Highlight lands instantly and fades out: list navigation is high frequency. */
291
319
  .cb-option.active {
292
320
  background: var(--cb-active-bg, var(--bg-elevated-2));
321
+ transition-duration: var(--dur-instant);
293
322
  }
294
323
  .cb-empty {
295
324
  padding: var(--sp-1) var(--sp-2);
@@ -37,6 +37,8 @@ declare function $$render<T>(): {
37
37
  }]>;
38
38
  /** Shown instead of rows when `options` is empty; without it the listbox hides. */
39
39
  empty?: string | Snippet;
40
+ /** Play UI sounds when enabled globally; false silences this instance. */
41
+ sound?: boolean;
40
42
  children: Snippet;
41
43
  class?: string;
42
44
  style?: string;
@@ -78,6 +80,8 @@ declare function $$render<T>(): {
78
80
  }]>;
79
81
  /** Shown instead of rows when `options` is empty; without it the listbox hides. */
80
82
  empty?: string | Snippet;
83
+ /** Play UI sounds when enabled globally; false silences this instance. */
84
+ sound?: boolean;
81
85
  children: Snippet;
82
86
  class?: string;
83
87
  style?: string;
@@ -229,8 +229,8 @@
229
229
  border-radius: var(--r-lg);
230
230
  }
231
231
  .composer.dragging {
232
- outline: 2px dashed var(--accent);
233
- outline-offset: var(--sp-1);
232
+ outline: var(--focus-ring-width) dashed var(--accent);
233
+ outline-offset: var(--focus-ring-offset);
234
234
  }
235
235
  .toolbar {
236
236
  display: flex;
@@ -21,6 +21,7 @@
21
21
  oncancel,
22
22
  class: klass = '',
23
23
  style: styleProp = '',
24
+ sound = true,
24
25
  }: {
25
26
  open?: boolean;
26
27
  title: string;
@@ -35,6 +36,8 @@
35
36
  oncancel?: () => void;
36
37
  class?: string;
37
38
  style?: string;
39
+ /** Play UI sounds when enabled globally; false silences this instance. */
40
+ sound?: boolean;
38
41
  } = $props();
39
42
  const t = $derived(canonicalTone(tone));
40
43
  const modalTone = $derived(t === 'danger' || t === 'warn' || t === 'info' ? t : 'neutral');
@@ -80,6 +83,7 @@
80
83
  size="sm"
81
84
  class={klass}
82
85
  style={styleProp}
86
+ {sound}
83
87
  >
84
88
  {#snippet body()}
85
89
  <div class="confirm-body" data-tsu="ConfirmModal">
@@ -14,6 +14,8 @@ type $$ComponentProps = {
14
14
  oncancel?: () => void;
15
15
  class?: string;
16
16
  style?: string;
17
+ /** Play UI sounds when enabled globally; false silences this instance. */
18
+ sound?: boolean;
17
19
  };
18
20
  declare const ConfirmModal: import("svelte").Component<$$ComponentProps, {}, "open">;
19
21
  type ConfirmModal = ReturnType<typeof ConfirmModal>;