@dorsk/tsumikit 0.73.2 → 0.75.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 (137) 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 +12 -6
  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/Skeleton.svelte +14 -8
  23. package/dist/components/atoms/Slider.svelte +79 -22
  24. package/dist/components/atoms/Slider.svelte.d.ts +2 -0
  25. package/dist/components/atoms/Spinner.svelte +464 -5
  26. package/dist/components/atoms/Spinner.svelte.d.ts +2 -0
  27. package/dist/components/atoms/Swatch.svelte +19 -8
  28. package/dist/components/atoms/Switch.svelte +34 -21
  29. package/dist/components/atoms/Switch.svelte.d.ts +2 -0
  30. package/dist/components/atoms/Text.svelte +1 -1
  31. package/dist/components/atoms/Textarea.svelte +34 -12
  32. package/dist/components/atoms/TimeDisplay.svelte +4 -2
  33. package/dist/components/layouts/AppShell.svelte +26 -15
  34. package/dist/components/layouts/MasterDetail.svelte +23 -21
  35. package/dist/components/layouts/NavBar.svelte +11 -8
  36. package/dist/components/layouts/NavItem.svelte +15 -7
  37. package/dist/components/layouts/NavSection.svelte +1 -1
  38. package/dist/components/layouts/ResizablePanel.svelte +37 -16
  39. package/dist/components/layouts/TabBar.svelte +24 -14
  40. package/dist/components/layouts/TabBar.svelte.d.ts +2 -0
  41. package/dist/components/molecules/Accordion.svelte +4 -0
  42. package/dist/components/molecules/Accordion.svelte.d.ts +2 -0
  43. package/dist/components/molecules/AttachmentList.svelte +14 -11
  44. package/dist/components/molecules/Breadcrumb.svelte +13 -9
  45. package/dist/components/molecules/Callout.svelte +3 -3
  46. package/dist/components/molecules/CapBar.svelte +23 -22
  47. package/dist/components/molecules/Carousel.svelte +27 -17
  48. package/dist/components/molecules/ChatBubble.svelte +2 -2
  49. package/dist/components/molecules/CodeBlock.svelte +2 -2
  50. package/dist/components/molecules/Combobox.svelte +30 -1
  51. package/dist/components/molecules/Combobox.svelte.d.ts +4 -0
  52. package/dist/components/molecules/Composer.svelte +2 -2
  53. package/dist/components/molecules/ConfirmModal.svelte +4 -0
  54. package/dist/components/molecules/ConfirmModal.svelte.d.ts +2 -0
  55. package/dist/components/molecules/CopyButton.svelte +46 -2
  56. package/dist/components/molecules/CopyButton.svelte.d.ts +2 -0
  57. package/dist/components/molecules/Disclosure.svelte +23 -12
  58. package/dist/components/molecules/Disclosure.svelte.d.ts +2 -0
  59. package/dist/components/molecules/Drawer.svelte +43 -23
  60. package/dist/components/molecules/Drawer.svelte.d.ts +2 -0
  61. package/dist/components/molecules/Dropzone.svelte +24 -8
  62. package/dist/components/molecules/EmojiPicker.svelte +27 -8
  63. package/dist/components/molecules/EmojiPicker.svelte.d.ts +2 -0
  64. package/dist/components/molecules/EmptyState.svelte +5 -5
  65. package/dist/components/molecules/Fieldset.svelte +8 -13
  66. package/dist/components/molecules/FileButton.svelte +57 -31
  67. package/dist/components/molecules/FileButton.svelte.d.ts +2 -0
  68. package/dist/components/molecules/FilterInput.svelte +51 -11
  69. package/dist/components/molecules/FontScalePicker.svelte +20 -9
  70. package/dist/components/molecules/GitRef.svelte +9 -7
  71. package/dist/components/molecules/GlyphStack.svelte +38 -14
  72. package/dist/components/molecules/IconButton.svelte +6 -3
  73. package/dist/components/molecules/IconButton.svelte.d.ts +2 -0
  74. package/dist/components/molecules/InputGroup.svelte +13 -12
  75. package/dist/components/molecules/LoadMore.svelte +6 -1
  76. package/dist/components/molecules/LoadMore.svelte.d.ts +2 -0
  77. package/dist/components/molecules/Menu.svelte +33 -7
  78. package/dist/components/molecules/Menu.svelte.d.ts +2 -0
  79. package/dist/components/molecules/Metric.svelte +11 -9
  80. package/dist/components/molecules/Modal.svelte +76 -26
  81. package/dist/components/molecules/Modal.svelte.d.ts +2 -0
  82. package/dist/components/molecules/OptionButton.svelte +28 -10
  83. package/dist/components/molecules/OptionButton.svelte.d.ts +2 -0
  84. package/dist/components/molecules/Pagination.svelte +26 -9
  85. package/dist/components/molecules/Pagination.svelte.d.ts +2 -0
  86. package/dist/components/molecules/Popover.svelte +107 -51
  87. package/dist/components/molecules/Popover.svelte.d.ts +2 -0
  88. package/dist/components/molecules/Prose.svelte +1 -1
  89. package/dist/components/molecules/PullToRefresh.svelte +14 -10
  90. package/dist/components/molecules/PullToRefresh.svelte.d.ts +2 -0
  91. package/dist/components/molecules/RadialMenu.svelte +52 -19
  92. package/dist/components/molecules/RadialMenu.svelte.d.ts +2 -0
  93. package/dist/components/molecules/RadioGroup.svelte +38 -23
  94. package/dist/components/molecules/RadioGroup.svelte.d.ts +2 -0
  95. package/dist/components/molecules/SectionHeader.svelte +13 -8
  96. package/dist/components/molecules/SeekSlider.svelte +38 -27
  97. package/dist/components/molecules/SeekSlider.svelte.d.ts +2 -0
  98. package/dist/components/molecules/SegmentedControl.svelte +139 -86
  99. package/dist/components/molecules/SegmentedControl.svelte.d.ts +2 -0
  100. package/dist/components/molecules/SelectButton.svelte +21 -6
  101. package/dist/components/molecules/SelectButton.svelte.d.ts +2 -0
  102. package/dist/components/molecules/SortableList.svelte +10 -11
  103. package/dist/components/molecules/SoundSettings.svelte +161 -0
  104. package/dist/components/molecules/SoundSettings.svelte.d.ts +17 -0
  105. package/dist/components/molecules/SplitButton.svelte +30 -3
  106. package/dist/components/molecules/SplitButton.svelte.d.ts +2 -0
  107. package/dist/components/molecules/SwipeRow.svelte +18 -15
  108. package/dist/components/molecules/SwipeRow.svelte.d.ts +2 -0
  109. package/dist/components/molecules/Tabs.svelte +92 -10
  110. package/dist/components/molecules/Tabs.svelte.d.ts +2 -0
  111. package/dist/components/molecules/ThemePicker.svelte +25 -10
  112. package/dist/components/molecules/Timestamp.svelte +12 -7
  113. package/dist/components/molecules/Toaster.svelte +65 -14
  114. package/dist/components/molecules/Toaster.svelte.d.ts +2 -0
  115. package/dist/components/molecules/Toggle.svelte +36 -13
  116. package/dist/components/molecules/Toggle.svelte.d.ts +2 -0
  117. package/dist/components/molecules/Tooltip.svelte +41 -10
  118. package/dist/components/molecules/Tooltip.svelte.d.ts +1 -1
  119. package/dist/components/molecules/VolumeSlider.svelte +34 -14
  120. package/dist/components/molecules/VolumeSlider.svelte.d.ts +2 -0
  121. package/dist/components/molecules/WorkingDir.svelte +1 -0
  122. package/dist/components/organisms/ConversationFeed.svelte +4 -0
  123. package/dist/components/organisms/DataTable.svelte +37 -14
  124. package/dist/floating.js +26 -0
  125. package/dist/index.d.ts +4 -0
  126. package/dist/index.js +4 -1
  127. package/dist/sound-synth.d.ts +3 -0
  128. package/dist/sound-synth.js +91 -0
  129. package/dist/sound.d.ts +1 -0
  130. package/dist/sound.js +3 -0
  131. package/dist/stores/sound.svelte.d.ts +20 -0
  132. package/dist/stores/sound.svelte.js +150 -0
  133. package/dist/stores/toast.svelte.d.ts +9 -0
  134. package/dist/stores/toast.svelte.js +45 -9
  135. package/dist/styles/reset.css +19 -2
  136. package/dist/styles/tokens.css +89 -6
  137. package/package.json +1 -1
