@dorsk/tsumikit 0.79.0 → 0.80.1
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.
- package/dist/components/atoms/Artwork.svelte +14 -10
- package/dist/components/atoms/Artwork.svelte.d.ts +1 -1
- package/dist/components/atoms/Badge.svelte +1 -4
- package/dist/components/atoms/Button.svelte +303 -9
- package/dist/components/atoms/Button.svelte.d.ts +4 -1
- package/dist/components/atoms/Card.svelte +142 -0
- package/dist/components/atoms/Checkbox.svelte +177 -1
- package/dist/components/atoms/Input.svelte +35 -0
- package/dist/components/atoms/Link.svelte +61 -0
- package/dist/components/atoms/Progress.svelte +74 -1
- package/dist/components/atoms/Progress.svelte.d.ts +1 -0
- package/dist/components/atoms/Select.svelte +30 -0
- package/dist/components/atoms/Skeleton.svelte +54 -3
- package/dist/components/atoms/Skeleton.svelte.d.ts +1 -0
- package/dist/components/atoms/Spinner.svelte +152 -3
- package/dist/components/atoms/Spinner.svelte.d.ts +1 -1
- package/dist/components/atoms/Switch.svelte +135 -0
- package/dist/components/atoms/Textarea.svelte +36 -0
- package/dist/components/atoms/Thinking.svelte +130 -0
- package/dist/components/atoms/Thinking.svelte.d.ts +15 -0
- package/dist/components/molecules/CopyButton.svelte +146 -5
- package/dist/components/molecules/Field.svelte +55 -1
- package/dist/components/molecules/FileButton.svelte +4 -2
- package/dist/components/molecules/InputGroup.svelte +1 -1
- package/dist/components/molecules/LookPicker.svelte +100 -0
- package/dist/components/molecules/LookPicker.svelte.d.ts +17 -0
- package/dist/components/molecules/MotionSettings.svelte +166 -0
- package/dist/components/molecules/MotionSettings.svelte.d.ts +18 -0
- package/dist/components/molecules/RadioGroup.svelte +61 -0
- package/dist/components/molecules/SectionHeader.svelte +2 -0
- package/dist/components/molecules/Tabs.svelte +157 -4
- package/dist/components/molecules/ThemePicker.svelte +8 -3
- package/dist/fx.d.ts +149 -0
- package/dist/fx.js +111 -0
- package/dist/fx.md +58 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +8 -2
- package/dist/looks/README.md +101 -0
- package/dist/looks/depth.css +939 -0
- package/dist/looks/editorial.css +545 -0
- package/dist/looks/tactile.css +871 -0
- package/dist/markdown.js +47 -21
- package/dist/pointer.d.ts +3 -0
- package/dist/pointer.js +40 -0
- package/dist/stores/fx.svelte.d.ts +17 -0
- package/dist/stores/fx.svelte.js +64 -0
- package/dist/stores/theme.svelte.d.ts +8 -2
- package/dist/stores/theme.svelte.js +33 -7
- package/dist/styles/themes.css +249 -249
- package/dist/styles/tokens.css +4 -3
- package/dist/theme-mode.d.ts +31 -0
- package/dist/theme-mode.js +19 -0
- package/package.json +3 -2
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
| 'bars'
|
|
9
9
|
| 'grid'
|
|
10
10
|
| 'clock'
|
|
11
|
-
| 'bounce'
|
|
11
|
+
| 'bounce'
|
|
12
|
+
| 'comet'
|
|
13
|
+
| 'breath'
|
|
14
|
+
| 'morph'
|
|
15
|
+
| 'sweep';
|
|
12
16
|
|
|
13
17
|
// Inner spans per variant; `.in` wraps the flex/grid ones in an absolute
|
|
14
18
|
// layer so the root keeps the same inline baseline as the others.
|
|
@@ -20,7 +24,11 @@
|
|
|
20
24
|
bars: { count: 4, wrap: true },
|
|
21
25
|
grid: { count: 9, wrap: true },
|
|
22
26
|
clock: { count: 0, wrap: false },
|
|
23
|
-
bounce: { count: 2, wrap: false }
|
|
27
|
+
bounce: { count: 2, wrap: false },
|
|
28
|
+
comet: { count: 1, wrap: false },
|
|
29
|
+
breath: { count: 5, wrap: true },
|
|
30
|
+
morph: { count: 0, wrap: false },
|
|
31
|
+
sweep: { count: 0, wrap: false }
|
|
24
32
|
};
|
|
25
33
|
</script>
|
|
26
34
|
|
|
@@ -29,7 +37,7 @@
|
|
|
29
37
|
// other variants are pure-CSS pictograms drawn in currentColor inside a 1em
|
|
30
38
|
// box. Every variant's base style is a meaningful static pose, which is what
|
|
31
39
|
// shows under prefers-reduced-motion. `--spinner-speed` scales the tempo.
|
|
32
|
-
// `clock` and `bounce` read best at 20px and up.
|
|
40
|
+
// `clock` and `bounce` read best at 20px and up; `breath` is 2.5em wide.
|
|
33
41
|
import Icon from './Icon.svelte';
|
|
34
42
|
|
|
35
43
|
let {
|
|
@@ -82,6 +90,11 @@
|
|
|
82
90
|
inherits: false;
|
|
83
91
|
initial-value: 100deg;
|
|
84
92
|
}
|
|
93
|
+
@property --tsu-spin-sweep {
|
|
94
|
+
syntax: '<angle>';
|
|
95
|
+
inherits: false;
|
|
96
|
+
initial-value: 0deg;
|
|
97
|
+
}
|
|
85
98
|
|
|
86
99
|
.spinner {
|
|
87
100
|
--sw: max(1.5px, 0.11em);
|
|
@@ -472,6 +485,142 @@
|
|
|
472
485
|
}
|
|
473
486
|
}
|
|
474
487
|
|
|
488
|
+
.v-comet::before,
|
|
489
|
+
.v-comet > span,
|
|
490
|
+
.v-sweep::before {
|
|
491
|
+
content: '';
|
|
492
|
+
position: absolute;
|
|
493
|
+
inset: 0;
|
|
494
|
+
border-radius: 50%;
|
|
495
|
+
-webkit-mask: radial-gradient(
|
|
496
|
+
farthest-side,
|
|
497
|
+
transparent calc(100% - var(--sw)),
|
|
498
|
+
#000 calc(100% - var(--sw) + 0.5px)
|
|
499
|
+
);
|
|
500
|
+
mask: radial-gradient(
|
|
501
|
+
farthest-side,
|
|
502
|
+
transparent calc(100% - var(--sw)),
|
|
503
|
+
#000 calc(100% - var(--sw) + 0.5px)
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
.v-comet::before {
|
|
507
|
+
background: currentColor;
|
|
508
|
+
opacity: 0.1;
|
|
509
|
+
}
|
|
510
|
+
.v-comet > span {
|
|
511
|
+
background: conic-gradient(from 0deg, transparent 0 35%, currentColor 100%);
|
|
512
|
+
animation: tsu-spin-comet calc(var(--t) * 1.1) linear infinite;
|
|
513
|
+
}
|
|
514
|
+
.v-comet > span::after {
|
|
515
|
+
content: '';
|
|
516
|
+
position: absolute;
|
|
517
|
+
top: 0;
|
|
518
|
+
left: 50%;
|
|
519
|
+
width: var(--sw);
|
|
520
|
+
height: var(--sw);
|
|
521
|
+
border-radius: 50%;
|
|
522
|
+
background: currentColor;
|
|
523
|
+
box-shadow: 0 0 0.25em 0.04em currentColor;
|
|
524
|
+
transform: translateX(-50%);
|
|
525
|
+
}
|
|
526
|
+
@keyframes tsu-spin-comet {
|
|
527
|
+
to {
|
|
528
|
+
transform: rotate(360deg);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.v-sweep::before {
|
|
533
|
+
background: conic-gradient(
|
|
534
|
+
from var(--tsu-spin-sweep),
|
|
535
|
+
color-mix(in srgb, currentColor 18%, transparent),
|
|
536
|
+
currentColor 50%,
|
|
537
|
+
color-mix(in srgb, currentColor 18%, transparent)
|
|
538
|
+
);
|
|
539
|
+
animation: tsu-spin-sweep calc(var(--t) * 1.4) linear infinite;
|
|
540
|
+
}
|
|
541
|
+
@keyframes tsu-spin-sweep {
|
|
542
|
+
to {
|
|
543
|
+
--tsu-spin-sweep: 360deg;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.v-morph::before {
|
|
548
|
+
content: '';
|
|
549
|
+
position: absolute;
|
|
550
|
+
inset: 0.08em;
|
|
551
|
+
border-radius: 50%;
|
|
552
|
+
background: currentColor;
|
|
553
|
+
animation: tsu-spin-morph calc(var(--t) * 2.4) cubic-bezier(0.77, 0, 0.175, 1) infinite;
|
|
554
|
+
}
|
|
555
|
+
@keyframes tsu-spin-morph {
|
|
556
|
+
0% {
|
|
557
|
+
border-radius: 50%;
|
|
558
|
+
transform: rotate(0deg) scale(1);
|
|
559
|
+
}
|
|
560
|
+
25% {
|
|
561
|
+
border-radius: 24%;
|
|
562
|
+
transform: rotate(90deg) scale(0.92);
|
|
563
|
+
}
|
|
564
|
+
50% {
|
|
565
|
+
border-radius: 50%;
|
|
566
|
+
transform: rotate(180deg) scale(1);
|
|
567
|
+
}
|
|
568
|
+
75% {
|
|
569
|
+
border-radius: 24%;
|
|
570
|
+
transform: rotate(270deg) scale(0.92);
|
|
571
|
+
}
|
|
572
|
+
100% {
|
|
573
|
+
border-radius: 50%;
|
|
574
|
+
transform: rotate(360deg) scale(1);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.v-breath {
|
|
579
|
+
width: 2.5em;
|
|
580
|
+
}
|
|
581
|
+
.v-breath .in {
|
|
582
|
+
display: flex;
|
|
583
|
+
align-items: center;
|
|
584
|
+
justify-content: space-between;
|
|
585
|
+
}
|
|
586
|
+
.v-breath .in span {
|
|
587
|
+
width: 0.34em;
|
|
588
|
+
height: 0.34em;
|
|
589
|
+
border-radius: 50%;
|
|
590
|
+
background: currentColor;
|
|
591
|
+
animation: tsu-spin-breath calc(var(--t) * 1.3) cubic-bezier(0.77, 0, 0.175, 1) infinite;
|
|
592
|
+
}
|
|
593
|
+
.v-breath .in span:nth-child(1) {
|
|
594
|
+
opacity: 1;
|
|
595
|
+
animation-delay: calc(var(--t) * -0.52);
|
|
596
|
+
}
|
|
597
|
+
.v-breath .in span:nth-child(2) {
|
|
598
|
+
opacity: 0.75;
|
|
599
|
+
animation-delay: calc(var(--t) * -0.39);
|
|
600
|
+
}
|
|
601
|
+
.v-breath .in span:nth-child(3) {
|
|
602
|
+
opacity: 0.55;
|
|
603
|
+
animation-delay: calc(var(--t) * -0.26);
|
|
604
|
+
}
|
|
605
|
+
.v-breath .in span:nth-child(4) {
|
|
606
|
+
opacity: 0.4;
|
|
607
|
+
animation-delay: calc(var(--t) * -0.13);
|
|
608
|
+
}
|
|
609
|
+
.v-breath .in span:nth-child(5) {
|
|
610
|
+
opacity: 0.28;
|
|
611
|
+
}
|
|
612
|
+
@keyframes tsu-spin-breath {
|
|
613
|
+
0%,
|
|
614
|
+
100% {
|
|
615
|
+
opacity: 0.3;
|
|
616
|
+
transform: scale(0.6);
|
|
617
|
+
}
|
|
618
|
+
40% {
|
|
619
|
+
opacity: 1;
|
|
620
|
+
transform: scale(1);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
475
624
|
@media (prefers-reduced-motion: reduce) {
|
|
476
625
|
.spinner.spinner,
|
|
477
626
|
.spinner.spinner span,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SpinnerVariant = 'icon' | 'arc' | 'dots' | 'orbit' | 'pulse' | 'bars' | 'grid' | 'clock' | 'bounce';
|
|
1
|
+
export type SpinnerVariant = 'icon' | 'arc' | 'dots' | 'orbit' | 'pulse' | 'bars' | 'grid' | 'clock' | 'bounce' | 'comet' | 'breath' | 'morph' | 'sweep';
|
|
2
2
|
type $$ComponentProps = {
|
|
3
3
|
variant?: SpinnerVariant;
|
|
4
4
|
/** Explicit pixel size. Omit to track the surrounding text (1em). */
|
|
@@ -36,11 +36,13 @@
|
|
|
36
36
|
|
|
37
37
|
const field = getFieldContext();
|
|
38
38
|
const isInvalid = $derived(invalid || !!field?.invalid);
|
|
39
|
+
let armed = $state(false);
|
|
39
40
|
|
|
40
41
|
function toggle(e: MouseEvent & { currentTarget: EventTarget & HTMLButtonElement }) {
|
|
41
42
|
onclick?.(e);
|
|
42
43
|
if (e.defaultPrevented) return;
|
|
43
44
|
checked = !checked;
|
|
45
|
+
armed = true;
|
|
44
46
|
playSound(checked ? 'toggle-on' : 'toggle-off', sound);
|
|
45
47
|
}
|
|
46
48
|
</script>
|
|
@@ -53,6 +55,7 @@
|
|
|
53
55
|
class="switch {klass}"
|
|
54
56
|
class:on={checked}
|
|
55
57
|
class:switch-sm={size === 'sm'}
|
|
58
|
+
class:armed
|
|
56
59
|
role="switch"
|
|
57
60
|
aria-checked={checked}
|
|
58
61
|
id={id ?? field?.id}
|
|
@@ -78,6 +81,8 @@
|
|
|
78
81
|
/* Track and knob sit on the 4px grid: md 24 / sm 20 tall, knob = track minus
|
|
79
82
|
border and padding, travel = track width minus the knob and insets. */
|
|
80
83
|
.switch {
|
|
84
|
+
position: relative;
|
|
85
|
+
isolation: isolate;
|
|
81
86
|
--sw-h: var(--chip-md);
|
|
82
87
|
--sw-w: 2.75rem;
|
|
83
88
|
--sw-inset: calc(var(--sp-0-5) + 1px);
|
|
@@ -97,6 +102,7 @@
|
|
|
97
102
|
border-color var(--dur-fast) var(--ease);
|
|
98
103
|
}
|
|
99
104
|
.switch .knob {
|
|
105
|
+
position: relative;
|
|
100
106
|
width: var(--sw-knob);
|
|
101
107
|
height: var(--sw-knob);
|
|
102
108
|
border-radius: var(--r-pill);
|
|
@@ -122,6 +128,135 @@
|
|
|
122
128
|
scale: calc(1 + (var(--press-scale) - var(--press-scale-sm)) * 2.4) 1;
|
|
123
129
|
transition-duration: var(--dur-press);
|
|
124
130
|
}
|
|
131
|
+
/* Effects follow the closest data-fx-switch ancestor; squash is the default. */
|
|
132
|
+
.switch:not(:global([data-fx-switch]:not([data-fx-switch='squash'])) *),
|
|
133
|
+
:global([data-fx-switch='squash']) .switch:not(:global([data-fx-switch='squash'] [data-fx-switch]:not([data-fx-switch='squash'])) *) {
|
|
134
|
+
&.armed.on .knob {
|
|
135
|
+
animation: sw-squash-on var(--dur-spring) var(--ease-out);
|
|
136
|
+
}
|
|
137
|
+
&.armed:not(.on) .knob {
|
|
138
|
+
animation: sw-squash-off var(--dur-spring) var(--ease-out);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/* On and off use separate names so the animation restarts on every toggle. */
|
|
142
|
+
@keyframes sw-squash-on {
|
|
143
|
+
35% {
|
|
144
|
+
scale: 1.4 0.8;
|
|
145
|
+
}
|
|
146
|
+
70% {
|
|
147
|
+
scale: 0.86 1.1;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
@keyframes sw-squash-off {
|
|
151
|
+
35% {
|
|
152
|
+
scale: 1.4 0.8;
|
|
153
|
+
}
|
|
154
|
+
70% {
|
|
155
|
+
scale: 0.86 1.1;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* A streak trails the knob in flight; a soft accent halo glows while on. */
|
|
160
|
+
:global([data-fx-switch='glow']) .switch:not(:global([data-fx-switch='glow'] [data-fx-switch]:not([data-fx-switch='glow'])) *) {
|
|
161
|
+
&::after {
|
|
162
|
+
content: '';
|
|
163
|
+
position: absolute;
|
|
164
|
+
inset: -40% -18%;
|
|
165
|
+
z-index: -1;
|
|
166
|
+
pointer-events: none;
|
|
167
|
+
background: radial-gradient(closest-side, color-mix(in srgb, var(--accent) 45%, transparent), transparent);
|
|
168
|
+
opacity: 0;
|
|
169
|
+
transition: opacity var(--dur-slow) var(--ease);
|
|
170
|
+
}
|
|
171
|
+
&.on::after {
|
|
172
|
+
opacity: 1;
|
|
173
|
+
}
|
|
174
|
+
& .knob::before {
|
|
175
|
+
content: '';
|
|
176
|
+
position: absolute;
|
|
177
|
+
top: 18%;
|
|
178
|
+
bottom: 18%;
|
|
179
|
+
width: 170%;
|
|
180
|
+
z-index: -1;
|
|
181
|
+
pointer-events: none;
|
|
182
|
+
border-radius: var(--r-pill);
|
|
183
|
+
opacity: 0;
|
|
184
|
+
}
|
|
185
|
+
&.on .knob::before {
|
|
186
|
+
right: 40%;
|
|
187
|
+
transform-origin: right center;
|
|
188
|
+
background: linear-gradient(to left, color-mix(in srgb, var(--text-on-accent) 75%, transparent), transparent);
|
|
189
|
+
}
|
|
190
|
+
&:not(.on) .knob::before {
|
|
191
|
+
left: 40%;
|
|
192
|
+
transform-origin: left center;
|
|
193
|
+
background: linear-gradient(to right, color-mix(in srgb, var(--accent) 80%, transparent), transparent);
|
|
194
|
+
}
|
|
195
|
+
&.armed.on .knob::before {
|
|
196
|
+
animation: sw-trail-on var(--dur-spring) var(--ease-out);
|
|
197
|
+
}
|
|
198
|
+
&.armed:not(.on) .knob::before {
|
|
199
|
+
animation: sw-trail-off var(--dur-spring) var(--ease-out);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
@keyframes sw-trail-on {
|
|
203
|
+
0%,
|
|
204
|
+
100% {
|
|
205
|
+
opacity: 0;
|
|
206
|
+
scale: 0.2 1;
|
|
207
|
+
}
|
|
208
|
+
30% {
|
|
209
|
+
opacity: 1;
|
|
210
|
+
scale: 1;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
@keyframes sw-trail-off {
|
|
214
|
+
0%,
|
|
215
|
+
100% {
|
|
216
|
+
opacity: 0;
|
|
217
|
+
scale: 0.2 1;
|
|
218
|
+
}
|
|
219
|
+
30% {
|
|
220
|
+
opacity: 1;
|
|
221
|
+
scale: 1;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* Calm: an unhurried glide with no overshoot; a small accent pip settles into the knob once on. */
|
|
226
|
+
:global([data-fx-switch='calm']) .switch:not(:global([data-fx-switch='calm'] [data-fx-switch]:not([data-fx-switch='calm'])) *) {
|
|
227
|
+
transition:
|
|
228
|
+
background var(--dur-slow) var(--ease),
|
|
229
|
+
border-color var(--dur-slow) var(--ease);
|
|
230
|
+
& .knob {
|
|
231
|
+
transform-origin: center;
|
|
232
|
+
transition:
|
|
233
|
+
translate var(--dur-slow) var(--ease-in-out),
|
|
234
|
+
scale var(--dur-fast) var(--ease-out),
|
|
235
|
+
background var(--dur-slow) var(--ease);
|
|
236
|
+
}
|
|
237
|
+
&:active:not(:disabled) .knob {
|
|
238
|
+
scale: var(--press-scale-sm);
|
|
239
|
+
}
|
|
240
|
+
& .knob::after {
|
|
241
|
+
content: '';
|
|
242
|
+
position: absolute;
|
|
243
|
+
inset: 32%;
|
|
244
|
+
border-radius: var(--r-pill);
|
|
245
|
+
background: var(--accent);
|
|
246
|
+
opacity: 0;
|
|
247
|
+
scale: 0.4;
|
|
248
|
+
transition:
|
|
249
|
+
opacity var(--dur-exit-fast) var(--ease-out),
|
|
250
|
+
scale var(--dur-exit-fast) var(--ease-out);
|
|
251
|
+
}
|
|
252
|
+
&.on .knob::after {
|
|
253
|
+
opacity: 1;
|
|
254
|
+
scale: 1;
|
|
255
|
+
transition:
|
|
256
|
+
opacity var(--dur-base) var(--ease) var(--dur-fast),
|
|
257
|
+
scale var(--dur-base) var(--ease-out) var(--dur-fast);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
125
260
|
.switch:focus-visible {
|
|
126
261
|
outline: var(--focus-ring);
|
|
127
262
|
outline-offset: var(--focus-ring-offset);
|
|
@@ -211,6 +211,7 @@
|
|
|
211
211
|
bind:value
|
|
212
212
|
use:autoresizeAction={typeof value === 'string' ? value : ''}
|
|
213
213
|
{...rest}
|
|
214
|
+
placeholder={rest.placeholder ?? (field ? ' ' : undefined)}
|
|
214
215
|
onkeydown={onsubmit || onkeydown ? handleKeydown : undefined}
|
|
215
216
|
id={id ?? field?.id}
|
|
216
217
|
aria-describedby={ariaDescribedby ?? field?.describedBy}
|
|
@@ -230,6 +231,7 @@
|
|
|
230
231
|
style:max-height={maxHeight}
|
|
231
232
|
bind:value
|
|
232
233
|
{...rest}
|
|
234
|
+
placeholder={rest.placeholder ?? (field ? ' ' : undefined)}
|
|
233
235
|
onkeydown={onsubmit || onkeydown ? handleKeydown : undefined}
|
|
234
236
|
id={id ?? field?.id}
|
|
235
237
|
aria-describedby={ariaDescribedby ?? field?.describedBy}
|
|
@@ -302,6 +304,10 @@
|
|
|
302
304
|
outline-offset: var(--focus-ring-offset);
|
|
303
305
|
box-shadow: var(--control-shadow), var(--focus-glow);
|
|
304
306
|
}
|
|
307
|
+
.textarea::placeholder {
|
|
308
|
+
color: var(--text-faint);
|
|
309
|
+
opacity: 1;
|
|
310
|
+
}
|
|
305
311
|
.textarea:disabled {
|
|
306
312
|
cursor: not-allowed;
|
|
307
313
|
}
|
|
@@ -335,6 +341,36 @@
|
|
|
335
341
|
.textarea[aria-invalid='true']:focus {
|
|
336
342
|
border-color: var(--danger);
|
|
337
343
|
}
|
|
344
|
+
.textarea {
|
|
345
|
+
--focus-tint: var(--accent);
|
|
346
|
+
}
|
|
347
|
+
.textarea[aria-invalid='true'] {
|
|
348
|
+
--focus-tint: var(--danger);
|
|
349
|
+
}
|
|
350
|
+
/* Focus bloom: a solid ring whose soft halo swells, then settles to a quieter glow. */
|
|
351
|
+
.textarea:not(.grouped):focus:not(:global([data-fx-focus='none']) *),
|
|
352
|
+
:global(:is([data-fx-focus='bloom'], [data-fx-focus='label'])) .textarea:not(.grouped):focus:not(:global(:is([data-fx-focus='bloom'], [data-fx-focus='label']) [data-fx-focus='none']) *) {
|
|
353
|
+
outline: none;
|
|
354
|
+
box-shadow:
|
|
355
|
+
var(--control-shadow),
|
|
356
|
+
0 0 0 var(--focus-ring-width) var(--focus-tint),
|
|
357
|
+
0 0 16px 2px color-mix(in srgb, var(--focus-tint) 24%, transparent);
|
|
358
|
+
animation: focus-bloom var(--dur-fast) var(--ease-out);
|
|
359
|
+
}
|
|
360
|
+
@keyframes focus-bloom {
|
|
361
|
+
0% {
|
|
362
|
+
box-shadow:
|
|
363
|
+
var(--control-shadow),
|
|
364
|
+
0 0 0 0 transparent,
|
|
365
|
+
0 0 0 0 transparent;
|
|
366
|
+
}
|
|
367
|
+
45% {
|
|
368
|
+
box-shadow:
|
|
369
|
+
var(--control-shadow),
|
|
370
|
+
0 0 0 calc(var(--focus-ring-width) + 1px) var(--focus-tint),
|
|
371
|
+
0 0 20px 4px color-mix(in srgb, var(--focus-tint) 32%, transparent);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
338
374
|
/* Inside an InputGroup the group draws the focus ring and the overlaid
|
|
339
375
|
adornments report their widths; the text stays clear of them. */
|
|
340
376
|
.textarea.grouped {
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
// Chat "assistant is working" indicator. `dots`: three dots rise one after
|
|
4
|
+
// the other, drawn in currentColor. `sheen`: a light sweeps through the
|
|
5
|
+
// status word (children, or `label`), with a blinking caret after it.
|
|
6
|
+
let {
|
|
7
|
+
variant = 'dots',
|
|
8
|
+
label = 'Thinking',
|
|
9
|
+
caret = true,
|
|
10
|
+
children,
|
|
11
|
+
class: klass = '',
|
|
12
|
+
style: styleProp = '',
|
|
13
|
+
...rest
|
|
14
|
+
}: {
|
|
15
|
+
variant?: 'dots' | 'sheen';
|
|
16
|
+
/** Announced to AT; also the visible word of `sheen` when there are no children. */
|
|
17
|
+
label?: string;
|
|
18
|
+
/** Blinking caret after the sheen text. */
|
|
19
|
+
caret?: boolean;
|
|
20
|
+
children?: Snippet;
|
|
21
|
+
class?: string;
|
|
22
|
+
style?: string;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
} = $props();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<span
|
|
28
|
+
data-tsu="Thinking"
|
|
29
|
+
class="thinking v-{variant} {klass}"
|
|
30
|
+
style={styleProp}
|
|
31
|
+
role="status"
|
|
32
|
+
aria-label={variant === 'dots' ? label : undefined}
|
|
33
|
+
{...rest}
|
|
34
|
+
>
|
|
35
|
+
{#if variant === 'dots'}
|
|
36
|
+
<span class="dots" aria-hidden="true"><span></span><span></span><span></span></span>
|
|
37
|
+
{:else}
|
|
38
|
+
<span class="text">{#if children}{@render children()}{:else}{label}{/if}</span>
|
|
39
|
+
{#if caret}<span class="caret" aria-hidden="true"></span>{/if}
|
|
40
|
+
{/if}
|
|
41
|
+
</span>
|
|
42
|
+
|
|
43
|
+
<style>
|
|
44
|
+
.thinking {
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
line-height: 1;
|
|
48
|
+
vertical-align: middle;
|
|
49
|
+
}
|
|
50
|
+
.dots {
|
|
51
|
+
display: inline-flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
gap: 0.3em;
|
|
54
|
+
padding: 0.25em 0;
|
|
55
|
+
}
|
|
56
|
+
.dots span {
|
|
57
|
+
display: block;
|
|
58
|
+
width: 0.4em;
|
|
59
|
+
height: 0.4em;
|
|
60
|
+
border-radius: 50%;
|
|
61
|
+
background: currentColor;
|
|
62
|
+
animation: tsu-thinking-dot 1.2s var(--ease-in-out) infinite;
|
|
63
|
+
}
|
|
64
|
+
.dots span:nth-child(1) {
|
|
65
|
+
opacity: 1;
|
|
66
|
+
}
|
|
67
|
+
.dots span:nth-child(2) {
|
|
68
|
+
opacity: 0.6;
|
|
69
|
+
animation-delay: 0.15s;
|
|
70
|
+
}
|
|
71
|
+
.dots span:nth-child(3) {
|
|
72
|
+
opacity: 0.3;
|
|
73
|
+
animation-delay: 0.3s;
|
|
74
|
+
}
|
|
75
|
+
@keyframes tsu-thinking-dot {
|
|
76
|
+
0%,
|
|
77
|
+
55%,
|
|
78
|
+
100% {
|
|
79
|
+
opacity: 0.35;
|
|
80
|
+
transform: translateY(0);
|
|
81
|
+
}
|
|
82
|
+
25% {
|
|
83
|
+
opacity: 1;
|
|
84
|
+
transform: translateY(-0.3em);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.v-sheen {
|
|
89
|
+
line-height: inherit;
|
|
90
|
+
}
|
|
91
|
+
.text {
|
|
92
|
+
background: linear-gradient(90deg, var(--text-muted) 0 38%, var(--text) 50%, var(--text-muted) 62% 100%);
|
|
93
|
+
background-size: 300% 100%;
|
|
94
|
+
background-position: 100% 0;
|
|
95
|
+
-webkit-background-clip: text;
|
|
96
|
+
background-clip: text;
|
|
97
|
+
color: transparent;
|
|
98
|
+
animation: tsu-thinking-sheen 1.8s linear infinite;
|
|
99
|
+
}
|
|
100
|
+
@keyframes tsu-thinking-sheen {
|
|
101
|
+
to {
|
|
102
|
+
background-position: 0% 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
.caret {
|
|
106
|
+
display: inline-block;
|
|
107
|
+
width: 2px;
|
|
108
|
+
height: 1em;
|
|
109
|
+
margin-left: 0.2em;
|
|
110
|
+
border-radius: 1px;
|
|
111
|
+
background: var(--accent);
|
|
112
|
+
animation: tsu-thinking-blink 1s steps(2, jump-none) infinite;
|
|
113
|
+
}
|
|
114
|
+
@keyframes tsu-thinking-blink {
|
|
115
|
+
to {
|
|
116
|
+
opacity: 0;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@media (prefers-reduced-motion: reduce) {
|
|
121
|
+
.dots span,
|
|
122
|
+
.text,
|
|
123
|
+
.caret {
|
|
124
|
+
animation: none;
|
|
125
|
+
}
|
|
126
|
+
.text {
|
|
127
|
+
background-position: 50% 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
variant?: 'dots' | 'sheen';
|
|
4
|
+
/** Announced to AT; also the visible word of `sheen` when there are no children. */
|
|
5
|
+
label?: string;
|
|
6
|
+
/** Blinking caret after the sheen text. */
|
|
7
|
+
caret?: boolean;
|
|
8
|
+
children?: Snippet;
|
|
9
|
+
class?: string;
|
|
10
|
+
style?: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
};
|
|
13
|
+
declare const Thinking: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
14
|
+
type Thinking = ReturnType<typeof Thinking>;
|
|
15
|
+
export default Thinking;
|