@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
@@ -7,6 +7,7 @@
7
7
  // Recolor per-instance via `--slider-accent` (defaults to the theme accent).
8
8
  import type { HTMLInputAttributes } from 'svelte/elements';
9
9
  import { getFieldContext, warnUnlabelled } from '../../field-context';
10
+ import { playSound } from '../../sound';
10
11
 
11
12
  let {
12
13
  value = $bindable(0),
@@ -23,6 +24,11 @@
23
24
  ticks = false,
24
25
  marks = [],
25
26
  width,
27
+ sound = true,
28
+ oninput,
29
+ onpointerdown,
30
+ onpointerup,
31
+ onpointercancel,
26
32
  class: klass = '',
27
33
  el = $bindable(null),
28
34
  ...rest
@@ -43,6 +49,8 @@
43
49
  marks?: { value: number; label: string }[];
44
50
  /** Fixed width (`flex: none`). */
45
51
  width?: string;
52
+ /** Play UI sounds when enabled globally; false silences this instance. */
53
+ sound?: boolean;
46
54
  id?: string;
47
55
  'aria-describedby'?: string | null;
48
56
  'aria-invalid'?: HTMLInputAttributes['aria-invalid'];
@@ -60,6 +68,32 @@
60
68
 
61
69
  $effect(() => warnUnlabelled(el, 'Slider'));
62
70
 
71
+ let dragging = false;
72
+ const discrete = $derived((+max - +min) / (+step || 1) <= 20);
73
+ function tick() {
74
+ if (!dragging || discrete) playSound('tick', sound);
75
+ }
76
+ function handleInput(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
77
+ tick();
78
+ oninput?.(e);
79
+ }
80
+ function handlePointerDown(e: PointerEvent & { currentTarget: EventTarget & HTMLInputElement }) {
81
+ dragging = true;
82
+ onpointerdown?.(e);
83
+ }
84
+ function handlePointerUp(e: PointerEvent & { currentTarget: EventTarget & HTMLInputElement }) {
85
+ dragging = false;
86
+ onpointerup?.(e);
87
+ }
88
+ function handlePointerCancel(e: PointerEvent & { currentTarget: EventTarget & HTMLInputElement }) {
89
+ dragging = false;
90
+ onpointercancel?.(e);
91
+ }
92
+ function pick(v: number) {
93
+ value = v;
94
+ playSound('tick', sound);
95
+ }
96
+
63
97
  const frac = (v: number) => Math.max(0, Math.min(1, (v - +min) / (+max - +min || 1)));
64
98
  const pct = $derived(frac(Number(value)) * 100);
65
99
  const tickValues = $derived.by(() => {
@@ -84,6 +118,10 @@
84
118
  aria-label={label}
85
119
  aria-valuetext={valueText}
86
120
  {...rest}
121
+ oninput={handleInput}
122
+ onpointerdown={handlePointerDown}
123
+ onpointerup={handlePointerUp}
124
+ onpointercancel={handlePointerCancel}
87
125
  aria-describedby={ariaDescribedby ?? field?.describedBy}
88
126
  aria-invalid={ariaInvalid ?? (isInvalid ? 'true' : undefined)}
89
127
  />
@@ -101,7 +139,7 @@
101
139
  {#if marks.length > 0}
102
140
  <span class="marks" aria-hidden="true">
103
141
  {#each marks as m, i (`${i}:${m.value}`)}
104
- <button type="button" class="mark" class:on={Number(value) === m.value} style="--f: {frac(m.value)}" tabindex="-1" onclick={() => (value = m.value)}>{m.label}</button>
142
+ <button type="button" class="mark" class:on={Number(value) === m.value} style="--f: {frac(m.value)}" tabindex="-1" onclick={() => pick(m.value)}>{m.label}</button>
105
143
  {/each}
106
144
  </span>
107
145
  {/if}
@@ -138,6 +176,9 @@
138
176
  color: var(--text);
139
177
  }
140
178
  .slider {
179
+ --thumb: 1.2rem;
180
+ --track: 0.35rem;
181
+ --halo: 0 0 0 4px color-mix(in srgb, var(--slider-accent, var(--accent)) 20%, transparent);
141
182
  display: flex;
142
183
  align-items: center;
143
184
  gap: var(--sp-3);
@@ -164,7 +205,7 @@
164
205
  }
165
206
  /* Track — WebKit/Blink. Filled up to --pct with the accent, then the rail. */
166
207
  input[type='range']::-webkit-slider-runnable-track {
167
- height: 0.35rem;
208
+ height: var(--track);
168
209
  border-radius: var(--r-pill);
169
210
  background: linear-gradient(
170
211
  to right,
@@ -173,12 +214,12 @@
173
214
  );
174
215
  }
175
216
  input[type='range']::-moz-range-track {
176
- height: 0.35rem;
217
+ height: var(--track);
177
218
  border-radius: var(--r-pill);
178
219
  background: var(--bg-elevated-2);
179
220
  }
180
221
  input[type='range']::-moz-range-progress {
181
- height: 0.35rem;
222
+ height: var(--track);
182
223
  border-radius: var(--r-pill);
183
224
  background: var(--slider-accent, var(--accent));
184
225
  }
@@ -186,36 +227,52 @@
186
227
  input[type='range']::-webkit-slider-thumb {
187
228
  appearance: none;
188
229
  -webkit-appearance: none;
189
- margin-top: -0.425rem; /* center on the 0.35rem track */
190
- width: 1.2rem;
191
- height: 1.2rem;
192
- border-radius: 50%;
230
+ margin-top: calc((var(--track) - var(--thumb)) / 2);
231
+ width: var(--thumb);
232
+ height: var(--thumb);
233
+ border-radius: var(--r-pill);
193
234
  background: var(--slider-accent, var(--accent));
194
235
  border: 2px solid var(--bg);
195
236
  box-shadow: var(--shadow-sm);
196
- transition: transform 0.1s var(--ease);
237
+ transition:
238
+ scale var(--dur-press) var(--ease-out),
239
+ box-shadow var(--dur-fast) var(--ease);
197
240
  }
198
241
  input[type='range']::-moz-range-thumb {
199
- width: 1.2rem;
200
- height: 1.2rem;
201
- border-radius: 50%;
242
+ width: var(--thumb);
243
+ height: var(--thumb);
244
+ border-radius: var(--r-pill);
202
245
  background: var(--slider-accent, var(--accent));
203
246
  border: 2px solid var(--bg);
204
247
  box-shadow: var(--shadow-sm);
248
+ transition:
249
+ scale var(--dur-press) var(--ease-out),
250
+ box-shadow var(--dur-fast) var(--ease);
205
251
  }
206
- input[type='range']:active::-webkit-slider-thumb {
207
- transform: scale(1.12);
252
+ @media (hover: hover) and (pointer: fine) {
253
+ input[type='range']:not(:disabled):hover::-webkit-slider-thumb {
254
+ box-shadow: var(--shadow-sm), var(--halo);
255
+ }
256
+ input[type='range']:not(:disabled):hover::-moz-range-thumb {
257
+ box-shadow: var(--shadow-sm), var(--halo);
258
+ }
259
+ }
260
+ input[type='range']:not(:disabled):active::-webkit-slider-thumb {
261
+ scale: 1.15;
262
+ }
263
+ input[type='range']:not(:disabled):active::-moz-range-thumb {
264
+ scale: 1.15;
208
265
  }
209
266
  input[type='range']:focus-visible {
210
267
  outline: none;
211
268
  }
212
269
  input[type='range']:focus-visible::-webkit-slider-thumb {
213
270
  outline: var(--focus-ring);
214
- outline-offset: 2px;
271
+ outline-offset: var(--focus-ring-offset);
215
272
  }
216
273
  input[type='range']:focus-visible::-moz-range-thumb {
217
274
  outline: var(--focus-ring);
218
- outline-offset: 2px;
275
+ outline-offset: var(--focus-ring-offset);
219
276
  }
220
277
  @media (pointer: coarse) {
221
278
  input[type='range'] {
@@ -223,7 +280,7 @@
223
280
  }
224
281
  }
225
282
  input[type='range']:disabled {
226
- opacity: 0.45;
283
+ opacity: var(--disabled-opacity);
227
284
  cursor: not-allowed;
228
285
  }
229
286
  .ticks {
@@ -235,11 +292,11 @@
235
292
  .tick {
236
293
  position: absolute;
237
294
  top: 50%;
238
- left: calc(0.6rem + (100% - 1.2rem) * var(--f));
239
- width: 0.35rem;
240
- height: 0.35rem;
241
- margin: -0.175rem 0 0 -0.175rem;
242
- border-radius: 50%;
295
+ left: calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--f));
296
+ width: var(--track);
297
+ height: var(--track);
298
+ margin: calc(var(--track) / -2) 0 0 calc(var(--track) / -2);
299
+ border-radius: var(--r-pill);
243
300
  background: var(--border-strong);
244
301
  }
245
302
  .tick.reached {
@@ -19,6 +19,8 @@ type $$ComponentProps = HTMLInputAttributes & {
19
19
  }[];
20
20
  /** Fixed width (`flex: none`). */
21
21
  width?: string;
22
+ /** Play UI sounds when enabled globally; false silences this instance. */
23
+ sound?: boolean;
22
24
  id?: string;
23
25
  'aria-describedby'?: string | null;
24
26
  'aria-invalid'?: HTMLInputAttributes['aria-invalid'];
@@ -1,15 +1,46 @@
1
+ <script lang="ts" module>
2
+ export type SpinnerVariant =
3
+ | 'icon'
4
+ | 'arc'
5
+ | 'dots'
6
+ | 'orbit'
7
+ | 'pulse'
8
+ | 'bars'
9
+ | 'grid'
10
+ | 'clock'
11
+ | 'bounce';
12
+
13
+ // Inner spans per variant; `.in` wraps the flex/grid ones in an absolute
14
+ // layer so the root keeps the same inline baseline as the others.
15
+ const PARTS: Record<Exclude<SpinnerVariant, 'icon'>, { count: number; wrap: boolean }> = {
16
+ arc: { count: 0, wrap: false },
17
+ dots: { count: 3, wrap: true },
18
+ orbit: { count: 2, wrap: false },
19
+ pulse: { count: 1, wrap: false },
20
+ bars: { count: 4, wrap: true },
21
+ grid: { count: 9, wrap: true },
22
+ clock: { count: 0, wrap: false },
23
+ bounce: { count: 2, wrap: false }
24
+ };
25
+ </script>
26
+
1
27
  <script lang="ts">
2
- // Inline loading spinner — a spinning `loader` glyph. Tracks the surrounding
3
- // text size by default (1em), or pass an explicit pixel `size`. The spin
4
- // animation is owned by Icon and is halted under prefers-reduced-motion, so
5
- // motion-sensitive users see a static glyph instead.
28
+ // Inline loading indicator. `icon` (default) spins the `loader` glyph; the
29
+ // other variants are pure-CSS pictograms drawn in currentColor inside a 1em
30
+ // box. Every variant's base style is a meaningful static pose, which is what
31
+ // shows under prefers-reduced-motion. `--spinner-speed` scales the tempo.
32
+ // `clock` and `bounce` read best at 20px and up.
6
33
  import Icon from './Icon.svelte';
7
34
 
8
35
  let {
36
+ variant = 'icon',
9
37
  size,
10
38
  label = 'Loading',
39
+ class: klass = '',
40
+ style: styleProp = '',
11
41
  ...rest
12
42
  }: {
43
+ variant?: SpinnerVariant;
13
44
  /** Explicit pixel size. Omit to track the surrounding text (1em). */
14
45
  size?: number;
15
46
  /** AT label announcing the busy state. Set to undefined to make the
@@ -19,6 +50,434 @@
19
50
  style?: string;
20
51
  [key: string]: unknown;
21
52
  } = $props();
53
+
54
+ const parts = $derived(variant === 'icon' ? undefined : PARTS[variant]);
22
55
  </script>
23
56
 
24
- <Icon name="loader" spin {size} {label} data-tsu="Spinner" {...rest} />
57
+ {#if !parts}
58
+ <Icon name="loader" spin {size} {label} data-tsu="Spinner" class={klass} style={styleProp} {...rest} />
59
+ {:else}
60
+ <span
61
+ data-tsu="Spinner"
62
+ class="spinner v-{variant} {klass}"
63
+ style="{size ? `font-size: ${size}px;` : ''}{styleProp}"
64
+ role={label ? 'img' : undefined}
65
+ aria-label={label}
66
+ aria-hidden={label ? undefined : 'true'}
67
+ {...rest}
68
+ >
69
+ {#if parts.wrap}
70
+ <span class="in">
71
+ {#each { length: parts.count } as _, i (i)}<span></span>{/each}
72
+ </span>
73
+ {:else}
74
+ {#each { length: parts.count } as _, i (i)}<span></span>{/each}
75
+ {/if}
76
+ </span>
77
+ {/if}
78
+
79
+ <style>
80
+ @property --tsu-spin-arc {
81
+ syntax: '<angle>';
82
+ inherits: false;
83
+ initial-value: 100deg;
84
+ }
85
+
86
+ .spinner {
87
+ --sw: max(1.5px, 0.11em);
88
+ --t: calc(1s * var(--spinner-speed, 1));
89
+ position: relative;
90
+ display: inline-block;
91
+ flex: none;
92
+ width: 1em;
93
+ height: 1em;
94
+ vertical-align: -0.125em;
95
+ color: inherit;
96
+ }
97
+ .spinner span,
98
+ .spinner::before,
99
+ .spinner::after {
100
+ box-sizing: border-box;
101
+ }
102
+ .in {
103
+ position: absolute;
104
+ inset: 0;
105
+ }
106
+
107
+ .v-arc::before,
108
+ .v-arc::after {
109
+ content: '';
110
+ position: absolute;
111
+ inset: 0;
112
+ border-radius: 50%;
113
+ background: currentColor;
114
+ -webkit-mask: radial-gradient(
115
+ farthest-side,
116
+ transparent calc(100% - var(--sw)),
117
+ #000 calc(100% - var(--sw) + 0.5px)
118
+ );
119
+ mask: radial-gradient(
120
+ farthest-side,
121
+ transparent calc(100% - var(--sw)),
122
+ #000 calc(100% - var(--sw) + 0.5px)
123
+ );
124
+ }
125
+ .v-arc::before {
126
+ opacity: 0.18;
127
+ }
128
+ .v-arc::after {
129
+ --tsu-spin-arc: 100deg;
130
+ -webkit-mask:
131
+ radial-gradient(farthest-side, transparent calc(100% - var(--sw)), #000 calc(100% - var(--sw) + 0.5px)),
132
+ conic-gradient(#000 var(--tsu-spin-arc), transparent 0);
133
+ -webkit-mask-composite: source-in;
134
+ mask:
135
+ radial-gradient(farthest-side, transparent calc(100% - var(--sw)), #000 calc(100% - var(--sw) + 0.5px))
136
+ intersect,
137
+ conic-gradient(#000 var(--tsu-spin-arc), transparent 0);
138
+ mask-composite: intersect;
139
+ animation:
140
+ tsu-spin-arc calc(var(--t) * 1.4) cubic-bezier(0.5, 0, 0.5, 1) infinite,
141
+ tsu-spin-rot calc(var(--t) * 1.4) linear infinite;
142
+ }
143
+ @keyframes tsu-spin-arc {
144
+ 0% {
145
+ --tsu-spin-arc: 20deg;
146
+ }
147
+ 50% {
148
+ --tsu-spin-arc: 280deg;
149
+ }
150
+ 100% {
151
+ --tsu-spin-arc: 20deg;
152
+ }
153
+ }
154
+ @keyframes tsu-spin-rot {
155
+ 0% {
156
+ transform: rotate(0deg);
157
+ }
158
+ 50% {
159
+ transform: rotate(160deg);
160
+ }
161
+ 100% {
162
+ transform: rotate(720deg);
163
+ }
164
+ }
165
+
166
+ .v-dots .in {
167
+ display: flex;
168
+ align-items: center;
169
+ justify-content: space-between;
170
+ padding: 0 0.04em;
171
+ }
172
+ .v-dots .in span {
173
+ width: 0.24em;
174
+ height: 0.24em;
175
+ border-radius: 50%;
176
+ background: currentColor;
177
+ animation: tsu-spin-dot calc(var(--t) * 1.1) cubic-bezier(0.45, 0, 0.55, 1) infinite;
178
+ }
179
+ .v-dots .in span:nth-child(1) {
180
+ opacity: 1;
181
+ }
182
+ .v-dots .in span:nth-child(2) {
183
+ opacity: 0.6;
184
+ animation-delay: calc(var(--t) * 0.14);
185
+ }
186
+ .v-dots .in span:nth-child(3) {
187
+ opacity: 0.32;
188
+ animation-delay: calc(var(--t) * 0.28);
189
+ }
190
+ @keyframes tsu-spin-dot {
191
+ 0%,
192
+ 60%,
193
+ 100% {
194
+ transform: translateY(0);
195
+ opacity: 0.32;
196
+ }
197
+ 25% {
198
+ transform: translateY(-0.22em);
199
+ opacity: 1;
200
+ }
201
+ }
202
+
203
+ .v-orbit::before {
204
+ content: '';
205
+ position: absolute;
206
+ inset: 0.08em;
207
+ border-radius: 50%;
208
+ border: max(1px, 0.06em) solid currentColor;
209
+ opacity: 0.22;
210
+ }
211
+ .v-orbit > span:nth-child(1) {
212
+ position: absolute;
213
+ left: 50%;
214
+ top: 50%;
215
+ width: 0.3em;
216
+ height: 0.3em;
217
+ margin: -0.15em;
218
+ border-radius: 50%;
219
+ background: currentColor;
220
+ animation: tsu-spin-core calc(var(--t) * 1.2) ease-in-out infinite;
221
+ }
222
+ .v-orbit > span:nth-child(2) {
223
+ position: absolute;
224
+ inset: 0;
225
+ transform: rotate(45deg);
226
+ animation: tsu-spin-orbit calc(var(--t) * 1.2) cubic-bezier(0.6, 0.15, 0.4, 0.85) infinite;
227
+ }
228
+ .v-orbit > span:nth-child(2)::before {
229
+ content: '';
230
+ position: absolute;
231
+ left: 50%;
232
+ top: 0;
233
+ width: 0.24em;
234
+ height: 0.24em;
235
+ margin-left: -0.12em;
236
+ border-radius: 50%;
237
+ background: currentColor;
238
+ }
239
+ @keyframes tsu-spin-orbit {
240
+ from {
241
+ transform: rotate(45deg);
242
+ }
243
+ to {
244
+ transform: rotate(405deg);
245
+ }
246
+ }
247
+ @keyframes tsu-spin-core {
248
+ 0%,
249
+ 100% {
250
+ transform: scale(1);
251
+ }
252
+ 50% {
253
+ transform: scale(0.7);
254
+ }
255
+ }
256
+
257
+ .v-pulse > span {
258
+ position: absolute;
259
+ left: 50%;
260
+ top: 50%;
261
+ width: 0.3em;
262
+ height: 0.3em;
263
+ margin: -0.15em;
264
+ border-radius: 50%;
265
+ background: currentColor;
266
+ }
267
+ .v-pulse::before,
268
+ .v-pulse::after {
269
+ content: '';
270
+ position: absolute;
271
+ inset: 0;
272
+ border-radius: 50%;
273
+ border: var(--sw) solid currentColor;
274
+ transform: scale(0.72);
275
+ opacity: 0.45;
276
+ animation: tsu-spin-ripple calc(var(--t) * 1.6) cubic-bezier(0.2, 0.6, 0.35, 1) infinite;
277
+ }
278
+ .v-pulse::after {
279
+ animation-delay: calc(var(--t) * -0.8);
280
+ }
281
+ @keyframes tsu-spin-ripple {
282
+ 0% {
283
+ transform: scale(0.3);
284
+ opacity: 1;
285
+ }
286
+ 100% {
287
+ transform: scale(1);
288
+ opacity: 0;
289
+ }
290
+ }
291
+
292
+ .v-bars .in {
293
+ display: flex;
294
+ align-items: center;
295
+ justify-content: space-between;
296
+ padding: 0.06em 0.08em;
297
+ }
298
+ .v-bars .in span {
299
+ width: 0.13em;
300
+ height: 100%;
301
+ border-radius: 0.1em;
302
+ background: currentColor;
303
+ transform-origin: 50% 50%;
304
+ animation: tsu-spin-bar var(--t) ease-in-out infinite;
305
+ }
306
+ .v-bars .in span:nth-child(1) {
307
+ transform: scaleY(0.4);
308
+ animation-delay: calc(var(--t) * -0.9);
309
+ }
310
+ .v-bars .in span:nth-child(2) {
311
+ transform: scaleY(0.85);
312
+ animation-delay: calc(var(--t) * -0.6);
313
+ }
314
+ .v-bars .in span:nth-child(3) {
315
+ transform: scaleY(0.6);
316
+ animation-delay: calc(var(--t) * -0.3);
317
+ }
318
+ .v-bars .in span:nth-child(4) {
319
+ transform: scaleY(1);
320
+ animation-delay: calc(var(--t) * -0.75);
321
+ }
322
+ @keyframes tsu-spin-bar {
323
+ 0%,
324
+ 100% {
325
+ transform: scaleY(0.3);
326
+ }
327
+ 50% {
328
+ transform: scaleY(1);
329
+ }
330
+ }
331
+
332
+ .v-grid .in {
333
+ display: grid;
334
+ grid-template-columns: repeat(3, 1fr);
335
+ place-items: center;
336
+ padding: 0.04em;
337
+ }
338
+ .v-grid .in span {
339
+ width: 0.2em;
340
+ height: 0.2em;
341
+ border-radius: 0.05em;
342
+ background: currentColor;
343
+ opacity: calc(1 - var(--d) * 0.18);
344
+ animation: tsu-spin-cell calc(var(--t) * 1.2) ease-in-out infinite;
345
+ animation-delay: calc(var(--t) * 0.1 * var(--d) - var(--t) * 1.2);
346
+ }
347
+ .v-grid .in span:nth-child(1) {
348
+ --d: 0;
349
+ }
350
+ .v-grid .in span:nth-child(2),
351
+ .v-grid .in span:nth-child(4) {
352
+ --d: 1;
353
+ }
354
+ .v-grid .in span:nth-child(3),
355
+ .v-grid .in span:nth-child(5),
356
+ .v-grid .in span:nth-child(7) {
357
+ --d: 2;
358
+ }
359
+ .v-grid .in span:nth-child(6),
360
+ .v-grid .in span:nth-child(8) {
361
+ --d: 3;
362
+ }
363
+ .v-grid .in span:nth-child(9) {
364
+ --d: 4;
365
+ }
366
+ @keyframes tsu-spin-cell {
367
+ 0%,
368
+ 100% {
369
+ opacity: 0.2;
370
+ transform: scale(0.7);
371
+ }
372
+ 35% {
373
+ opacity: 1;
374
+ transform: scale(1);
375
+ }
376
+ }
377
+
378
+ .v-clock {
379
+ border-radius: 50%;
380
+ border: var(--sw) solid currentColor;
381
+ }
382
+ .v-clock::before,
383
+ .v-clock::after {
384
+ content: '';
385
+ position: absolute;
386
+ left: 50%;
387
+ bottom: 50%;
388
+ width: var(--sw);
389
+ margin-left: calc(var(--sw) / -2);
390
+ border-radius: var(--sw);
391
+ background: currentColor;
392
+ transform-origin: 50% 100%;
393
+ translate: 0 calc(var(--sw) / 2);
394
+ }
395
+ .v-clock::before {
396
+ height: 0.32em;
397
+ transform: rotate(0deg);
398
+ animation: tsu-spin-hand-m var(--t) linear infinite;
399
+ }
400
+ .v-clock::after {
401
+ height: 0.22em;
402
+ transform: rotate(90deg);
403
+ animation: tsu-spin-hand-h calc(var(--t) * 12) linear infinite;
404
+ }
405
+ @keyframes tsu-spin-hand-m {
406
+ from {
407
+ transform: rotate(0deg);
408
+ }
409
+ to {
410
+ transform: rotate(360deg);
411
+ }
412
+ }
413
+ @keyframes tsu-spin-hand-h {
414
+ from {
415
+ transform: rotate(90deg);
416
+ }
417
+ to {
418
+ transform: rotate(450deg);
419
+ }
420
+ }
421
+
422
+ .v-bounce > span:nth-child(1) {
423
+ position: absolute;
424
+ left: 50%;
425
+ bottom: 0.16em;
426
+ width: 0.34em;
427
+ height: 0.34em;
428
+ margin-left: -0.17em;
429
+ border-radius: 50%;
430
+ background: currentColor;
431
+ transform-origin: 50% 100%;
432
+ animation: tsu-spin-ball calc(var(--t) * 0.9) infinite;
433
+ }
434
+ .v-bounce > span:nth-child(2) {
435
+ position: absolute;
436
+ left: 50%;
437
+ bottom: 0.06em;
438
+ width: 0.46em;
439
+ height: 0.08em;
440
+ margin-left: -0.23em;
441
+ border-radius: 50%;
442
+ background: currentColor;
443
+ opacity: 0.3;
444
+ animation: tsu-spin-shadow calc(var(--t) * 0.9) infinite;
445
+ }
446
+ @keyframes tsu-spin-ball {
447
+ 0% {
448
+ transform: translateY(0) scale(1.18, 0.82);
449
+ animation-timing-function: cubic-bezier(0.2, 0.7, 0.4, 1);
450
+ }
451
+ 12% {
452
+ transform: translateY(0) scale(1, 1);
453
+ animation-timing-function: cubic-bezier(0.2, 0.7, 0.4, 1);
454
+ }
455
+ 50% {
456
+ transform: translateY(-0.5em) scale(0.95, 1.05);
457
+ animation-timing-function: cubic-bezier(0.6, 0, 0.8, 0.3);
458
+ }
459
+ 100% {
460
+ transform: translateY(0) scale(1.18, 0.82);
461
+ }
462
+ }
463
+ @keyframes tsu-spin-shadow {
464
+ 0%,
465
+ 100% {
466
+ transform: scaleX(1);
467
+ opacity: 0.35;
468
+ }
469
+ 50% {
470
+ transform: scaleX(0.5);
471
+ opacity: 0.12;
472
+ }
473
+ }
474
+
475
+ @media (prefers-reduced-motion: reduce) {
476
+ .spinner.spinner,
477
+ .spinner.spinner span,
478
+ .spinner.spinner::before,
479
+ .spinner.spinner::after {
480
+ animation: none;
481
+ }
482
+ }
483
+ </style>
@@ -1,4 +1,6 @@
1
+ export type SpinnerVariant = 'icon' | 'arc' | 'dots' | 'orbit' | 'pulse' | 'bars' | 'grid' | 'clock' | 'bounce';
1
2
  type $$ComponentProps = {
3
+ variant?: SpinnerVariant;
2
4
  /** Explicit pixel size. Omit to track the surrounding text (1em). */
3
5
  size?: number;
4
6
  /** AT label announcing the busy state. Set to undefined to make the