@@ -5,6 +5,7 @@
5
5
  // JS to open the dialog. Emits the chosen files via `onfiles`. Dependency-free.
6
6
  import Icon from '../atoms/Icon.svelte';
7
7
  import type { IconName } from '../atoms/Icon.svelte';
8
+ import { playSound } from '../../sound';
8
9
 
9
10
  let {
10
11
  onfiles,
@@ -20,6 +21,7 @@
20
21
  control = false,
21
22
  box,
22
23
  hitArea = 'auto',
24
+ sound = true,
23
25
  class: klass = ''
24
26
  }: {
25
27
  onfiles: (files: File[]) => void;
@@ -47,6 +49,8 @@
47
49
  box?: 'xs' | 'sm' | 'md' | 'lg';
48
50
  /** Icon-only buttons grow a touch hit slab on coarse pointers; `compact` opts out. */
49
51
  hitArea?: 'auto' | 'compact';
52
+ /** Play UI sounds when enabled globally; false silences this instance. */
53
+ sound?: boolean;
50
54
  class?: string;
51
55
  } = $props();
52
56
 
@@ -81,6 +85,7 @@
81
85
  {accept}
82
86
  {multiple}
83
87
  {disabled}
88
+ onclick={() => playSound('click', sound)}
84
89
  onchange={onchange}
