@astryxdesign/lab 0.7.8-canary.36cde5e → 0.7.8-canary.55aaeda
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartZoom.d.ts","sourceRoot":"","sources":["../../src/Chart/ChartZoom.tsx"],"names":[],"mappings":"AAmBA,6CAA6C;AAC7C,MAAM,MAAM,mBAAmB,GAC7B,WAAW,GAAG,UAAU,GAAG,cAAc,GAAG,aAAa,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IACrD,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IAErD;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;OAIG;IACH,OAAO,CAAC,EAAE,KAAK,GAAG,mBAAmB,CAAC;CACvC;AAiDD,wBAAgB,SAAS,CAAC,EACxB,eAAe,EACf,eAAe,EACf,SAAe,EACf,KAAa,EACb,KAAa,EACb,OAAqB,GACtB,EAAE,cAAc,+
|
|
1
|
+
{"version":3,"file":"ChartZoom.d.ts","sourceRoot":"","sources":["../../src/Chart/ChartZoom.tsx"],"names":[],"mappings":"AAmBA,6CAA6C;AAC7C,MAAM,MAAM,mBAAmB,GAC7B,WAAW,GAAG,UAAU,GAAG,cAAc,GAAG,aAAa,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IACrD,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IAErD;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;OAIG;IACH,OAAO,CAAC,EAAE,KAAK,GAAG,mBAAmB,CAAC;CACvC;AAiDD,wBAAgB,SAAS,CAAC,EACxB,eAAe,EACf,eAAe,EACf,SAAe,EACf,KAAa,EACb,KAAa,EACb,OAAqB,GACtB,EAAE,cAAc,+BA6RhB"}
|
package/dist/Chart/ChartZoom.js
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Zoom is aspect-ratio-normalized: both axes scale by the same visual
|
|
9
9
|
* factor regardless of their domain ranges. Reset button restores
|
|
10
|
-
* the
|
|
10
|
+
* the domains of the first render.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { useCallback, useRef,
|
|
13
|
+
import { useCallback, useRef, useState } from 'react';
|
|
14
14
|
import { createPortal } from 'react-dom';
|
|
15
15
|
import { IconButton } from '@astryxdesign/core/IconButton';
|
|
16
16
|
import { useChart } from "./ChartContext.js";
|
|
@@ -77,20 +77,10 @@ export function ChartZoom({
|
|
|
77
77
|
yScale,
|
|
78
78
|
svgRef
|
|
79
79
|
} = useChart();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (initialDomainsRef.current) {
|
|
85
|
-
return;
|
|
86
|
-
} // only capture once
|
|
87
|
-
const xDomain = isBandScale(xScale) ? null : xScale.domain();
|
|
88
|
-
const yDomain = yScale.domain();
|
|
89
|
-
initialDomainsRef.current = {
|
|
90
|
-
x: xDomain,
|
|
91
|
-
y: yDomain
|
|
92
|
-
};
|
|
93
|
-
}, [xScale, yScale]);
|
|
80
|
+
const [initialDomains] = useState(() => ({
|
|
81
|
+
x: isBandScale(xScale) ? null : xScale.domain(),
|
|
82
|
+
y: yScale.domain()
|
|
83
|
+
}));
|
|
94
84
|
const dragRef = useRef(null);
|
|
95
85
|
const pointersRef = useRef(new Map());
|
|
96
86
|
const pinchRef = useRef(null);
|
|
@@ -219,15 +209,11 @@ export function ChartZoom({
|
|
|
219
209
|
}
|
|
220
210
|
}, []);
|
|
221
211
|
const handleReset = useCallback(() => {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
if (init.x) {
|
|
227
|
-
onXDomainChange?.(init.x);
|
|
212
|
+
if (initialDomains.x) {
|
|
213
|
+
onXDomainChange?.(initialDomains.x);
|
|
228
214
|
}
|
|
229
|
-
onYDomainChange?.(
|
|
230
|
-
}, [onXDomainChange, onYDomainChange]);
|
|
215
|
+
onYDomainChange?.(initialDomains.y);
|
|
216
|
+
}, [initialDomains, onXDomainChange, onYDomainChange]);
|
|
231
217
|
|
|
232
218
|
// Toolbar portal target
|
|
233
219
|
const portalTarget = svgRef.current?.parentElement;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/lab",
|
|
3
|
-
"version": "0.7.8-canary.
|
|
3
|
+
"version": "0.7.8-canary.55aaeda",
|
|
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.8-canary.
|
|
52
|
+
"@astryxdesign/core": "0.7.8-canary.55aaeda",
|
|
53
53
|
"react": ">=19.0.0",
|
|
54
54
|
"react-dom": ">=19.0.0"
|
|
55
55
|
},
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
"d3-shape": "^3.2.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@astryxdesign/a11y-spec": "0.7.8-canary.
|
|
63
|
-
"@astryxdesign/cli": "0.7.8-canary.
|
|
64
|
-
"@astryxdesign/interaction-harness": "0.7.8-canary.
|
|
62
|
+
"@astryxdesign/a11y-spec": "0.7.8-canary.55aaeda",
|
|
63
|
+
"@astryxdesign/cli": "0.7.8-canary.55aaeda",
|
|
64
|
+
"@astryxdesign/interaction-harness": "0.7.8-canary.55aaeda",
|
|
65
65
|
"@babel/cli": "^7.29.7",
|
|
66
66
|
"@babel/core": "^7.29.7",
|
|
67
67
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file ChartZoom.test.tsx
|
|
5
5
|
* @input Renders ChartZoom inside Chart with a stubbed ResizeObserver
|
|
6
|
-
* @output Render-smoke coverage for zoom: capture surface, portaled toolbar, toolbar opt-out
|
|
6
|
+
* @output Render-smoke coverage for zoom: capture surface, portaled toolbar, toolbar opt-out,
|
|
7
|
+
* and Reset back to the first render's domains.
|
|
7
8
|
* Wheel/pinch gestures need createSVGPoint/getScreenCTM, which do not exist in
|
|
8
9
|
* jsdom, so they are intentionally out of scope here. (#4295)
|
|
9
10
|
* @position Colocated test for ChartZoom.tsx
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
|
|
13
|
-
import {render, screen, act} from '@testing-library/react';
|
|
14
|
+
import {render, screen, act, fireEvent} from '@testing-library/react';
|
|
14
15
|
import {Chart} from './Chart';
|
|
15
16
|
import {ChartZoom} from './ChartZoom';
|
|
16
17
|
|
|
@@ -87,6 +88,44 @@ describe('ChartZoom', () => {
|
|
|
87
88
|
).toBeInTheDocument();
|
|
88
89
|
});
|
|
89
90
|
|
|
91
|
+
it("resets to the first render's domains after the caller changes them", () => {
|
|
92
|
+
const onXDomainChange = vi.fn();
|
|
93
|
+
const onYDomainChange = vi.fn();
|
|
94
|
+
function ZoomedChart({
|
|
95
|
+
xDomain,
|
|
96
|
+
yDomain,
|
|
97
|
+
}: {
|
|
98
|
+
xDomain: [number, number];
|
|
99
|
+
yDomain: [number, number];
|
|
100
|
+
}) {
|
|
101
|
+
return (
|
|
102
|
+
<Chart
|
|
103
|
+
data={data}
|
|
104
|
+
xKey="x"
|
|
105
|
+
yKeys={['y']}
|
|
106
|
+
xDomain={xDomain}
|
|
107
|
+
yDomain={yDomain}
|
|
108
|
+
interactive>
|
|
109
|
+
<ChartZoom
|
|
110
|
+
onXDomainChange={onXDomainChange}
|
|
111
|
+
onYDomainChange={onYDomainChange}
|
|
112
|
+
/>
|
|
113
|
+
</Chart>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const {rerender} = render(
|
|
117
|
+
<ZoomedChart xDomain={[0, 2]} yDomain={[0, 4]} />,
|
|
118
|
+
);
|
|
119
|
+
reportWidth(464);
|
|
120
|
+
reportWidth(465);
|
|
121
|
+
|
|
122
|
+
rerender(<ZoomedChart xDomain={[0.5, 1.5]} yDomain={[1, 3]} />);
|
|
123
|
+
fireEvent.click(screen.getByRole('button', {name: 'Reset zoom'}));
|
|
124
|
+
|
|
125
|
+
expect(onXDomainChange).toHaveBeenLastCalledWith([0, 2]);
|
|
126
|
+
expect(onYDomainChange).toHaveBeenLastCalledWith([0, 4]);
|
|
127
|
+
});
|
|
128
|
+
|
|
90
129
|
it('renders no toolbar when toolbar is false', () => {
|
|
91
130
|
render(
|
|
92
131
|
<Chart data={data} xKey="x" yKeys={['y']} interactive>
|
package/src/Chart/ChartZoom.tsx
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Zoom is aspect-ratio-normalized: both axes scale by the same visual
|
|
9
9
|
* factor regardless of their domain ranges. Reset button restores
|
|
10
|
-
* the
|
|
10
|
+
* the domains of the first render.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {type CSSProperties, useCallback, useRef,
|
|
13
|
+
import {type CSSProperties, useCallback, useRef, useState} from 'react';
|
|
14
14
|
import {createPortal} from 'react-dom';
|
|
15
15
|
import {IconButton} from '@astryxdesign/core/IconButton';
|
|
16
16
|
import {useChart} from './ChartContext';
|
|
@@ -101,22 +101,15 @@ export function ChartZoom({
|
|
|
101
101
|
}: ChartZoomProps) {
|
|
102
102
|
const {width, height, xScale, yScale, svgRef} = useChart();
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
const initialDomainsRef = useRef<{
|
|
104
|
+
const [initialDomains] = useState<{
|
|
106
105
|
x: [number, number] | null;
|
|
107
106
|
y: [number, number];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
if (initialDomainsRef.current) {
|
|
112
|
-
return;
|
|
113
|
-
} // only capture once
|
|
114
|
-
const xDomain = isBandScale(xScale)
|
|
107
|
+
}>(() => ({
|
|
108
|
+
x: isBandScale(xScale)
|
|
115
109
|
? null
|
|
116
|
-
: ((xScale as ScaleLinear<number, number>).domain() as [number, number])
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}, [xScale, yScale]);
|
|
110
|
+
: ((xScale as ScaleLinear<number, number>).domain() as [number, number]),
|
|
111
|
+
y: yScale.domain() as [number, number],
|
|
112
|
+
}));
|
|
120
113
|
|
|
121
114
|
const dragRef = useRef<{
|
|
122
115
|
startX: number;
|
|
@@ -303,15 +296,11 @@ export function ChartZoom({
|
|
|
303
296
|
}, []);
|
|
304
297
|
|
|
305
298
|
const handleReset = useCallback(() => {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (init.x) {
|
|
311
|
-
onXDomainChange?.(init.x);
|
|
299
|
+
if (initialDomains.x) {
|
|
300
|
+
onXDomainChange?.(initialDomains.x);
|
|
312
301
|
}
|
|
313
|
-
onYDomainChange?.(
|
|
314
|
-
}, [onXDomainChange, onYDomainChange]);
|
|
302
|
+
onYDomainChange?.(initialDomains.y);
|
|
303
|
+
}, [initialDomains, onXDomainChange, onYDomainChange]);
|
|
315
304
|
|
|
316
305
|
// Toolbar portal target
|
|
317
306
|
const portalTarget = svgRef.current?.parentElement;
|
|
@@ -28,8 +28,10 @@ import {
|
|
|
28
28
|
import {
|
|
29
29
|
busyWindowProof,
|
|
30
30
|
CONTROL_RGB,
|
|
31
|
+
countBetween,
|
|
31
32
|
inPresentationOrder,
|
|
32
33
|
startBusyControl,
|
|
34
|
+
traceRendering,
|
|
33
35
|
trackMarker,
|
|
34
36
|
type TrackedFrame,
|
|
35
37
|
} from '@astryxdesign/interaction-harness';
|
|
@@ -413,50 +415,47 @@ test('a value change keeps moving while a script holds the main thread', async (
|
|
|
413
415
|
).toBeLessThanOrEqual(6);
|
|
414
416
|
});
|
|
415
417
|
|
|
416
|
-
test('a value change lays out and paints nothing per frame', async ({
|
|
417
|
-
page,
|
|
418
|
-
browser,
|
|
419
|
-
}) => {
|
|
418
|
+
test('a value change lays out and paints nothing per frame', async ({page}) => {
|
|
420
419
|
await open(page, 20);
|
|
421
|
-
// Stretch the theme's duration so the
|
|
420
|
+
// Stretch the theme's duration so the second judged below sits wholly
|
|
421
|
+
// inside the change.
|
|
422
422
|
await page.evaluate(() =>
|
|
423
423
|
(
|
|
424
424
|
document.querySelector('.astryx-circular-progress-fill') as HTMLElement
|
|
425
425
|
).style.setProperty('--duration-medium', '3000ms'),
|
|
426
426
|
);
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
427
|
+
// Tracing starts before the change, so however long it takes to start, it
|
|
428
|
+
// takes none of the change's time. The judged second starts two frames
|
|
429
|
+
// after the change's animations are ready, 200 ms in.
|
|
430
|
+
let from = 0;
|
|
431
|
+
let to = 0;
|
|
432
|
+
let moving = false;
|
|
433
|
+
const trace = await traceRendering(page, async () => {
|
|
434
|
+
await setArgs(page, {value: 80});
|
|
435
|
+
await page.waitForTimeout(200);
|
|
436
|
+
from = await page.evaluate(async () => {
|
|
437
|
+
await Promise.all(document.getAnimations().map(async a => a.ready));
|
|
438
|
+
await new Promise(resolve =>
|
|
439
|
+
requestAnimationFrame(() => requestAnimationFrame(resolve)),
|
|
440
|
+
);
|
|
441
|
+
return performance.timeOrigin + performance.now();
|
|
442
|
+
});
|
|
443
|
+
await page.waitForTimeout(1000);
|
|
444
|
+
[to, moving] = await page.evaluate(
|
|
445
|
+
() =>
|
|
446
|
+
[
|
|
447
|
+
performance.timeOrigin + performance.now(),
|
|
448
|
+
document.getAnimations().some(a => a.playState === 'running'),
|
|
449
|
+
] as const,
|
|
450
|
+
);
|
|
431
451
|
});
|
|
432
|
-
|
|
433
|
-
const moving = await page.evaluate(() =>
|
|
434
|
-
document.getAnimations().some(a => a.playState === 'running'),
|
|
435
|
-
);
|
|
436
|
-
const trace = JSON.parse((await browser.stopTracing()).toString()) as {
|
|
437
|
-
traceEvents: {
|
|
438
|
-
name: string;
|
|
439
|
-
ph: string;
|
|
440
|
-
pid: number;
|
|
441
|
-
tid: number;
|
|
442
|
-
args?: {name?: string};
|
|
443
|
-
}[];
|
|
444
|
-
};
|
|
445
|
-
const mainThreads = new Set(
|
|
446
|
-
trace.traceEvents
|
|
447
|
-
.filter(e => e.ph === 'M' && e.name === 'thread_name')
|
|
448
|
-
.filter(e => e.args?.name === 'CrRendererMain')
|
|
449
|
-
.map(e => `${e.pid}:${e.tid}`),
|
|
450
|
-
);
|
|
451
|
-
const count = (name: string) =>
|
|
452
|
-
trace.traceEvents.filter(
|
|
453
|
-
e => e.name === name && mainThreads.has(`${e.pid}:${e.tid}`),
|
|
454
|
-
).length;
|
|
452
|
+
const counts = countBetween(trace, from, to);
|
|
455
453
|
// A full second of the change: nothing lays out or paints. A
|
|
456
454
|
// stroke-dashoffset transition repainted every frame (36 paints in 410 ms).
|
|
455
|
+
expect(to - from).toBeGreaterThanOrEqual(1000);
|
|
457
456
|
expect(moving).toBe(true);
|
|
458
|
-
expect(
|
|
459
|
-
expect(
|
|
457
|
+
expect(counts.Layout ?? 0).toBe(0);
|
|
458
|
+
expect(counts.Paint ?? 0).toBe(0);
|
|
460
459
|
});
|
|
461
460
|
|
|
462
461
|
test('at rest the ring draws what the stroked circle drew', async ({
|