@astryxdesign/lab 0.7.5-canary.fd0aba8 → 0.7.6-canary.0ef60dd
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/CircularProgress/CircularProgress.d.ts +2 -1
- package/dist/CircularProgress/CircularProgress.d.ts.map +1 -1
- package/dist/CircularProgress/CircularProgress.js +293 -16
- package/dist/CommentPin/CommentPin.d.ts.map +1 -1
- package/dist/CommentPin/CommentPin.js +37 -3
- package/dist/lab.css +8 -1
- package/package.json +4 -4
- package/src/Chat/__tests__/ChatTypingIndicator.a11y.chromium.spec.ts +20 -8
- package/src/CircularProgress/CircularProgress.test.tsx +389 -10
- package/src/CircularProgress/CircularProgress.tsx +364 -20
- package/src/CircularProgress/__tests__/CircularProgress.a11y.chromium.spec.ts +21 -9
- package/src/CircularProgress/__tests__/CircularProgress.a11y.states.ts +2 -2
- package/src/CircularProgress/__tests__/CircularProgressMotion.a11y.chromium.spec.ts +886 -0
- package/src/CommentPin/CommentPin.doc.mjs +6 -4
- package/src/CommentPin/CommentPin.spec.md +14 -13
- package/src/CommentPin/CommentPin.test.tsx +177 -1
- package/src/CommentPin/CommentPin.tsx +44 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file CircularProgress.tsx
|
|
3
|
-
* @input Uses React
|
|
3
|
+
* @input Uses React (useId, refs, layout effects), stylex, SVG, Web
|
|
4
|
+
* Animations, color/spacing/duration/ease tokens
|
|
4
5
|
* @output Exports CircularProgress component, CircularProgressProps, CircularProgressSize, CircularProgressVariant types
|
|
5
6
|
* @position Core implementation; consumed by index.ts
|
|
6
7
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CircularProgress.d.ts","sourceRoot":"","sources":["../../src/CircularProgress/CircularProgress.tsx"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"CircularProgress.d.ts","sourceRoot":"","sources":["../../src/CircularProgress/CircularProgress.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAYrE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAGlD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,IAAI,CAAC;IACb,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,OAAO,EAAE,IAAI,CAAC;CACf;AAED,MAAM,MAAM,uBAAuB,GAAG,MAAM,0BAA0B,CAAC;AAEvE,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAWtD,MAAM,WAAW,qBAAsB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACtE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B;;;OAGG;IACH,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAClC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAmWD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAS,EACT,GAAS,EACT,KAAK,EACL,aAAoB,EACpB,aAAqB,EACrB,gBAA0C,EAC1C,QAAQ,EACR,IAAW,EACX,OAAkB,EAClB,eAAuB,EACvB,UAAkB,EAClB,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,UAAU,EACzB,GAAG,EACH,GAAG,IAAI,EACR,EAAE,qBAAqB,+BA+QvB;yBAjSe,gBAAgB"}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file CircularProgress.tsx
|
|
7
|
-
* @input Uses React
|
|
7
|
+
* @input Uses React (useId, refs, layout effects), stylex, SVG, Web
|
|
8
|
+
* Animations, color/spacing/duration/ease tokens
|
|
8
9
|
* @output Exports CircularProgress component, CircularProgressProps, CircularProgressSize, CircularProgressVariant types
|
|
9
10
|
* @position Core implementation; consumed by index.ts
|
|
10
11
|
*
|
|
@@ -14,7 +15,7 @@
|
|
|
14
15
|
* - /packages/lab/src/CircularProgress/index.ts (exports if types change)
|
|
15
16
|
* - /apps/storybook/stories/CircularProgress.stories.tsx (storybook stories)
|
|
16
17
|
*/
|
|
17
|
-
import { useId } from 'react';
|
|
18
|
+
import { useId, useLayoutEffect, useRef } from 'react';
|
|
18
19
|
import * as stylex from '@stylexjs/stylex';
|
|
19
20
|
import '@astryxdesign/core/theme/tokens.stylex';
|
|
20
21
|
import { colorVars, durationVars, easeVars, fontWeightVars, spacingVars, typeScaleVars } from '@astryxdesign/core/theme/tokens.stylex';
|
|
@@ -35,6 +36,8 @@ import { themeProps } from '@astryxdesign/core/utils';
|
|
|
35
36
|
* ```
|
|
36
37
|
*/
|
|
37
38
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
39
|
+
/** How far a turning layer's drawing reaches past the ring's box, in px. */
|
|
40
|
+
const FILL_BLEED = 2;
|
|
38
41
|
const SIZE_CONFIG = {
|
|
39
42
|
sm: {
|
|
40
43
|
diameter: 32,
|
|
@@ -83,9 +86,11 @@ const styles = {
|
|
|
83
86
|
$$css: true
|
|
84
87
|
},
|
|
85
88
|
fill: {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
kVAEAm: "x10l6tqk",
|
|
90
|
+
kpwlN0: "x10a8y8t",
|
|
91
|
+
kVQacm: "xb3r6kr",
|
|
92
|
+
kfzvcC: "x47corl",
|
|
93
|
+
k1ekBW: "x13b0p5u",
|
|
89
94
|
kIyJzY: "x80gvsz",
|
|
90
95
|
kAMwcw: "xlr8y92",
|
|
91
96
|
$$css: true
|
|
@@ -153,6 +158,129 @@ function defaultFormatValueLabel(value, max) {
|
|
|
153
158
|
return `${pct}%`;
|
|
154
159
|
}
|
|
155
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Each half-ring's turn and the leading cap's for a filled share `p` (0–1),
|
|
163
|
+
* and which parts show. A half-ring parked at the edge of its half (the right
|
|
164
|
+
* one at 0, the left one until the value passes half) is hidden, so its
|
|
165
|
+
* antialiased end never leaves a hairline at 6 o'clock.
|
|
166
|
+
*/
|
|
167
|
+
function fillTurns(p) {
|
|
168
|
+
const turn = 360 * p;
|
|
169
|
+
return {
|
|
170
|
+
right: Math.min(Math.max(turn, 0), 180),
|
|
171
|
+
left: Math.min(Math.max(turn, 180), 360),
|
|
172
|
+
cap: turn,
|
|
173
|
+
rightShown: p > 0 ? 1 : 0,
|
|
174
|
+
leftShown: p > 0.5 ? 1 : 0,
|
|
175
|
+
caps: p > 0 ? 1 : 0
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** A CSS time (`410ms`, `0.41s`, or a list's first entry) in milliseconds. */
|
|
180
|
+
function parseTime(value) {
|
|
181
|
+
const first = value.split(',')[0]?.trim() ?? '';
|
|
182
|
+
const amount = Number.parseFloat(first);
|
|
183
|
+
if (!Number.isFinite(amount)) {
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
return first.endsWith('ms') ? amount : amount * 1000;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The theme's easing as a function of time (0–1), evaluated by the browser
|
|
191
|
+
* itself through a detached animation, so any CSS easing works. It runs only
|
|
192
|
+
* where Web Animations exists (its caller returns first otherwise), on the
|
|
193
|
+
* fill's computed `transition-timing-function`: one easing, which a browser
|
|
194
|
+
* always reports as valid CSS.
|
|
195
|
+
*/
|
|
196
|
+
function easingOf(easing) {
|
|
197
|
+
const effect = new KeyframeEffect(null, null, {
|
|
198
|
+
duration: 1000,
|
|
199
|
+
easing,
|
|
200
|
+
fill: 'both'
|
|
201
|
+
});
|
|
202
|
+
const probe = new Animation(effect);
|
|
203
|
+
return t => {
|
|
204
|
+
probe.currentTime = t * 1000;
|
|
205
|
+
return effect.getComputedTiming().progress ?? t;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Keyframes for a change from `from` to `to` (filled shares), sampled once a
|
|
211
|
+
* frame on the theme's easing, with an extra sample exactly where the arc
|
|
212
|
+
* crosses 12 or 6 o'clock so each half stops and starts on time.
|
|
213
|
+
*/
|
|
214
|
+
function fillKeyframes(from, to, duration, ease) {
|
|
215
|
+
const count = Math.max(8, Math.ceil(duration / (1000 / 60)));
|
|
216
|
+
const grid = Array.from({
|
|
217
|
+
length: count + 1
|
|
218
|
+
}, (_, i) => i / count);
|
|
219
|
+
const at = t => from + (to - from) * ease(t);
|
|
220
|
+
// Where the eased path passes half (p = 0.5): a sample there, and one just
|
|
221
|
+
// after it, so the halves hand over on time and the left half's
|
|
222
|
+
// visibility steps rather than fades.
|
|
223
|
+
const crossings = [];
|
|
224
|
+
for (let i = 0; i < count; i++) {
|
|
225
|
+
const a = grid[i];
|
|
226
|
+
const b = grid[i + 1];
|
|
227
|
+
const fa = at(a) - 0.5;
|
|
228
|
+
const fb = at(b) - 0.5;
|
|
229
|
+
if (fa === 0) {
|
|
230
|
+
crossings.push(a);
|
|
231
|
+
} else if (fa * fb < 0) {
|
|
232
|
+
let low = a;
|
|
233
|
+
let high = b;
|
|
234
|
+
for (let k = 0; k < 24; k++) {
|
|
235
|
+
const mid = (low + high) / 2;
|
|
236
|
+
if ((at(low) - 0.5) * (at(mid) - 0.5) <= 0) {
|
|
237
|
+
high = mid;
|
|
238
|
+
} else {
|
|
239
|
+
low = mid;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
crossings.push((low + high) / 2);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const times = [...new Set([...grid, ...crossings, ...crossings.map(c => c + 1e-4).filter(t => t < 1)])].sort((x, y) => x - y);
|
|
246
|
+
const frames = times.map(t => ({
|
|
247
|
+
t,
|
|
248
|
+
...fillTurns(t === 1 ? to : at(t))
|
|
249
|
+
}));
|
|
250
|
+
// The sample at a crossing sits exactly on half, where the left half reads
|
|
251
|
+
// as hidden. Give it the side the arc is leaving instead, so the left half
|
|
252
|
+
// steps at once both ways: shrinking through half, it used to fade out
|
|
253
|
+
// from the sample before, while it still drew part of the arc (a lighter
|
|
254
|
+
// band between the end cap and 6 o'clock for a frame).
|
|
255
|
+
const onHalf = new Set(crossings);
|
|
256
|
+
for (let i = 1; i < frames.length; i += 1) {
|
|
257
|
+
if (onHalf.has(frames[i].t) || frames[i].t === 1 && to === 0.5) {
|
|
258
|
+
frames[i].leftShown = frames[i - 1].leftShown;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
right: frames.map(f => ({
|
|
263
|
+
offset: f.t,
|
|
264
|
+
transform: `rotate(${f.right}deg)`,
|
|
265
|
+
opacity: f.rightShown
|
|
266
|
+
})),
|
|
267
|
+
left: frames.map(f => ({
|
|
268
|
+
offset: f.t,
|
|
269
|
+
transform: `rotate(${f.left}deg)`,
|
|
270
|
+
opacity: f.leftShown
|
|
271
|
+
})),
|
|
272
|
+
cap: frames.map(f => ({
|
|
273
|
+
offset: f.t,
|
|
274
|
+
transform: `rotate(${f.cap}deg)`,
|
|
275
|
+
opacity: f.caps
|
|
276
|
+
})),
|
|
277
|
+
start: frames.map(f => ({
|
|
278
|
+
offset: f.t,
|
|
279
|
+
opacity: f.caps
|
|
280
|
+
}))
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
156
284
|
/**
|
|
157
285
|
* A circular/radial progress indicator that shows completion as a ring.
|
|
158
286
|
*
|
|
@@ -205,7 +333,6 @@ export function CircularProgress({
|
|
|
205
333
|
const safeMax = Number.isFinite(max) ? max : 0;
|
|
206
334
|
const clampedValue = Math.min(Math.max(0, safeValue), safeMax);
|
|
207
335
|
const percentage = safeMax > 0 ? clampedValue / safeMax : 0;
|
|
208
|
-
const dashoffset = circumference * (1 - percentage);
|
|
209
336
|
const valueText = formatValueLabel(clampedValue, safeMax);
|
|
210
337
|
const center = diameter / 2;
|
|
211
338
|
const showLabel = !isLabelHidden;
|
|
@@ -213,6 +340,89 @@ export function CircularProgress({
|
|
|
213
340
|
const centerContent = children != null ? children : showValueLabel ? valueText : null;
|
|
214
341
|
const fillVariant = isDisabled ? 'disabled' : variant;
|
|
215
342
|
const trackVariant = isDisabled ? 'neutral' : variant;
|
|
343
|
+
|
|
344
|
+
// A value change turns the fill's layers with Web Animations on the
|
|
345
|
+
// compositor, from wherever the arc is drawn now (mid-change, the running
|
|
346
|
+
// change's own curve says where) to the new value, on the theme's
|
|
347
|
+
// duration and easing. What renders is always the resting state, so a
|
|
348
|
+
// first render, a render without script, or a skipped animation shows the
|
|
349
|
+
// value.
|
|
350
|
+
const fillRef = useRef(null);
|
|
351
|
+
const rightRef = useRef(null);
|
|
352
|
+
const leftRef = useRef(null);
|
|
353
|
+
const capRef = useRef(null);
|
|
354
|
+
const startRef = useRef(null);
|
|
355
|
+
// Where the arc is drawn: the value at rest, or, when a change is
|
|
356
|
+
// interrupted, the point its own curve has reached. A change's cleanup
|
|
357
|
+
// records it and releases the change's animations (react-component-runtime
|
|
358
|
+
// INV3), so the next change starts from there and an unmount or a switch to
|
|
359
|
+
// indeterminate leaves nothing running. While indeterminate no value is
|
|
360
|
+
// drawn (null): the ring draws its own arc, not the value it holds, so the
|
|
361
|
+
// next value draws at once instead of sweeping from that held value.
|
|
362
|
+
const drawn = useRef(percentage);
|
|
363
|
+
useLayoutEffect(() => {
|
|
364
|
+
const from = drawn.current;
|
|
365
|
+
if (isIndeterminate) {
|
|
366
|
+
drawn.current = null;
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (from === null || from === percentage) {
|
|
370
|
+
drawn.current = percentage;
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const fill = fillRef.current;
|
|
374
|
+
const layers = [rightRef.current, leftRef.current, capRef.current, startRef.current];
|
|
375
|
+
drawn.current = percentage;
|
|
376
|
+
if (fill == null || layers.some(layer => typeof layer?.animate !== 'function')) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const style = getComputedStyle(fill);
|
|
380
|
+
const duration = parseTime(style.transitionDuration);
|
|
381
|
+
if (duration <= 0) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const ease = easingOf(style.transitionTimingFunction);
|
|
385
|
+
const frames = fillKeyframes(from, percentage, duration, ease);
|
|
386
|
+
const timing = {
|
|
387
|
+
duration,
|
|
388
|
+
easing: 'linear',
|
|
389
|
+
fill: 'backwards'
|
|
390
|
+
};
|
|
391
|
+
const [right, left, cap, start] = layers;
|
|
392
|
+
const animations = [right.animate(frames.right, timing), left.animate(frames.left, timing), cap.animate(frames.cap, timing), start.animate(frames.start, timing)];
|
|
393
|
+
return () => {
|
|
394
|
+
const [first] = animations;
|
|
395
|
+
const progress = first.playState !== 'finished' ? Number(first.effect?.getComputedTiming().progress ?? 1) : 1;
|
|
396
|
+
// `progress` is the change's linear time fraction; the arc is drawn on
|
|
397
|
+
// its easing, so the eased position is where it is.
|
|
398
|
+
drawn.current = from + (percentage - from) * ease(progress);
|
|
399
|
+
for (const animation of animations) {
|
|
400
|
+
animation.cancel();
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
}, [percentage, isIndeterminate]);
|
|
404
|
+
const turns = fillTurns(percentage);
|
|
405
|
+
// One half of the ring's stroke, from 6 o'clock clockwise to 12 (the left
|
|
406
|
+
// half) before its layer turns; butt ends, the caps are drawn apart.
|
|
407
|
+
const bleedBox = `${-FILL_BLEED} ${-FILL_BLEED} ${diameter + 2 * FILL_BLEED} ${diameter + 2 * FILL_BLEED}`;
|
|
408
|
+
const halfRing = /*#__PURE__*/_jsx("svg", {
|
|
409
|
+
width: diameter + 2 * FILL_BLEED,
|
|
410
|
+
height: diameter + 2 * FILL_BLEED,
|
|
411
|
+
viewBox: bleedBox,
|
|
412
|
+
...{
|
|
413
|
+
className: "x1lliihq xbh8q5q xy6n8ys"
|
|
414
|
+
},
|
|
415
|
+
children: /*#__PURE__*/_jsx("circle", {
|
|
416
|
+
cx: center,
|
|
417
|
+
cy: center,
|
|
418
|
+
r: radius,
|
|
419
|
+
strokeWidth: strokeWidth,
|
|
420
|
+
strokeDasharray: `${circumference / 2} ${circumference}`,
|
|
421
|
+
transform: `rotate(90 ${center} ${center})`
|
|
422
|
+
})
|
|
423
|
+
});
|
|
424
|
+
// A round cap at 12 o'clock on the ring's centerline (a zero-length stroke).
|
|
425
|
+
const capPath = `M ${center} ${strokeWidth / 2} h 0`;
|
|
216
426
|
return /*#__PURE__*/_jsxs("div", {
|
|
217
427
|
ref: ref,
|
|
218
428
|
...mergeProps(themeProps('circular-progress', {
|
|
@@ -274,16 +484,83 @@ export function CircularProgress({
|
|
|
274
484
|
cy: center,
|
|
275
485
|
r: radius,
|
|
276
486
|
strokeWidth: strokeWidth
|
|
277
|
-
}) :
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
487
|
+
}) : null]
|
|
488
|
+
}), isIndeterminate ? null :
|
|
489
|
+
/*#__PURE__*/
|
|
490
|
+
// The determinate fill (see `styles.fill`). Its stroke color is
|
|
491
|
+
// set on the theming target and inherited by every part.
|
|
492
|
+
_jsxs("span", {
|
|
493
|
+
ref: fillRef,
|
|
494
|
+
"aria-hidden": "true",
|
|
495
|
+
...mergeProps(themeProps('circular-progress-fill', {
|
|
496
|
+
variant: fillVariant
|
|
497
|
+
}), stylex.props(styles.fill, variantStyles[fillVariant])),
|
|
498
|
+
children: [/*#__PURE__*/_jsx("span", {
|
|
499
|
+
...{
|
|
500
|
+
className: "x10l6tqk x13vifvy x3hqpx7 x5yr21d xb3r6kr x1nrll8i"
|
|
501
|
+
},
|
|
502
|
+
children: /*#__PURE__*/_jsx("span", {
|
|
503
|
+
ref: rightRef,
|
|
504
|
+
...{
|
|
505
|
+
className: "x10l6tqk x13vifvy x75ztot x5yr21d x1q1rmc8 xb5nubl"
|
|
506
|
+
},
|
|
507
|
+
style: {
|
|
508
|
+
transform: `rotate(${turns.right}deg)`,
|
|
509
|
+
opacity: turns.rightShown
|
|
510
|
+
},
|
|
511
|
+
children: halfRing
|
|
512
|
+
})
|
|
513
|
+
}), /*#__PURE__*/_jsx("span", {
|
|
514
|
+
...{
|
|
515
|
+
className: "x10l6tqk x13vifvy x3hqpx7 x5yr21d xb3r6kr xu96u03"
|
|
516
|
+
},
|
|
517
|
+
children: /*#__PURE__*/_jsx("span", {
|
|
518
|
+
ref: leftRef,
|
|
519
|
+
...{
|
|
520
|
+
className: "x10l6tqk x13vifvy x75ztot x5yr21d x1q1rmc8 xu96u03"
|
|
521
|
+
},
|
|
522
|
+
style: {
|
|
523
|
+
transform: `rotate(${turns.left}deg)`,
|
|
524
|
+
opacity: turns.leftShown
|
|
525
|
+
},
|
|
526
|
+
children: halfRing
|
|
527
|
+
})
|
|
528
|
+
}), /*#__PURE__*/_jsx("svg", {
|
|
529
|
+
width: diameter,
|
|
530
|
+
height: diameter,
|
|
531
|
+
viewBox: `0 0 ${diameter} ${diameter}`,
|
|
532
|
+
...{
|
|
533
|
+
className: "x10l6tqk x10a8y8t x1q1rmc8 x1lliihq xbh8q5q x1owpc8m"
|
|
534
|
+
},
|
|
535
|
+
children: /*#__PURE__*/_jsx("path", {
|
|
536
|
+
ref: startRef,
|
|
537
|
+
d: capPath,
|
|
538
|
+
strokeWidth: strokeWidth,
|
|
539
|
+
style: {
|
|
540
|
+
opacity: turns.caps
|
|
541
|
+
}
|
|
542
|
+
})
|
|
543
|
+
}), /*#__PURE__*/_jsx("span", {
|
|
544
|
+
ref: capRef,
|
|
545
|
+
...{
|
|
546
|
+
className: "x10l6tqk x10a8y8t x1q1rmc8"
|
|
547
|
+
},
|
|
548
|
+
style: {
|
|
549
|
+
transform: `rotate(${turns.cap}deg)`,
|
|
550
|
+
opacity: turns.caps
|
|
551
|
+
},
|
|
552
|
+
children: /*#__PURE__*/_jsx("svg", {
|
|
553
|
+
width: diameter + 2 * FILL_BLEED,
|
|
554
|
+
height: diameter + 2 * FILL_BLEED,
|
|
555
|
+
viewBox: bleedBox,
|
|
556
|
+
...{
|
|
557
|
+
className: "x1owpc8m x1lliihq xbh8q5q xy6n8ys"
|
|
558
|
+
},
|
|
559
|
+
children: /*#__PURE__*/_jsx("path", {
|
|
560
|
+
d: capPath,
|
|
561
|
+
strokeWidth: strokeWidth
|
|
562
|
+
})
|
|
563
|
+
})
|
|
287
564
|
})]
|
|
288
565
|
}), centerContent != null && /*#__PURE__*/_jsx("div", {
|
|
289
566
|
...{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentPin.d.ts","sourceRoot":"","sources":["../../src/CommentPin/CommentPin.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AA4BlD,qEAAqE;AACrE,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhD,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,EAAE,EAAE,MAAM,CAAC;IACX,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,SAAS,CAAC,iBAAiB,CAAC,EAC5B,UAAU,CACX;IACC,0CAA0C;IAC1C,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,YAAY,EAAE,SAAS,CAAC,qBAAqB,EAAE,GAAG,qBAAqB,EAAE,CAAC,CAAC;IAC3E;;;;OAIG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yCAAyC;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"CommentPin.d.ts","sourceRoot":"","sources":["../../src/CommentPin/CommentPin.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AA4BlD,qEAAqE;AACrE,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhD,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,EAAE,EAAE,MAAM,CAAC;IACX,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,SAAS,CAAC,iBAAiB,CAAC,EAC5B,UAAU,CACX;IACC,0CAA0C;IAC1C,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,YAAY,EAAE,SAAS,CAAC,qBAAqB,EAAE,GAAG,qBAAqB,EAAE,CAAC,CAAC;IAC3E;;;;OAIG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yCAAyC;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA6OD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,YAAY,EACZ,IAAW,EACX,WAAe,EACf,WAAe,EACf,UAAkB,EAClB,QAAgB,EAChB,UAAkB,EAClB,UAAkB,EAClB,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,eAAe,GAAG,KAAK,CAAC,SAAS,CAwLnC;yBAvMe,UAAU"}
|
|
@@ -88,6 +88,11 @@ const styles = {
|
|
|
88
88
|
kSiTet: "xyd83as x1tbh5bv",
|
|
89
89
|
$$css: true
|
|
90
90
|
},
|
|
91
|
+
disabledUnselected: {
|
|
92
|
+
kVAM5u: "x9r1u3d xmnqt3w",
|
|
93
|
+
kMwMTN: "xv1l7n4 x1bhcspo",
|
|
94
|
+
$$css: true
|
|
95
|
+
},
|
|
91
96
|
overflowCircle: {
|
|
92
97
|
kg3NbH: "xnjsko4",
|
|
93
98
|
$$css: true
|
|
@@ -269,8 +274,37 @@ export function CommentPin({
|
|
|
269
274
|
ref: mergedRef,
|
|
270
275
|
type: "button",
|
|
271
276
|
"aria-label": label,
|
|
272
|
-
"aria-pressed": isSelected
|
|
273
|
-
disabled
|
|
277
|
+
"aria-pressed": isSelected
|
|
278
|
+
// A disabled pin keeps its tab stop and is announced as disabled through
|
|
279
|
+
// aria-disabled instead of the native attribute (spec:AST-085 FR1). The
|
|
280
|
+
// activation guards below keep it inert (FR2); focus is kept when the
|
|
281
|
+
// pin becomes disabled while focused (FR4), and the shared focus outline
|
|
282
|
+
// still paints, dimmed with the rest of the pin (FR6).
|
|
283
|
+
,
|
|
284
|
+
"aria-disabled": isDisabled ? 'true' : undefined,
|
|
285
|
+
onClick: e => {
|
|
286
|
+
if (isDisabled) {
|
|
287
|
+
// A natively disabled element dispatched no click at all, so an
|
|
288
|
+
// aria-disabled pin must not reach the caller's handler, and the
|
|
289
|
+
// click must not leak to a clickable ancestor (annotation surface)
|
|
290
|
+
// the native attribute never let it reach either (FR2).
|
|
291
|
+
e.preventDefault();
|
|
292
|
+
e.stopPropagation();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
rest.onClick?.(e);
|
|
296
|
+
}
|
|
297
|
+
// The keyboard paths to activation are Enter and Space; other keys still
|
|
298
|
+
// reach caller handlers, as on an enabled pin (FR2).
|
|
299
|
+
,
|
|
300
|
+
onKeyDown: e => {
|
|
301
|
+
if (isDisabled && (e.key === 'Enter' || e.key === ' ')) {
|
|
302
|
+
e.preventDefault();
|
|
303
|
+
e.stopPropagation();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
rest.onKeyDown?.(e);
|
|
307
|
+
},
|
|
274
308
|
...mergeProps(themeProps('comment-pin', {
|
|
275
309
|
size: resolvedSize,
|
|
276
310
|
grouped: isGrouped ? 'grouped' : null,
|
|
@@ -279,7 +313,7 @@ export function CommentPin({
|
|
|
279
313
|
dimmed: isDimmed ? 'dimmed' : null,
|
|
280
314
|
resolved: isResolved ? 'resolved' : null,
|
|
281
315
|
disabled: isDisabled ? 'disabled' : null
|
|
282
|
-
}), focusOutlineProps.focusVisible(styles.root, sizeStyles[resolvedSize], interactionOverlayStyles.backgroundImage, (isGrouped || hasUnread) && styles.withCount, isDimmed && !isSelected && styles.dimmed, isSelected && styles.selected, isDisabled && styles.disabled, xstyle), className, style),
|
|
316
|
+
}), focusOutlineProps.focusVisible(styles.root, sizeStyles[resolvedSize], interactionOverlayStyles.backgroundImage, (isGrouped || hasUnread) && styles.withCount, isDimmed && !isSelected && styles.dimmed, isSelected && styles.selected, isDisabled && styles.disabled, isDisabled && !isSelected && styles.disabledUnselected, xstyle), className, style),
|
|
283
317
|
children: [/*#__PURE__*/_jsx("span", {
|
|
284
318
|
"aria-hidden": "true",
|
|
285
319
|
...{
|
package/dist/lab.css
CHANGED
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
.xln7xf2:not(#\#){font:inherit}
|
|
64
64
|
.x10a8y8t:not(#\#){inset:0}
|
|
65
65
|
.xkdpibf:not(#\#){margin:-1px}
|
|
66
|
+
.xy6n8ys:not(#\#){margin:-2px}
|
|
66
67
|
.x1ghz6dp:not(#\#){margin:0}
|
|
67
68
|
.x10h29id:not(#\#){margin:var(--spacing-0-5)}
|
|
68
69
|
.x1717udv:not(#\#){padding:0}
|
|
@@ -562,8 +563,8 @@
|
|
|
562
563
|
.x15406qy:not(#\#):not(#\#):not(#\#){transition-property:background-color}
|
|
563
564
|
.xq90yva:not(#\#):not(#\#):not(#\#){transition-property:border-color,background-color}
|
|
564
565
|
.xt3l3uh:not(#\#):not(#\#):not(#\#){transition-property:color}
|
|
566
|
+
.x13b0p5u:not(#\#):not(#\#):not(#\#){transition-property:none}
|
|
565
567
|
.x19991ni:not(#\#):not(#\#):not(#\#){transition-property:opacity}
|
|
566
|
-
.xxnu56j:not(#\#):not(#\#):not(#\#){transition-property:stroke-dashoffset}
|
|
567
568
|
.x1gfj591:not(#\#):not(#\#):not(#\#){transition-property:top,left,width,height}
|
|
568
569
|
.x11xpdln:not(#\#):not(#\#):not(#\#){transition-property:transform}
|
|
569
570
|
.x9lcvmn:not(#\#):not(#\#):not(#\#){transition-timing-function:ease-out}
|
|
@@ -578,6 +579,7 @@
|
|
|
578
579
|
.xuxw1ft:not(#\#):not(#\#):not(#\#){white-space:nowrap}
|
|
579
580
|
.x126k92a:not(#\#):not(#\#):not(#\#){white-space:pre-wrap}
|
|
580
581
|
.x1sdyfia:not(#\#):not(#\#):not(#\#){white-space:pre}
|
|
582
|
+
.x1q1rmc8:not(#\#):not(#\#):not(#\#){will-change:transform,opacity}
|
|
581
583
|
.x1so62im:not(#\#):not(#\#):not(#\#){will-change:transform}
|
|
582
584
|
.x1lldw8n:not(#\#):not(#\#):not(#\#){word-break:normal}
|
|
583
585
|
.x8knxv4:not(#\#):not(#\#):not(#\#){z-index:-1}
|
|
@@ -676,6 +678,9 @@
|
|
|
676
678
|
.x1ueg155:not(#\#):not(#\#):not(#\#):not(#\#){height:var(--size-element-md)}
|
|
677
679
|
.x6k0iem:not(#\#):not(#\#):not(#\#):not(#\#){height:var(--size-element-sm)}
|
|
678
680
|
.x16ye13r:not(#\#):not(#\#):not(#\#):not(#\#){height:var(--x-height)}
|
|
681
|
+
.xb5nubl:not(#\#):not(#\#):not(#\#):not(#\#){left:-100%}
|
|
682
|
+
.xu96u03:not(#\#):not(#\#):not(#\#):not(#\#){left:0}
|
|
683
|
+
.x1nrll8i:not(#\#):not(#\#):not(#\#):not(#\#){left:50%}
|
|
679
684
|
.x1unci72:not(#\#):not(#\#):not(#\#):not(#\#){left:var(--spacing-4)}
|
|
680
685
|
.xlstkdb:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:var(--spacing-0-5)}
|
|
681
686
|
.x1p37lm5:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:var(--spacing-2)}
|
|
@@ -746,10 +751,12 @@
|
|
|
746
751
|
.x6jxa94:not(#\#):not(#\#):not(#\#):not(#\#){width:14px}
|
|
747
752
|
.x1kky2od:not(#\#):not(#\#):not(#\#):not(#\#){width:16px}
|
|
748
753
|
.x1i1rx1s:not(#\#):not(#\#):not(#\#):not(#\#){width:1px}
|
|
754
|
+
.x75ztot:not(#\#):not(#\#):not(#\#):not(#\#){width:200%}
|
|
749
755
|
.xvy4d1p:not(#\#):not(#\#):not(#\#):not(#\#){width:24px}
|
|
750
756
|
.x1dz1jew:not(#\#):not(#\#):not(#\#):not(#\#){width:280px}
|
|
751
757
|
.x1849jeq:not(#\#):not(#\#):not(#\#):not(#\#){width:30px}
|
|
752
758
|
.x1td3qas:not(#\#):not(#\#):not(#\#):not(#\#){width:32px}
|
|
759
|
+
.x3hqpx7:not(#\#):not(#\#):not(#\#):not(#\#){width:50%}
|
|
753
760
|
.x1ftt334:not(#\#):not(#\#):not(#\#):not(#\#){width:5px}
|
|
754
761
|
.x1smflpy:not(#\#):not(#\#):not(#\#):not(#\#){width:calc(100% - 2 * var(--spacing-0-5) - var(--border-width))}
|
|
755
762
|
.xurgr9q:not(#\#):not(#\#):not(#\#):not(#\#){width:calc(100% + (var(--spacing-3) * 2))}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/lab",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6-canary.0ef60dd",
|
|
4
4
|
"description": "Experimental Astryx components — published to npm only under the @canary dist-tag for early testing; never released as a stable (latest) version.",
|
|
5
5
|
"author": "Meta Open Source",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@stylexjs/stylex": ">=0.10.0",
|
|
52
|
-
"@astryxdesign/core": "0.7.
|
|
52
|
+
"@astryxdesign/core": "0.7.6-canary.0ef60dd",
|
|
53
53
|
"react": ">=19.0.0",
|
|
54
54
|
"react-dom": ">=19.0.0"
|
|
55
55
|
},
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"d3-shape": "^3.2.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@astryxdesign/a11y-spec": "0.7.
|
|
63
|
-
"@astryxdesign/cli": "0.7.
|
|
62
|
+
"@astryxdesign/a11y-spec": "0.7.6-canary.0ef60dd",
|
|
63
|
+
"@astryxdesign/cli": "0.7.6-canary.0ef60dd",
|
|
64
64
|
"@babel/cli": "^7.29.7",
|
|
65
65
|
"@babel/core": "^7.29.7",
|
|
66
66
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -140,17 +140,29 @@ async function runState(
|
|
|
140
140
|
});
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
// Checking coverage runs every state again, which on a loaded runner nears
|
|
144
|
+
// the per-test timeout as one test. It runs as one serial part per state,
|
|
145
|
+
// sharing their results, then one check over all of them.
|
|
146
|
+
test.describe('the exact known failure matches one executed ChatTypingIndicator result', () => {
|
|
147
|
+
test.describe.configure({mode: 'serial'});
|
|
147
148
|
const results: BindingResult[] = [];
|
|
149
|
+
|
|
148
150
|
for (const state of CHAT_TYPING_STATUS_BINDING_STATES) {
|
|
149
|
-
|
|
151
|
+
test(`runs ${state.id}`, async ({page}) => {
|
|
152
|
+
const cdp = await page.context().newCDPSession(page);
|
|
153
|
+
results.push(await runState(page, cdp, state));
|
|
154
|
+
});
|
|
150
155
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
156
|
+
|
|
157
|
+
test('the exact known failure matches one executed result', () => {
|
|
158
|
+
// Every part ran in this worker; this test alone proves nothing.
|
|
159
|
+
expect(results.map(result => result.state).sort()).toEqual(
|
|
160
|
+
CHAT_TYPING_STATUS_BINDING_STATES.map(state => state.id).sort(),
|
|
161
|
+
);
|
|
162
|
+
expect(
|
|
163
|
+
unmatchedKnownFailures(CHAT_TYPING_STATUS_KNOWN_FAILURES, results),
|
|
164
|
+
).toEqual([]);
|
|
165
|
+
});
|
|
154
166
|
});
|
|
155
167
|
|
|
156
168
|
for (const state of CHAT_TYPING_STATUS_BINDING_STATES) {
|