85
90
  />
86
91
  {#if icon}
@@ -100,41 +105,52 @@
100
105
  align-items: center;
101
106
  justify-content: center;
102
107
  gap: var(--sp-2);
103
- padding: var(--sp-2) var(--sp-4);
104
- min-height: 2.5rem;
108
+ padding: var(--sp-2) var(--btn-px-md);
109
+ min-height: var(--control-height-default);
105
110
  border: 1px solid var(--border-strong);
106
111
  border-radius: var(--r-md);
107
112
  background: var(--surface);
108
113
  color: var(--text);
109
114
  font-weight: var(--fw-medium);
110
- font-size: var(--fs-sm);
111
- line-height: 1;
115
+ font-size: var(--control-fs-md);
116
+ line-height: var(--lh-none);
112
117
  white-space: nowrap;
113
118
  cursor: pointer;
114
119
  user-select: none;
120
+ --icon-size: var(--control-icon-md);
121
+ --file-scale-dur: var(--dur-fast);
115
122
  transition:
116
- background 0.12s var(--ease),
117
- border-color 0.12s var(--ease),
118
- opacity 0.12s var(--ease);
123
+ background var(--dur-fast) ease,
124
+ border-color var(--dur-fast) ease,
125
+ color var(--dur-fast) ease,
126
+ opacity var(--dur-fast) ease,
127
+ scale var(--file-scale-dur) var(--ease-out);
119
128
  }
120
- .file-btn:hover:not(.disabled) {
121
- border-color: var(--accent);
129
+ .file-btn:active:not(.disabled) {
130
+ --file-scale-dur: var(--dur-press);
131
+ scale: var(--press-scale);
132
+ }
133
+ .file-btn.icon-only:active:not(.disabled),
134
+ .file-btn.box:active:not(.disabled) {
135
+ scale: var(--press-scale-sm);
122
136
  }
123
- /* Size variants mirror the Button atom (sm 2rem / md 2.5rem / lg 3rem). */
137
+ /* Size variants mirror the Button atom. */
124
138
  .file-btn.sm {
125
- min-height: 2rem;
126
- padding: var(--sp-1) var(--sp-3);
127
- font-size: var(--fs-xs);
139
+ min-height: var(--control-height-compact);
140
+ padding: var(--sp-1) var(--btn-px-sm);
141
+ font-size: var(--control-fs-sm);
142
+ --icon-size: var(--control-icon-sm);
128
143
  }
129
144
  .file-btn.lg {
130
- min-height: 3rem;
131
- padding: var(--sp-3) var(--sp-5);
132
- font-size: var(--fs-base);
145
+ min-height: var(--control-height-large);
146
+ padding: var(--sp-3) var(--btn-px-lg);
147
+ font-size: var(--control-fs-lg);
148
+ --icon-size: var(--control-icon-lg);
133
149
  }
134
150
  .file-btn.control {
135
151
  height: var(--control-height);
136
152
  min-height: var(--control-height);
137
- padding: 0 var(--sp-3);
153
+ padding: 0 var(--control-px-md);
138
154
  }
139
155
  /* icon-only: square it up and drop the label gap. */
140
156
  .file-btn.icon-only {
@@ -162,12 +178,17 @@
162
178
  inset-inline-end: 0;
163
179
  }
164
180
  }
165
- /* Emoji glyph is bumped above the text size — at 1em a paperclip is hard to
166
- read, so render it a touch larger for legibility. */
181
+ /* An emoji reads small next to an SVG glyph of the same size: one icon tier up. */
167
182
  .emoji {
168
183
  display: inline-flex;
169
- font-size: 1.35em;
170
- line-height: 1;
184
+ font-size: var(--icon-lg);
185
+ line-height: var(--lh-none);
186
+ }
187
+ .file-btn.sm .emoji {
188
+ font-size: var(--icon-md);
189
+ }
190
+ .file-btn.lg .emoji {
191
+ font-size: var(--icon-xl);
171
192
  }
172
193
  .file-btn.primary {
173
194
  background: var(--accent);
@@ -175,25 +196,30 @@
175
196
  color: var(--text-on-accent);
176
197
  font-weight: var(--fw-semibold);
177
198
  }
