@agent-native/toolkit 0.18.0 → 0.18.1-nightly-20260829003721
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/ui/cube-loader.d.ts +1 -1
- package/dist/ui/cube-loader.d.ts.map +1 -1
- package/dist/ui/cube-loader.js +20 -3
- package/dist/ui/cube-loader.js.map +1 -1
- package/dist/ui/cube-loader.spec.js +46 -12
- package/dist/ui/cube-loader.spec.js.map +1 -1
- package/package.json +1 -1
- package/src/ui/cube-loader.spec.tsx +62 -17
- package/src/ui/cube-loader.tsx +30 -1
package/dist/ui/cube-loader.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type CubeLoaderProps = Omit<React.SVGProps<SVGSVGElement>, "children"> & {
|
|
2
2
|
size?: number | string;
|
|
3
3
|
};
|
|
4
|
-
export declare function CubeLoader({ className, size, width, height, ...props }: CubeLoaderProps): import("react").JSX.Element;
|
|
4
|
+
export declare function CubeLoader({ className, size, width, height, ref, ...props }: CubeLoaderProps): import("react").JSX.Element;
|
|
5
5
|
//# sourceMappingURL=cube-loader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cube-loader.d.ts","sourceRoot":"","sources":["../../src/ui/cube-loader.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cube-loader.d.ts","sourceRoot":"","sources":["../../src/ui/cube-loader.tsx"],"names":[],"mappings":"AAyBA,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,EAC7B,UAAU,CACX,GAAG;IACF,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,IAAI,EACJ,KAAK,EACL,MAAM,EACN,GAAG,EACH,GAAG,KAAK,EACT,EAAE,eAAe,+BAqDjB"}
|
package/dist/ui/cube-loader.js
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback } from "react";
|
|
2
3
|
import { cn } from "../utils.js";
|
|
3
4
|
const CUBE_DELAYS = [90, 180, 270, 0, 90, 180, 90, 180, 270];
|
|
4
|
-
|
|
5
|
+
const CUBE_ANIMATION_DURATION_MS = 650;
|
|
6
|
+
function setRef(ref, value) {
|
|
7
|
+
if (typeof ref === "function")
|
|
8
|
+
ref(value);
|
|
9
|
+
else if (ref)
|
|
10
|
+
ref.current = value;
|
|
11
|
+
}
|
|
12
|
+
function setCubeAnimationPhase(svg, ref) {
|
|
13
|
+
if (svg && typeof window !== "undefined") {
|
|
14
|
+
svg.style.setProperty("--an-cube-loader-phase", `${window.performance.now() % CUBE_ANIMATION_DURATION_MS}ms`);
|
|
15
|
+
}
|
|
16
|
+
setRef(ref, svg);
|
|
17
|
+
}
|
|
18
|
+
export function CubeLoader({ className, size, width, height, ref, ...props }) {
|
|
5
19
|
const hasRole = Object.prototype.hasOwnProperty.call(props, "role");
|
|
6
20
|
const hasExplicitSize = size !== undefined || width !== undefined || height !== undefined;
|
|
7
21
|
const ariaLabel = props["aria-label"] ?? (hasRole ? undefined : "Loading");
|
|
8
|
-
|
|
22
|
+
const setRefs = useCallback((svg) => setCubeAnimationPhase(svg, ref), [ref]);
|
|
23
|
+
return (_jsxs("svg", { ...props, ref: setRefs, role: hasRole ? props.role : "status", "aria-label": ariaLabel, width: width ?? size ?? 24, height: height ?? size ?? 24, viewBox: "0 0 24 24", fill: "currentColor", className: cn(!hasExplicitSize && "size-4", className), "data-agent-native-cube-loader": "true", children: [_jsx("style", { children: `
|
|
9
24
|
@keyframes an-cube-pulse {
|
|
10
25
|
0%, 100% { opacity: 0.15; }
|
|
11
26
|
50% { opacity: 0.95; }
|
|
@@ -18,6 +33,8 @@ export function CubeLoader({ className, size, width, height, ...props }) {
|
|
|
18
33
|
@media (prefers-reduced-motion: reduce) {
|
|
19
34
|
.an-cube-cell { animation: none; }
|
|
20
35
|
}
|
|
21
|
-
` }), CUBE_DELAYS.map((delay, index) => (_jsx("rect", { className: "an-cube-cell", x: 2.5 + (index % 3) * 7, y: 2.5 + Math.floor(index / 3) * 7, width: 5, height: 5, rx: 1, style: {
|
|
36
|
+
` }), CUBE_DELAYS.map((delay, index) => (_jsx("rect", { className: "an-cube-cell", x: 2.5 + (index % 3) * 7, y: 2.5 + Math.floor(index / 3) * 7, width: 5, height: 5, rx: 1, style: {
|
|
37
|
+
animationDelay: `calc(${delay}ms - var(--an-cube-loader-phase, 0ms))`,
|
|
38
|
+
} }, index)))] }));
|
|
22
39
|
}
|
|
23
40
|
//# sourceMappingURL=cube-loader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cube-loader.js","sourceRoot":"","sources":["../../src/ui/cube-loader.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEjC,MAAM,WAAW,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"cube-loader.js","sourceRoot":"","sources":["../../src/ui/cube-loader.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAY,MAAM,OAAO,CAAC;AAE9C,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEjC,MAAM,WAAW,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAC7D,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAEvC,SAAS,MAAM,CAAU,GAA6B,EAAE,KAAqB;IAC3E,IAAI,OAAO,GAAG,KAAK,UAAU;QAAE,GAAG,CAAC,KAAK,CAAC,CAAC;SACrC,IAAI,GAAG;QAAE,GAAG,CAAC,OAAO,GAAG,KAAK,CAAC;AACpC,CAAC;AAED,SAAS,qBAAqB,CAC5B,GAAyB,EACzB,GAAwB;IAExB,IAAI,GAAG,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QACzC,GAAG,CAAC,KAAK,CAAC,WAAW,CACnB,wBAAwB,EACxB,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,0BAA0B,IAAI,CAC7D,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AACnB,CAAC;AASD,MAAM,UAAU,UAAU,CAAC,EACzB,SAAS,EACT,IAAI,EACJ,KAAK,EACL,MAAM,EACN,GAAG,EACH,GAAG,KAAK,EACQ;IAChB,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACpE,MAAM,eAAe,GACnB,IAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,CAAC;IACpE,MAAM,SAAS,GAAG,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC3E,MAAM,OAAO,GAAG,WAAW,CACzB,CAAC,GAAyB,EAAE,EAAE,CAAC,qBAAqB,CAAC,GAAG,EAAE,GAAG,CAAC,EAC9D,CAAC,GAAG,CAAC,CACN,CAAC;IAEF,OAAO,CACL,kBACM,KAAK,EACT,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,gBACzB,SAAS,EACrB,KAAK,EAAE,KAAK,IAAI,IAAI,IAAI,EAAE,EAC1B,MAAM,EAAE,MAAM,IAAI,IAAI,IAAI,EAAE,EAC5B,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,cAAc,EACnB,SAAS,EAAE,EAAE,CAAC,CAAC,eAAe,IAAI,QAAQ,EAAE,SAAS,CAAC,mCACxB,MAAM,aAEpC,0BAAQ;;;;;;;;;;;;;OAaP,GAAS,EACT,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CACjC,eAEE,SAAS,EAAC,cAAc,EACxB,CAAC,EAAE,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,EACxB,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,EAClC,KAAK,EAAE,CAAC,EACR,MAAM,EAAE,CAAC,EACT,EAAE,EAAE,CAAC,EACL,KAAK,EAAE;oBACL,cAAc,EAAE,QAAQ,KAAK,wCAAwC;iBACtE,IATI,KAAK,CAUV,CACH,CAAC,IACE,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -1,23 +1,57 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// @vitest-environment happy-dom
|
|
3
|
+
import { act } from "react";
|
|
4
|
+
import { createElement } from "react";
|
|
5
|
+
import { createRoot } from "react-dom/client";
|
|
6
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
8
|
import { CubeLoader } from "./cube-loader.js";
|
|
3
9
|
describe("CubeLoader", () => {
|
|
10
|
+
let container;
|
|
11
|
+
let root;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
14
|
+
container = document.createElement("div");
|
|
15
|
+
document.body.appendChild(container);
|
|
16
|
+
root = createRoot(container);
|
|
17
|
+
});
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
act(() => root.unmount());
|
|
20
|
+
container.remove();
|
|
21
|
+
vi.unstubAllGlobals();
|
|
22
|
+
});
|
|
4
23
|
it("honors explicit sizing and decorative semantics", () => {
|
|
5
|
-
const loader = CubeLoader
|
|
24
|
+
const loader = renderToStaticMarkup(createElement(CubeLoader, {
|
|
6
25
|
"aria-label": undefined,
|
|
7
26
|
role: undefined,
|
|
8
27
|
size: 10,
|
|
9
|
-
});
|
|
10
|
-
expect(loader
|
|
11
|
-
expect(loader
|
|
12
|
-
expect(loader
|
|
13
|
-
expect(loader.
|
|
14
|
-
expect(loader.
|
|
28
|
+
}));
|
|
29
|
+
expect(loader).toContain('width="10"');
|
|
30
|
+
expect(loader).toContain('height="10"');
|
|
31
|
+
expect(loader).not.toContain('class="size-4"');
|
|
32
|
+
expect(loader).not.toContain("role=");
|
|
33
|
+
expect(loader).not.toContain("aria-label=");
|
|
15
34
|
});
|
|
16
35
|
it("adds accessible defaults when role semantics are omitted", () => {
|
|
17
|
-
const loader = CubeLoader
|
|
18
|
-
expect(loader
|
|
19
|
-
expect(loader.
|
|
20
|
-
expect(loader
|
|
36
|
+
const loader = renderToStaticMarkup(createElement(CubeLoader));
|
|
37
|
+
expect(loader).toContain('role="status"');
|
|
38
|
+
expect(loader).toContain('aria-label="Loading"');
|
|
39
|
+
expect(loader).toContain('class="size-4"');
|
|
40
|
+
});
|
|
41
|
+
it("anchors its cells to the page timeline when mounted again", () => {
|
|
42
|
+
expect(renderToStaticMarkup(createElement(CubeLoader))).toContain("calc(90ms - var(--an-cube-loader-phase, 0ms))");
|
|
43
|
+
});
|
|
44
|
+
it("preserves a caller ref without reattaching on rerenders", () => {
|
|
45
|
+
const callerRef = vi.fn();
|
|
46
|
+
act(() => {
|
|
47
|
+
root.render(_jsx(CubeLoader, { ref: callerRef, "data-testid": "first-render" }));
|
|
48
|
+
});
|
|
49
|
+
expect(callerRef).toHaveBeenCalledTimes(1);
|
|
50
|
+
expect(callerRef).toHaveBeenLastCalledWith(expect.any(SVGSVGElement));
|
|
51
|
+
act(() => {
|
|
52
|
+
root.render(_jsx(CubeLoader, { ref: callerRef, "data-testid": "second-render" }));
|
|
53
|
+
});
|
|
54
|
+
expect(callerRef).toHaveBeenCalledTimes(1);
|
|
21
55
|
});
|
|
22
56
|
});
|
|
23
57
|
//# sourceMappingURL=cube-loader.spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cube-loader.spec.js","sourceRoot":"","sources":["../../src/ui/cube-loader.spec.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cube-loader.spec.js","sourceRoot":"","sources":["../../src/ui/cube-loader.spec.tsx"],"names":[],"mappings":";AAAA,gCAAgC;AAEhC,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,UAAU,EAAa,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEzE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,IAAI,SAAyB,CAAC;IAC9B,IAAI,IAAU,CAAC;IAEf,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,UAAU,CAAC,0BAA0B,EAAE,IAAI,CAAC,CAAC;QAChD,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC1C,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1B,SAAS,CAAC,MAAM,EAAE,CAAC;QACnB,EAAE,CAAC,gBAAgB,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,MAAM,GAAG,oBAAoB,CACjC,aAAa,CAAC,UAAU,EAAE;YACxB,YAAY,EAAE,SAAS;YACvB,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,EAAE;SACT,CAAC,CACH,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACvC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,MAAM,GAAG,oBAAoB,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC;QAE/D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,CAAC,oBAAoB,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAC/D,+CAA+C,CAChD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAE1B,GAAG,CAAC,GAAG,EAAE;YACP,IAAI,CAAC,MAAM,CAAC,KAAC,UAAU,IAAC,GAAG,EAAE,SAAS,iBAAc,cAAc,GAAG,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,SAAS,CAAC,CAAC,wBAAwB,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC;QAEtE,GAAG,CAAC,GAAG,EAAE;YACP,IAAI,CAAC,MAAM,CAAC,KAAC,UAAU,IAAC,GAAG,EAAE,SAAS,iBAAc,eAAe,GAAG,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,28 +1,73 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import { act } from "react";
|
|
4
|
+
import { createElement } from "react";
|
|
5
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
6
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
8
|
|
|
4
9
|
import { CubeLoader } from "./cube-loader.js";
|
|
5
10
|
|
|
6
11
|
describe("CubeLoader", () => {
|
|
12
|
+
let container: HTMLDivElement;
|
|
13
|
+
let root: Root;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
17
|
+
container = document.createElement("div");
|
|
18
|
+
document.body.appendChild(container);
|
|
19
|
+
root = createRoot(container);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
act(() => root.unmount());
|
|
24
|
+
container.remove();
|
|
25
|
+
vi.unstubAllGlobals();
|
|
26
|
+
});
|
|
27
|
+
|
|
7
28
|
it("honors explicit sizing and decorative semantics", () => {
|
|
8
|
-
const loader =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
expect(loader
|
|
17
|
-
expect(loader
|
|
18
|
-
expect(loader.
|
|
29
|
+
const loader = renderToStaticMarkup(
|
|
30
|
+
createElement(CubeLoader, {
|
|
31
|
+
"aria-label": undefined,
|
|
32
|
+
role: undefined,
|
|
33
|
+
size: 10,
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
expect(loader).toContain('width="10"');
|
|
38
|
+
expect(loader).toContain('height="10"');
|
|
39
|
+
expect(loader).not.toContain('class="size-4"');
|
|
40
|
+
expect(loader).not.toContain("role=");
|
|
41
|
+
expect(loader).not.toContain("aria-label=");
|
|
19
42
|
});
|
|
20
43
|
|
|
21
44
|
it("adds accessible defaults when role semantics are omitted", () => {
|
|
22
|
-
const loader = CubeLoader
|
|
45
|
+
const loader = renderToStaticMarkup(createElement(CubeLoader));
|
|
46
|
+
|
|
47
|
+
expect(loader).toContain('role="status"');
|
|
48
|
+
expect(loader).toContain('aria-label="Loading"');
|
|
49
|
+
expect(loader).toContain('class="size-4"');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("anchors its cells to the page timeline when mounted again", () => {
|
|
53
|
+
expect(renderToStaticMarkup(createElement(CubeLoader))).toContain(
|
|
54
|
+
"calc(90ms - var(--an-cube-loader-phase, 0ms))",
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("preserves a caller ref without reattaching on rerenders", () => {
|
|
59
|
+
const callerRef = vi.fn();
|
|
60
|
+
|
|
61
|
+
act(() => {
|
|
62
|
+
root.render(<CubeLoader ref={callerRef} data-testid="first-render" />);
|
|
63
|
+
});
|
|
64
|
+
expect(callerRef).toHaveBeenCalledTimes(1);
|
|
65
|
+
expect(callerRef).toHaveBeenLastCalledWith(expect.any(SVGSVGElement));
|
|
66
|
+
|
|
67
|
+
act(() => {
|
|
68
|
+
root.render(<CubeLoader ref={callerRef} data-testid="second-render" />);
|
|
69
|
+
});
|
|
23
70
|
|
|
24
|
-
expect(
|
|
25
|
-
expect(loader.props["aria-label"]).toBe("Loading");
|
|
26
|
-
expect(loader.props.className).toContain("size-4");
|
|
71
|
+
expect(callerRef).toHaveBeenCalledTimes(1);
|
|
27
72
|
});
|
|
28
73
|
});
|
package/src/ui/cube-loader.tsx
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
|
+
import { useCallback, type Ref } from "react";
|
|
2
|
+
|
|
1
3
|
import { cn } from "../utils.js";
|
|
2
4
|
|
|
3
5
|
const CUBE_DELAYS = [90, 180, 270, 0, 90, 180, 90, 180, 270];
|
|
6
|
+
const CUBE_ANIMATION_DURATION_MS = 650;
|
|
7
|
+
|
|
8
|
+
function setRef<Element>(ref: Ref<Element> | undefined, value: Element | null) {
|
|
9
|
+
if (typeof ref === "function") ref(value);
|
|
10
|
+
else if (ref) ref.current = value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function setCubeAnimationPhase(
|
|
14
|
+
svg: SVGSVGElement | null,
|
|
15
|
+
ref?: Ref<SVGSVGElement>,
|
|
16
|
+
) {
|
|
17
|
+
if (svg && typeof window !== "undefined") {
|
|
18
|
+
svg.style.setProperty(
|
|
19
|
+
"--an-cube-loader-phase",
|
|
20
|
+
`${window.performance.now() % CUBE_ANIMATION_DURATION_MS}ms`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
setRef(ref, svg);
|
|
24
|
+
}
|
|
4
25
|
|
|
5
26
|
export type CubeLoaderProps = Omit<
|
|
6
27
|
React.SVGProps<SVGSVGElement>,
|
|
@@ -14,16 +35,22 @@ export function CubeLoader({
|
|
|
14
35
|
size,
|
|
15
36
|
width,
|
|
16
37
|
height,
|
|
38
|
+
ref,
|
|
17
39
|
...props
|
|
18
40
|
}: CubeLoaderProps) {
|
|
19
41
|
const hasRole = Object.prototype.hasOwnProperty.call(props, "role");
|
|
20
42
|
const hasExplicitSize =
|
|
21
43
|
size !== undefined || width !== undefined || height !== undefined;
|
|
22
44
|
const ariaLabel = props["aria-label"] ?? (hasRole ? undefined : "Loading");
|
|
45
|
+
const setRefs = useCallback(
|
|
46
|
+
(svg: SVGSVGElement | null) => setCubeAnimationPhase(svg, ref),
|
|
47
|
+
[ref],
|
|
48
|
+
);
|
|
23
49
|
|
|
24
50
|
return (
|
|
25
51
|
<svg
|
|
26
52
|
{...props}
|
|
53
|
+
ref={setRefs}
|
|
27
54
|
role={hasRole ? props.role : "status"}
|
|
28
55
|
aria-label={ariaLabel}
|
|
29
56
|
width={width ?? size ?? 24}
|
|
@@ -56,7 +83,9 @@ export function CubeLoader({
|
|
|
56
83
|
width={5}
|
|
57
84
|
height={5}
|
|
58
85
|
rx={1}
|
|
59
|
-
style={{
|
|
86
|
+
style={{
|
|
87
|
+
animationDelay: `calc(${delay}ms - var(--an-cube-loader-phase, 0ms))`,
|
|
88
|
+
}}
|
|
60
89
|
/>
|
|
61
90
|
))}
|
|
62
91
|
</svg>
|