178
- .file-btn.primary:hover:not(.disabled) {
179
- filter: brightness(1.08);
180
- }
181
199
  .file-btn.ghost {
182
200
  background: transparent;
183
201
  border-color: transparent;
184
202
  }
185
- .file-btn.ghost:hover:not(.disabled) {
186
- background: var(--bg-elevated-2);
187
- border-color: transparent;
188
- }
189
203
  /* The hidden input keeps focusability (sr-only, not display:none), so mirror
190
204
  its focus onto the label for a visible ring. */
191
205
  .file-btn:focus-within {
192
- outline: 2px solid var(--accent);
193
- outline-offset: 2px;
206
+ outline: var(--focus-ring);
207
+ outline-offset: var(--focus-ring-offset);
194
208
  }
195
209
  .file-btn.disabled {
196
- opacity: 0.45;
210
+ opacity: var(--disabled-opacity);
197
211
  cursor: not-allowed;
198
212
  }
213
+ @media (hover: hover) and (pointer: fine) {
214
+ .file-btn:hover:not(.disabled) {
215
+ border-color: var(--accent);
216
+ }
217
+ .file-btn.primary:hover:not(.disabled) {
218
+ filter: brightness(1.08);
219
+ }
220
+ .file-btn.ghost:hover:not(.disabled) {
221
+ background: var(--bg-elevated-2);
222
+ border-color: transparent;
223
+ }
224
+ }
199
225
  </style>
@@ -26,6 +26,8 @@ type $$ComponentProps = {
26
26
  box?: 'xs' | 'sm' | 'md' | 'lg';
27
27
  /** Icon-only buttons grow a touch hit slab on coarse pointers; `compact` opts out. */
28
28
  hitArea?: 'auto' | 'compact';
29
+ /** Play UI sounds when enabled globally; false silences this instance. */
30
+ sound?: boolean;
29
31
  class?: string;
30
32
  };
31
33
  declare const FileButton: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -465,20 +465,26 @@
465
465
  min-width: 0;
466
466
  }
467
467
  .fi__bar {
468
+ --fi-icon: var(--control-icon-md);
468
469
  display: flex;
469
470
  align-items: center;
470
471
  gap: var(--sp-2);
471
- padding: 0 var(--sp-3);
472
+ padding: 0 var(--control-px-md);
472
473
  min-height: var(--control-height-default);
473
474
  background: var(--bg-elevated);
474
475
  border: 1px solid var(--border-strong);
475
476
  border-radius: var(--r-md);
477
+ transition:
478
+ border-color var(--dur-fast) ease,
479
+ box-shadow var(--dur-fast) ease;
476
480
  }
477
481
  .fi--sm .fi__bar {
478
482
  min-height: var(--control-height-compact);
483
+ padding: 0 var(--control-px-sm);
484
+ --fi-icon: var(--control-icon-sm);
479
485
  }
480
486
  .fi--sm .fi__input {
481
- font-size: var(--fs-sm);
487
+ font-size: var(--control-fs-sm);
482
488
  }
483
489
  .fi--pill .fi__bar {
484
490
  border-radius: var(--r-pill);
@@ -493,11 +499,11 @@
493
499
  .fi__kbd {
494
500
  font-family: var(--font-mono);
495
501
  font-size: var(--fs-xs);
496
- line-height: 1;
502
+ line-height: var(--lh-none);
497
503
  color: var(--text-faint);
498
504
  border: 1px solid var(--border);
499
505
  border-radius: var(--r-sm);
500
- padding: 2px var(--sp-1);
506
+ padding: var(--sp-0-5) var(--sp-1);
501
507
  }
502
508
  .fi__bar:focus-within {
503
509
  border-color: var(--accent);
@@ -509,6 +515,7 @@
509
515
  }
510
516
  .fi__icon {
511
517
  display: flex;
518
+ font-size: var(--fi-icon);
512
519
  color: var(--text-faint);
513
520
  }
514
521
  .fi__field {
@@ -533,16 +540,16 @@
533
540
  background: transparent;
534
541
  color: var(--text);
535
542
  font-family: var(--font-mono);
536
- font-size: var(--fs-md);
543
+ font-size: var(--control-fs-md);
537
544
  outline: none;
538
545
  min-width: 0;
539
546
  }
540
547
  @media (pointer: coarse) {
541
548
  .fi__input {
542
- font-size: max(16px, var(--fs-md));
549
+ font-size: max(16px, var(--control-fs-md));
543
550
  }
544
551
  .fi--sm .fi__input {
545
- font-size: max(16px, var(--fs-sm));
552
+ font-size: max(16px, var(--control-fs-sm));
546
553
  }
547
554
  }
548
555
  .fi--sans .fi__input {
@@ -555,22 +562,40 @@
555
562
  color: transparent;
556
563
  }
557
564
  .fi__clear {
565
+ --clear-scale-dur: var(--dur-fast);
558
566
  display: flex;
559
567
  border: 0;
560
568
  background: transparent;
569
+ font-size: var(--fi-icon);
561
570
  color: var(--text-faint);
562
571
  cursor: pointer;
563
572
  padding: var(--sp-1);
564
573
  border-radius: var(--r-sm);
574
+ transition:
575
+ color var(--dur-fast) ease,
576
+ background-color var(--dur-fast) ease,
577
+ opacity var(--dur-fast) ease,
578
+ scale var(--clear-scale-dur) var(--ease-out);
579
+ }
580
+ @starting-style {
581
+ .fi__clear {
582
+ opacity: 0;
583
+ }
565
584
  }
566
- .fi__clear:hover {
567
- color: var(--text);
568
- background: var(--bg-elevated-2);
585
+ .fi__clear:active {
586
+ --clear-scale-dur: var(--dur-press);
587
+ scale: var(--press-scale-sm);
588
+ }
589
+ @media (hover: hover) and (pointer: fine) {
590
+ .fi__clear:hover {
591
+ color: var(--text);
592
+ background: var(--bg-elevated-2);
593
+ }
569
594
  }
570
595
 
571
596
  .fi__menu {
572
597
  position: absolute;
573
- top: calc(100% + 4px);
598
+ top: calc(100% + var(--sp-1));
574
599
  left: 0;
575
600
  right: 0;
576
601
  z-index: var(--z-drawer);
@@ -581,6 +606,18 @@
581
606
  padding: var(--sp-1);
582
607
  max-height: 320px;
583
608
  overflow-y: auto;
609
+ transform-origin: top left;
610
+ transition:
611
+ opacity var(--dur-fast) var(--ease-out),
612
+ scale var(--dur-fast) var(--ease-out),
613
+ translate var(--dur-fast) var(--ease-out);
614
+ }
615
+ @starting-style {
616
+ .fi__menu {
617
+ opacity: 0;
618
+ scale: var(--enter-scale);
619
+ translate: 0 calc(-1 * var(--shift-sm));
620
+ }
584
621
  }
585
622
  .fi__menu-head {
586
623
  font-size: var(--fs-xs);
@@ -602,9 +639,12 @@
602
639
  border-radius: var(--r-sm);
603
640
  cursor: pointer;
604
641
  font-size: var(--fs-sm);
642
+ transition: background-color var(--dur-fast) ease;
605
643
  }
644
+ /* Highlight lands instantly and fades out: list navigation is high frequency. */
606
645
  .fi__opt.is-active {
607
646
  background: var(--accent-dim);
647
+ transition-duration: var(--dur-instant);
608
648
  }
609
649
  .fi__opt-label {
610
650
  font-weight: var(--fw-medium);
@@ -75,14 +75,14 @@
75
75
  .glyph {
76
76
  font-weight: var(--fw-bold);
77
77
  font-size: 1rem;
78
- line-height: 1;
78
+ line-height: var(--lh-none);
79
79
  }
80
80
  .panel {
81
81
  width: min(15rem, calc(100vw - var(--sp-6)));
82
82
  box-sizing: border-box;
83
83
  overflow: hidden;
84
84
  font-size: 0.875rem;
85
- line-height: 1.4;
85
+ line-height: var(--lh-snug);
86
86
  padding: var(--sp-2) var(--sp-3);
87
87
  }
88
88
  .row {
@@ -106,16 +106,27 @@
106
106
  color: var(--text-muted);
107
107
  font-family: inherit;
108
108
  font-weight: var(--fw-bold);
109
- line-height: 1;
109
+ line-height: var(--lh-none);
110
110
  cursor: pointer;
111
- }
112
- .end:hover {
113
- background: var(--bg-elevated-2);
114
- color: var(--text);
111
+ --end-scale-dur: var(--dur-fast);
112
+ transition:
113
+ background-color var(--dur-fast) ease,
114
+ color var(--dur-fast) ease,
115
+ scale var(--end-scale-dur) var(--ease-out);
115
116
  }
116
117
  .end:focus-visible {
117
- outline: 2px solid var(--accent);
118
- outline-offset: 1px;
118
+ outline: var(--focus-ring);
119
+ outline-offset: var(--focus-ring-offset-inset);
120
+ }
121
+ .end:active {
122
+ --end-scale-dur: var(--dur-press);
123
+ scale: var(--press-scale-sm);
124
+ }
125
+ @media (hover: hover) and (pointer: fine) {
126
+ .end:hover {
127
+ background: var(--bg-elevated-2);
128
+ color: var(--text);
129
+ }
119
130
  }
120
131
  .small {
121
132
  font-size: 0.75rem;
@@ -115,7 +115,7 @@
115
115
  min-width: 0;
116
116
  font-family: var(--font-mono);
117
117
  font-size: var(--fs-xs);
118
- line-height: 1.4;
118
+ line-height: var(--lh-snug);
119
119
  white-space: nowrap;
120
120
  }
121
121
  .chip {
@@ -123,7 +123,7 @@
123
123
  align-items: center;
124
124
  gap: 0.25em;
125
125
  min-width: 0;
126
- padding: 0.15rem var(--sp-2);
126
+ padding: var(--sp-0-5) var(--sp-2);
127
127
  border-radius: var(--r-pill);
128
128
  background: var(--bg-elevated-2);
129
129
  color: var(--text-muted);
@@ -137,14 +137,16 @@
137
137
  color: var(--git-ref-tone);
138
138
  border-color: color-mix(in srgb, var(--git-ref-tone) 40%, transparent);
139
139
  background: color-mix(in srgb, var(--git-ref-tone) 12%, transparent);
140
- transition: border-color 0.12s var(--ease);
140
+ transition: border-color var(--dur-fast) var(--ease);
141
141
  }
142
- .pr:hover {
143
- border-color: var(--git-ref-tone);
142
+ @media (hover: hover) and (pointer: fine) {
143
+ .pr:hover {
144
+ border-color: var(--git-ref-tone);
145
+ }
144
146
  }
145
147
  .pr:focus-visible {
146
- outline: 2px solid var(--git-ref-tone);
147
- outline-offset: 2px;
148
+ outline: var(--focus-ring-width) solid var(--git-ref-tone);
149
+ outline-offset: var(--focus-ring-offset);
148
150
  }
149
151
  .text {
150
152
  min-width: 0;
@@ -55,6 +55,10 @@
55
55
  }: Omit<HTMLAttributes<HTMLSpanElement>, keyof Own> & Own = $props();
56
56
 
57
57
  let root = $state<HTMLElement>();
58
+ // Coarse pointers cannot hit 1rem cells, so a folded stack with items opens
59
+ // its tiles on tap unless the caller chose an expand mode.
60
+ let coarse = $state(false);
61
+ const mode = $derived(expand !== 'none' ? expand : coarse ? 'tap' : 'none');
58
62
  let width = $state(Number.POSITIVE_INFINITY);
59
63
  let limit = $state(0);
60
64
  const stacked = $derived(stack === 'always' || (stack === 'auto' && width < limit));
@@ -82,7 +86,7 @@
82
86
  const CONTROL = 'button, a[href], input, select, textarea, [role="button"], [tabindex]:not([tabindex="-1"])';
83
87
 
84
88
  const tiles = $derived(items?.slice(0, 4) ?? []);
85
- const expandable = $derived(expand !== 'none' && stacked && tiles.length > 0);
89
+ const expandable = $derived(mode !== 'none' && stacked && tiles.length > 0);
86
90
  let panel = $state<HTMLDivElement>();
87
91
  let open = $state(false);
88
92
  let armed = $state(-1);
@@ -151,7 +155,7 @@
151
155
  }
152
156
 
153
157
  function onPointerDown(e: PointerEvent) {
154
- if (!expandable || expand !== 'slide' || e.button !== 0) return;
158
+ if (!expandable || mode !== 'slide' || e.button !== 0) return;
155
159
  e.preventDefault();
156
160
  e.stopPropagation();
157
161
  slide.down(e, root);
@@ -177,12 +181,21 @@
177
181
  if (!expandable || e.detail === 0) return;
178
182
  e.preventDefault();
179
183
  e.stopPropagation();
180
- if (expand === 'tap') {
184
+ if (mode === 'tap') {
181
185
  if (open) hide();
182
186
  else show();
183
187
  }
184
188
  }
185
189
 
190
+ $effect(() => {
191
+ if (!browser || typeof matchMedia !== 'function') return;
192
+ const mq = matchMedia('(pointer: coarse)');
193
+ const sync = () => (coarse = mq.matches);
194
+ sync();
195
+ mq.addEventListener?.('change', sync);
196
+ return () => mq.removeEventListener?.('change', sync);
197
+ });
198
+
186
199
  $effect(() => {
187
200
  if (!expandable && open) hide();
188
201
  });
@@ -229,7 +242,7 @@
229
242
  class:stacked
230
243
  data-stack={stack}
231
244
  data-stacked={stacked || undefined}
232
- data-expand={expandable ? expand : undefined}
245
+ data-expand={expandable ? mode : undefined}
233
246
  {...rest}
234
247
  onpointerdown={onPointerDown}
235
248
  onpointermove={onPointerMove}
@@ -249,13 +262,13 @@
249
262
  {/if}
250
263
  </span>
251
264
 
252
- {#if expand !== 'none' && items}
265
+ {#if mode !== 'none' && items}
253
266
  <!-- `manual`: an `auto` popover would light-dismiss on the captured slide release. -->
254
267
  <div
255
268
  bind:this={panel}
256
269
  popover="manual"
257
270
  class="expand-panel"
258
- data-expand-panel={expand}
271
+ data-expand-panel={mode}
259
272
  role="group"
260
273
  aria-label={expandLabel}
261
274
  ontoggle={onToggle}
@@ -291,10 +304,12 @@
291
304
  display: inline-grid;
292
305
  grid-template-columns: repeat(2, 1rem);
293
306
  grid-auto-rows: 1rem;
294
- gap: 1px;
307
+ gap: var(--sp-px);
295
308
  place-items: center;
296
309
  }
310
+ /* Cells clip, so any focus ring inside draws inset. */
297
311
  .stacked > :global(*) {
312
+ --focus-ring-offset: var(--focus-ring-offset-inset);
298
313
  width: 1rem;
299
314
  height: 1rem;
300
315
  max-width: 1rem;
@@ -304,7 +319,7 @@
304
319
  justify-content: center;
305
320
  overflow: hidden;
306
321
  font-size: var(--fs-xs);
307
- line-height: 1;
322
+ line-height: var(--lh-none);
308
323
  }
309
324
  .glyph-stack[data-expand] {
310
325
  cursor: pointer;
@@ -325,18 +340,24 @@
325
340
  border: 1px solid var(--border-strong);
326
341
  border-radius: var(--r-md);
327
342
  box-shadow: var(--shadow-md);
343
+ opacity: 0;
344
+ scale: var(--enter-scale);
345
+ transition:
346
+ opacity var(--dur-base) var(--ease-out),
347
+ scale var(--dur-base) var(--ease-out);
328
348
  }
329
349
  .expand-panel:popover-open {
330
350
  display: grid;
331
351
  grid-template-columns: repeat(2, minmax(var(--glyph-stack-tile, 3rem), 1fr));
332
352
  grid-auto-rows: minmax(var(--glyph-stack-tile, 3rem), 1fr);
333
353
  gap: var(--sp-1);
334
- animation: glyph-expand 0.12s var(--ease);
354
+ opacity: 1;
355
+ scale: 1;
335
356
  }
336
- @keyframes glyph-expand {
337
- from {
357
+ @starting-style {
358
+ .expand-panel:popover-open {
338
359
  opacity: 0;
339
- transform: scale(0.6);
360
+ scale: var(--enter-scale);
340
361
  }
341
362
  }
342
363
  .tile {
@@ -344,13 +365,16 @@
344
365
  align-items: center;
345
366
  justify-content: center;
346
367
  padding: var(--sp-1);
347
- border-radius: var(--r-sm);
368
+ border-radius: calc(var(--r-md) - var(--sp-1) - 1px);
348
369
  background: var(--surface);
349
- transition: background 0.08s var(--ease);
370
+ transition:
371
+ background var(--dur-fast) var(--ease),
372
+ box-shadow var(--dur-fast) var(--ease);
350
373
  }
351
374
  .tile.armed {
352
375
  background: color-mix(in srgb, var(--accent) 18%, var(--surface));
353
376
  box-shadow: inset 0 0 0 2px var(--accent);
377
+ transition-duration: var(--dur-instant);
354
378
  }
355
379
  .tile-body {
356
380
  display: inline-flex;
@@ -26,8 +26,7 @@
26
26
  tone?: Tone | 'none';
27
27
  // Outlined `--box-lg` square icon-chip (header/toolbar actions).
28
28
  chip?: boolean;
29
- // Shared square box scale (`--box-xs/sm/md/lg`); default `md` is the classic
30
- // 2.25rem box. Forwarded to Button.
29
+ // Shared square box scale (`--box-xs/sm/md/lg`). Forwarded to Button.
31
30
  box?: 'xs' | 'sm' | 'md' | 'lg';
32
31
  // SVG glyph px. Emoji text glyphs render at ×1.35 of it.
33
32
  size?: number;
@@ -63,6 +62,8 @@
63
62
  * in the accessible name. */
64
63
  count?: number;
65
64
  countMax?: number;
65
+ /** Play UI sounds when enabled globally; false silences this instance. */
66
+ sound?: boolean;
66
67
  class?: string;
67
68
  };
68
69
 
@@ -90,6 +91,7 @@
90
91
  loading = false,
91
92
  count,
92
93
  countMax,
94
+ sound = true,
93
95
  disabled = false,
94
96
  onclick,
95
97
  class: klass = '',
@@ -118,6 +120,7 @@
118
120
  {loading}
119
121
  {count}
120
122
  {countMax}
123
+ {sound}
121
124
  {title}
122
125
  {onclick}
123
126
  icon={!inline && !chip && !showLabel}
@@ -166,6 +169,6 @@
166
169
  display: inline-flex;
167
170
  align-items: center;
168
171
  justify-content: center;
169
- line-height: 1;
172
+ line-height: var(--lh-none);
170
173
  }
171
174
  </style>
@@ -40,6 +40,8 @@ type IconButtonProps = HTMLButtonAttributes & {
40
40
  * in the accessible name. */
41
41
  count?: number;
42
42
  countMax?: number;
43
+ /** Play UI sounds when enabled globally; false silences this instance. */
44
+ sound?: boolean;
43
45
  class?: string;
44
46
  };
45
47
  declare const IconButton: import("svelte").Component<IconButtonProps, {}, "">;
@@ -113,9 +113,9 @@
113
113
  full width and the scrollbar ends above the controls. */
114
114
  .ig {
115
115
  --ig-h: var(--control-height-default);
116
- --ig-pad-x: var(--sp-3);
116
+ --ig-pad-x: var(--control-px-md);
117
117
  --ig-gap: var(--sp-2);
118
- --ig-inset: 2px;
118
+ --ig-inset: var(--sp-0-5);
119
119
  --ig-radius: var(--r-md);
120
120
  position: relative;
121
121
  display: flex;
@@ -123,12 +123,12 @@
123
123
  }
124
124
  .ig-sm {
125
125
  --ig-h: var(--control-height-compact);
126
- --ig-pad-x: var(--sp-2);
126
+ --ig-pad-x: var(--control-px-sm);
127
127
  --ig-gap: var(--sp-1);
128
128
  }
129
129
  .ig-lg {
130
130
  --ig-h: var(--control-height-large);
131
- --ig-pad-x: var(--sp-4);
131
+ --ig-pad-x: var(--control-px-lg);
132
132
  }
133
133
  .ig-field {
134
134
  width: 100%;
@@ -138,10 +138,10 @@
138
138
  following the field's; inner corners go square. In the bar they hug the
139
139
  bottom and side borders, so only the bottom-outer corner is rounded. */
140
140
  .ig-adorn {
141
- --ig-fuse-r: calc(var(--ig-radius) - 1px);
141
+ --ig-fuse-r: calc(var(--ig-radius) - var(--sp-px));
142
142
  position: absolute;
143
- top: 1px;
144
- bottom: 1px;
143
+ top: var(--sp-px);
144
+ bottom: var(--sp-px);
145
145
  z-index: 1;
146
146
  display: flex;
147
147
  align-items: center;
@@ -149,10 +149,10 @@
149
149
  pointer-events: none;
150
150
  }
151
151
  .ig-leading {
152
- inset-inline-start: 1px;
152
+ inset-inline-start: var(--sp-px);
153
153
  }
154
154
  .ig-trailing {
155
- inset-inline-end: 1px;
155
+ inset-inline-end: var(--sp-px);
156
156
  }
157
157
  .ig-adorn > :global(*) {
158
158
  pointer-events: auto;
@@ -197,7 +197,7 @@
197
197
  }
198
198
  .ig-bar .ig-adorn {
199
199
  top: auto;
200
- height: calc(var(--ig-h) - 1px);
200
+ height: calc(var(--ig-h) - var(--sp-px));
201
201
  }
202
202
  .ig-bar .ig-leading > :global(:first-child) {
203
203
  --btn-radius: 0 0 0 var(--ig-fuse-r);
@@ -225,7 +225,7 @@
225
225
  border: 1px solid var(--textarea-border, var(--border-strong));
226
226
  border-top: 0;
227
227
  border-radius: 0 0 var(--ig-radius) var(--ig-radius);
228
- transition: border-color 0.12s var(--ease);
228
+ transition: border-color var(--dur-fast) ease;
229
229
  pointer-events: none;
230
230
  }
231
231
  .ig-bar:has(.ig-field :global(:focus))::before {
@@ -244,7 +244,8 @@
244
244
  pointer-events: none;
245
245
  }
246
246
  .ig.disabled {
247
- opacity: 0.45;
247
+ opacity: var(--disabled-opacity);
248
+ cursor: not-allowed;
248
249
  }
249
250
  .ig.disabled .ig-adorn > :global(:disabled) {
250
251
  opacity: 1;