;
+ mutate(() => {
+ pending = dialog.openAsync();
+ });
+ expect(document.querySelector("[data-overlay]")).not.toBeNull();
+
+ view.unmount();
+
+ await expect(pending).resolves.toBeUndefined();
+ expect(document.querySelector("[data-overlay]")).toBeNull();
+ });
+
+ it("does not rerender overlay instances when OverlayProvider children rerender", () => {
+ const cue = createCue();
+ let renders = 0;
+ const dialog = cue.createOverlay(() => {
+ renders += 1;
+ return createElement("div", { "data-overlay": true });
+ });
+
+ function App({ label }: { label: string }) {
+ return createElement(
+ cue.OverlayProvider,
+ null,
+ createElement("span", { "data-label": label }),
+ );
+ }
+
+ const view = render(createElement(App, { label: "first" }));
+ mutate(() => {
+ dialog.open();
+ });
+ const rendersAfterOpen = renders;
+
+ view.rerender(createElement(App, { label: "second" }));
+
+ expect(renders).toBe(rendersAfterOpen);
+ expect(document.querySelector("[data-label]")?.getAttribute("data-label")).toBe("second");
+ });
+
+ it("does not render overlays from a second provider", () => {
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const cue = createCue();
+ const dialog = cue.createOverlay(() => createElement("div", { "data-overlay": true }));
+ const first = document.createElement("div");
+ const second = document.createElement("div");
+ document.body.append(first, second);
+
+ render(createElement(cue.OverlayProvider, { container: first }));
+ render(createElement(cue.OverlayProvider, { container: second }));
+ mutate(() => {
+ dialog.open();
+ });
+
+ expect(first.querySelector("[data-overlay]")).not.toBeNull();
+ expect(second.querySelector("[data-overlay]")).toBeNull();
+ expect(warn).toHaveBeenCalled();
+
+ warn.mockRestore();
+ first.remove();
+ second.remove();
+ });
+
+ it("uses the Cue environment delay as the default close delay", () => {
+ vi.useFakeTimers();
+ const cue = createCue({ delay: 40 });
+ let close: OverlayContext["close"] | undefined;
+ const dialog = cue.createOverlay((_props, context) => {
+ close = context.close;
+ return createElement("div", { "data-overlay": true });
+ });
+
+ mount(cue);
+ mutate(() => {
+ dialog.open();
+ });
+ mutate(() => {
+ close?.();
+ });
+
+ expect(document.querySelector("[data-overlay]")).not.toBeNull();
+ mutate(() => {
+ vi.advanceTimersByTime(39);
+ });
+ expect(document.querySelector("[data-overlay]")).not.toBeNull();
+ mutate(() => {
+ vi.advanceTimersByTime(1);
+ });
+ expect(document.querySelector("[data-overlay]")).toBeNull();
+ });
+});
diff --git a/packages/react/src/create-cue.ts b/packages/react/src/create-cue.ts
index 4cd405b..3624b49 100644
--- a/packages/react/src/create-cue.ts
+++ b/packages/react/src/create-cue.ts
@@ -1,83 +1,84 @@
+"use client";
+
import { createOverlayProvider } from "./overlay-provider.js";
import { createOverlayStore } from "./overlay-manager.js";
import type {
CloseOptions,
- CueBackdrop,
CueComponents,
- OverlayRenderer,
+ CueOptions,
OverlayDefinition,
+ OverlayDefinitionOptions,
+ OverlayHandle,
+ OverlayRenderer,
} from "./types.js";
-type OpenArguments = {} extends P ? [props?: P] : [props: P];
-
-type OverlayHandle
= {
- /** Open one instance and return a close function bound to that instance. */
- open: (...args: OpenArguments
) => () => void;
- /** Open one instance and resolve when that instance closes. */
- openAsync: (...args: OpenArguments
) => Promise;
- /** Close every live instance created from this overlay definition. */
- closeAll: (options?: CloseOptions) => void;
-};
-
-type CueOptions = {
- backdrop?: CueBackdrop;
- components?: C;
-};
-
/** Create an isolated overlay environment with its own provider and lifecycle state. */
export function createCue(options: CueOptions = {}) {
- const store = createOverlayStore();
+ const store = createOverlayStore({ delay: options.delay });
const components = (options.components ?? {}) as C;
- const OverlayProvider = createOverlayProvider({ store, components, backdrop: options.backdrop });
+ const pendingResolvers = new Map void>();
+
+ function settle(instanceId: string, result: unknown) {
+ pendingResolvers.get(instanceId)?.(result);
+ pendingResolvers.delete(instanceId);
+ }
+
+ function teardown() {
+ for (const resolve of pendingResolvers.values()) {
+ resolve(undefined);
+ }
+
+ pendingResolvers.clear();
+ store.reset();
+ }
+
+ const OverlayProvider = createOverlayProvider({
+ store,
+ components,
+ backdrop: options.backdrop,
+ teardown,
+ });
/** Create an overlay definition owned by this Cue environment. */
function createOverlay(
render: OverlayRenderer
,
+ definitionOptions: OverlayDefinitionOptions = {},
): OverlayHandle
{
const definition: OverlayDefinition = {
render: render as OverlayDefinition["render"],
+ backdrop: definitionOptions.backdrop,
};
- const pendingResolvers = new Map void>();
-
- function settle(instanceId: string, result: R | undefined) {
- pendingResolvers.get(instanceId)?.(result);
- pendingResolvers.delete(instanceId);
- }
- function closeInstance(instanceId: string, options: CloseOptions = {}) {
- settle(instanceId, options.result);
- store.close(instanceId, { delay: options.delay });
+ function closeInstance(instanceId: string, closeOptions: CloseOptions = {}) {
+ settle(instanceId, closeOptions.result);
+ store.close(instanceId, { delay: closeOptions.delay });
}
function mountInstance(props: P | undefined) {
let instanceId = "";
- const close = (options?: CloseOptions) => closeInstance(instanceId, options);
+ const close = (closeOptions?: CloseOptions) => closeInstance(instanceId, closeOptions);
instanceId = store.add(definition, props ?? {}, close);
return { instanceId, close };
}
- function open(...args: OpenArguments) {
+ function open(...args: Parameters["open"]>) {
return mountInstance(args[0]).close;
}
- function openAsync(...args: OpenArguments) {
+ function openAsync(...args: Parameters["openAsync"]>) {
return new Promise((resolve) => {
const { instanceId } = mountInstance(args[0]);
- pendingResolvers.set(instanceId, resolve);
+ pendingResolvers.set(instanceId, resolve as (result: unknown) => void);
});
}
- function closeAll(options: CloseOptions = {}) {
- const instances = store
- .all()
- .filter(
- (overlay) => overlay.definition === definition && (overlay.open || overlay.visible),
- );
+ function closeAll(closeOptions: CloseOptions = {}) {
+ const instances = store.all().filter((overlay) => overlay.definition === definition);
for (const instance of instances) {
- closeInstance(instance.id, options);
+ closeInstance(instance.id, closeOptions);
}
}
diff --git a/packages/react/src/create-overlay.test-d.ts b/packages/react/src/create-overlay.test-d.ts
index 6a145a5..7eb99f3 100644
--- a/packages/react/src/create-overlay.test-d.ts
+++ b/packages/react/src/create-overlay.test-d.ts
@@ -1,6 +1,14 @@
import { createElement, type ComponentProps, type ReactNode } from "react";
import { expectTypeOf, test } from "vitest";
-import { createCue, type CloseOptions } from "./index.js";
+import {
+ createCue,
+ type CloseOptions,
+ type CueBackdropCloseOptions,
+ type CueOptions,
+ type OverlayDefinitionOptions,
+ type OverlayHandle,
+ type OverlayProviderProps,
+} from "./index.js";
function Wrapper({ children }: { children?: ReactNode }) {
return createElement("div", null, children);
@@ -25,16 +33,42 @@ test("createCue infers the exact application component map", () => {
});
});
-test("backdrop receives a strategy-based close callback", () => {
+test("backdrop receives open and a strategy-based close callback", () => {
createCue({
- backdrop: ({ close }) => {
- expectTypeOf(close).toEqualTypeOf<(options: { strategy: "last" | "all" }) => void>();
+ backdrop: ({ open, close }) => {
+ expectTypeOf(open).toBeBoolean();
+ expectTypeOf(close).toEqualTypeOf<(options: CueBackdropCloseOptions) => void>();
return null;
},
});
});
+test("createCue accepts delay next to backdrop and components", () => {
+ expectTypeOf().toMatchTypeOf<{
+ delay?: number;
+ backdrop?: CueOptions["backdrop"];
+ components?: CueOptions["components"];
+ }>();
+});
+
+test("createOverlay accepts a definition-level backdrop override", () => {
+ const cue = createCue();
+
+ cue.createOverlay(() => null, { backdrop: false });
+ cue.createOverlay(() => null, {
+ backdrop: ({ open, close }) => {
+ expectTypeOf(open).toBeBoolean();
+ void close;
+ return null;
+ },
+ });
+
+ expectTypeOf().toMatchTypeOf<{
+ backdrop?: false | CueOptions["backdrop"];
+ }>();
+});
+
test("context keeps runtime values out of application props", () => {
const cue = createCue();
@@ -79,7 +113,7 @@ test("required props remain required for open and openAsync", () => {
dialog.open({});
});
-test("result types flow through openAsync and context close", () => {
+test("result types flow through openAsync, context close, and open's close", () => {
const cue = createCue();
const dialog = cue.createOverlay<{}, boolean>((_props, context) => {
expectTypeOf(context.close).toEqualTypeOf<(options?: CloseOptions) => void>();
@@ -91,15 +125,19 @@ test("result types flow through openAsync and context close", () => {
return null;
});
+ expectTypeOf(dialog.open()).toEqualTypeOf<(options?: CloseOptions) => void>();
expectTypeOf(dialog.openAsync()).resolves.toEqualTypeOf();
+ expectTypeOf["open"]>().toMatchTypeOf();
});
-test("provider exposes only its children prop", () => {
+test("provider exposes children and an optional portal container", () => {
const cue = createCue({ components: { footer: Footer } });
const Provider = cue.OverlayProvider;
- expectTypeOf>().toEqualTypeOf<{
+ expectTypeOf>().toEqualTypeOf();
+ expectTypeOf().toMatchTypeOf<{
children?: ReactNode;
+ container?: Element | DocumentFragment;
}>();
});
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index d1ca7b7..aaea15a 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -1,2 +1,12 @@
export { createCue } from "./create-cue.js";
-export type { CloseOptions, OverlayContext } from "./types.js";
+export type {
+ CloseOptions,
+ CueBackdrop,
+ CueBackdropCloseOptions,
+ CueComponents,
+ CueOptions,
+ OverlayContext,
+ OverlayDefinitionOptions,
+ OverlayHandle,
+ OverlayProviderProps,
+} from "./types.js";
diff --git a/packages/react/src/overlay-manager.test.ts b/packages/react/src/overlay-manager.test.ts
deleted file mode 100644
index 306c9d7..0000000
--- a/packages/react/src/overlay-manager.test.ts
+++ /dev/null
@@ -1,282 +0,0 @@
-import { renderToStaticMarkup } from "react-dom/server";
-import { createElement, type ReactNode } from "react";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import { createCue, type OverlayContext } from "./index.js";
-
-function Backdrop() {
- return createElement("div", { "data-cue-backdrop": true });
-}
-
-function renderProvider(cue: ReturnType) {
- return renderToStaticMarkup(createElement(cue.OverlayProvider));
-}
-
-function count(markup: string, marker: string) {
- return markup.split(marker).length - 1;
-}
-
-beforeEach(() => {
- vi.useFakeTimers();
-});
-
-afterEach(() => {
- vi.clearAllTimers();
- vi.useRealTimers();
-});
-
-describe("Cue isolation", () => {
- it("keeps independently created Cue instances isolated", () => {
- const a = createCue({ backdrop: Backdrop });
- const b = createCue({ backdrop: Backdrop });
- const aOverlay = a.createOverlay(() => createElement("div", { "data-overlay": "a" }));
- const bOverlay = b.createOverlay(() => createElement("div", { "data-overlay": "b" }));
-
- aOverlay.open();
-
- expect(renderProvider(a)).toContain('data-overlay="a"');
- expect(renderProvider(b)).not.toContain("data-overlay");
-
- bOverlay.open();
- expect(renderProvider(a)).not.toContain('data-overlay="b"');
- expect(renderProvider(b)).toContain('data-overlay="b"');
- });
-});
-
-describe("overlay handles", () => {
- it("keeps each returned close function bound to one instance", () => {
- const cue = createCue();
- const dialog = cue.createOverlay<{ id: string }>((props, context) =>
- createElement("div", {
- "data-overlay-id": props.id,
- "data-open": context.open,
- }),
- );
-
- const closeFirst = dialog.open({ id: "first" });
- dialog.open({ id: "second" });
- closeFirst();
-
- const markup = renderProvider(cue);
- expect(markup).toContain('data-overlay-id="first" data-open="false"');
- expect(markup).toContain('data-overlay-id="second" data-open="true"');
- });
-
- it("closes an instance through onOpenChange", () => {
- let context: OverlayContext<{}, undefined> | undefined;
- const cue = createCue();
- const dialog = cue.createOverlay((_props, nextContext) => {
- context = nextContext;
- return null;
- });
-
- dialog.open();
- renderProvider(cue);
- context?.onOpenChange(false);
-
- expect(renderProvider(cue)).toBe("");
- vi.advanceTimersByTime(300);
- expect(renderProvider(cue)).toBe("");
- });
-
- it("closeAll closes every instance from one overlay definition", () => {
- const cue = createCue();
- const dialog = cue.createOverlay((_props, context) =>
- createElement("div", { "data-open": context.open }),
- );
-
- dialog.open();
- dialog.open();
- dialog.closeAll();
-
- expect(count(renderProvider(cue), 'data-open="false"')).toBe(2);
- vi.advanceTimersByTime(300);
- expect(renderProvider(cue)).not.toContain("data-open");
- });
-});
-
-describe("async overlays", () => {
- it("keeps concurrent results isolated", async () => {
- const contexts = new Map>();
- const cue = createCue({ backdrop: Backdrop });
- const dialog = cue.createOverlay<{ id: string }, boolean>((props, context) => {
- contexts.set(props.id, context);
- return createElement("div", { "data-overlay-id": props.id });
- });
-
- const first = dialog.openAsync({ id: "first" });
- const second = dialog.openAsync({ id: "second" });
- renderProvider(cue);
-
- contexts.get("first")?.close({ result: true });
- expect(count(renderProvider(cue), "data-cue-backdrop")).toBe(1);
- contexts.get("second")?.close({ result: false });
-
- await expect(first).resolves.toBe(true);
- await expect(second).resolves.toBe(false);
- });
-
- it("settles every pending result through closeAll", async () => {
- const cue = createCue();
- const dialog = cue.createOverlay<{}, string>(() => null);
- const first = dialog.openAsync();
- const second = dialog.openAsync();
-
- dialog.closeAll({ result: "dismissed" });
-
- await expect(first).resolves.toBe("dismissed");
- await expect(second).resolves.toBe("dismissed");
- });
-
- it("resolves dismissals as undefined", async () => {
- let context: OverlayContext<{}, boolean> | undefined;
- const cue = createCue();
- const dialog = cue.createOverlay<{}, boolean>((_props, nextContext) => {
- context = nextContext;
- return null;
- });
-
- const result = dialog.openAsync();
- renderProvider(cue);
- context?.onOpenChange(false);
-
- await expect(result).resolves.toBeUndefined();
- });
-});
-
-describe("shared components", () => {
- function Wrapper({ children }: { children?: ReactNode }) {
- return createElement("section", { "data-component": "wrapper" }, children);
- }
-
- function Footer({ children }: { children?: ReactNode }) {
- return createElement("footer", { "data-component": "footer" }, children);
- }
-
- it("passes configured components through context without merging them into props", () => {
- const seenProps: object[] = [];
- const seenContexts: object[] = [];
- const cue = createCue({ components: { wrapper: Wrapper, footer: Footer } });
- const dialog = cue.createOverlay<{ message: string }>((props, context) => {
- seenProps.push(props);
- seenContexts.push(context);
- const WrapperComponent = context.components.wrapper;
- const FooterComponent = context.components.footer;
-
- return createElement(
- WrapperComponent,
- null,
- props.message,
- createElement(FooterComponent, null, "Actions"),
- );
- });
-
- dialog.open({ message: "Hello" });
- const markup = renderProvider(cue);
-
- expect(markup).toContain('data-component="wrapper"');
- expect(markup).toContain('data-component="footer"');
- expect(seenProps[0]).toEqual({ message: "Hello" });
- expect(Object.keys(seenContexts[0] ?? {}).sort()).toEqual([
- "close",
- "components",
- "onOpenChange",
- "open",
- ]);
- });
-});
-
-describe("shared backdrop", () => {
- it("lets the backdrop choose which instances to close", () => {
- let closeOverlays: ((options: { strategy: "last" | "all" }) => void) | undefined;
- function ConfiguredBackdrop({
- close,
- }: {
- close: (options: { strategy: "last" | "all" }) => void;
- }) {
- closeOverlays = close;
- return createElement("div", { "data-cue-backdrop": true });
- }
-
- const cue = createCue({ backdrop: ConfiguredBackdrop });
- const dialog = cue.createOverlay((_props, context) =>
- createElement("div", { "data-open": context.open }),
- );
-
- dialog.open();
- dialog.open();
- renderProvider(cue);
- expect(count(renderProvider(cue), "data-cue-backdrop")).toBe(1);
-
- closeOverlays?.({ strategy: "last" });
-
- expect(count(renderProvider(cue), 'data-open="false"')).toBe(1);
- expect(count(renderProvider(cue), 'data-open="true"')).toBe(1);
- expect(count(renderProvider(cue), "data-cue-backdrop")).toBe(1);
-
- closeOverlays?.({ strategy: "all" });
-
- expect(count(renderProvider(cue), 'data-open="false"')).toBe(2);
- expect(count(renderProvider(cue), 'data-open="true"')).toBe(0);
- expect(count(renderProvider(cue), "data-cue-backdrop")).toBe(0);
- });
-
- it("renders one backdrop for an open stack", () => {
- const cue = createCue({ backdrop: Backdrop });
- const dialog = cue.createOverlay(() => createElement("div", { "data-overlay": true }));
-
- expect(count(renderProvider(cue), "data-cue-backdrop")).toBe(0);
- dialog.open();
- expect(count(renderProvider(cue), "data-cue-backdrop")).toBe(1);
- dialog.open();
- expect(count(renderProvider(cue), "data-cue-backdrop")).toBe(1);
- });
-
- it("keeps the backdrop while a closing instance remains visible", () => {
- const cue = createCue({ backdrop: Backdrop });
- let renderCount = 0;
- let closeTop: OverlayContext<{}, undefined>["close"] | undefined;
- const dialog = cue.createOverlay((_props, context) => {
- if (renderCount++ === 1) {
- closeTop = context.close;
- }
- return null;
- });
- dialog.open();
- dialog.open();
- renderProvider(cue);
-
- closeTop?.({ delay: 50 });
- expect(count(renderProvider(cue), "data-cue-backdrop")).toBe(1);
-
- vi.advanceTimersByTime(50);
- expect(count(renderProvider(cue), "data-cue-backdrop")).toBe(1);
- });
-
- it("removes the backdrop when the final instance starts closing", () => {
- const cue = createCue({ backdrop: Backdrop });
- let close: OverlayContext<{}, undefined>["close"] | undefined;
- const dialog = cue.createOverlay((_props, context) => {
- close = context.close;
- return null;
- });
- dialog.open();
- renderProvider(cue);
-
- close?.({ delay: 50 });
- expect(count(renderProvider(cue), "data-cue-backdrop")).toBe(0);
- vi.advanceTimersByTime(50);
- expect(count(renderProvider(cue), "data-cue-backdrop")).toBe(0);
- });
-
- it("removes the backdrop when closeAll starts closing the final instances", () => {
- const cue = createCue({ backdrop: Backdrop });
- const dialog = cue.createOverlay(() => null);
- dialog.open();
- dialog.open();
-
- dialog.closeAll({ delay: 25 });
- expect(count(renderProvider(cue), "data-cue-backdrop")).toBe(0);
- vi.advanceTimersByTime(25);
- expect(count(renderProvider(cue), "data-cue-backdrop")).toBe(0);
- });
-});
diff --git a/packages/react/src/overlay-manager.ts b/packages/react/src/overlay-manager.ts
index 7aca31c..9b56fc8 100644
--- a/packages/react/src/overlay-manager.ts
+++ b/packages/react/src/overlay-manager.ts
@@ -3,7 +3,6 @@ import type { OverlayDefinition } from "./types.js";
export type OverlayInstance = {
id: string;
open: boolean;
- visible: boolean;
props: object;
definition: OverlayDefinition;
close: () => void;
@@ -11,10 +10,11 @@ export type OverlayInstance = {
export type OverlayStore = ReturnType;
-export function createOverlayStore() {
+export function createOverlayStore({ delay: defaultDelay = 300 }: { delay?: number } = {}) {
let overlays: OverlayInstance[] = [];
let nextId = 0;
const listeners = new Set<() => void>();
+ const timeouts = new Map>();
function notify() {
for (const listener of listeners) {
@@ -38,7 +38,6 @@ export function createOverlayStore() {
{
id,
open: true,
- visible: true,
props,
definition,
close,
@@ -50,6 +49,13 @@ export function createOverlayStore() {
}
function remove(id: string) {
+ const timeout = timeouts.get(id);
+
+ if (timeout !== undefined) {
+ clearTimeout(timeout);
+ timeouts.delete(id);
+ }
+
if (!overlays.some((overlay) => overlay.id === id)) {
return;
}
@@ -58,7 +64,7 @@ export function createOverlayStore() {
notify();
}
- function close(id: string, { delay = 300 }: { delay?: number } = {}) {
+ function close(id: string, { delay = defaultDelay }: { delay?: number } = {}) {
const overlay = overlays.find((item) => item.id === id);
if (!overlay || !overlay.open) {
@@ -70,13 +76,23 @@ export function createOverlayStore() {
? {
...item,
open: false,
- visible: true,
}
: item,
);
notify();
- setTimeout(() => remove(id), delay);
+ const timeout = setTimeout(() => remove(id), delay);
+ timeouts.set(id, timeout);
+ }
+
+ function reset() {
+ for (const timeout of timeouts.values()) {
+ clearTimeout(timeout);
+ }
+
+ timeouts.clear();
+ overlays = [];
+ notify();
}
function all() {
@@ -87,6 +103,7 @@ export function createOverlayStore() {
add,
all,
close,
+ reset,
subscribe,
};
}
diff --git a/packages/react/src/overlay-provider.tsx b/packages/react/src/overlay-provider.tsx
index 71619cf..dac0b25 100644
--- a/packages/react/src/overlay-provider.tsx
+++ b/packages/react/src/overlay-provider.tsx
@@ -1,28 +1,95 @@
"use client";
-import { createElement, useSyncExternalStore, type ComponentType, type ReactNode } from "react";
+import {
+ createElement,
+ memo,
+ useId,
+ useLayoutEffect,
+ useSyncExternalStore,
+ type ComponentType,
+ type ReactNode,
+} from "react";
+import { createPortal } from "react-dom";
import type { OverlayInstance, OverlayStore } from "./overlay-manager.js";
import { createOverlayStack } from "./overlay-stack.js";
-import type { CueBackdrop, CueComponents, OverlayContext } from "./types.js";
+import type { CueBackdrop, CueComponents, OverlayContext, OverlayProviderProps } from "./types.js";
-export type OverlayProviderProps = {
- children?: ReactNode;
-};
+export type { OverlayProviderProps };
export function createOverlayProvider(params: {
store: OverlayStore;
components: C;
backdrop?: CueBackdrop;
+ teardown: () => void;
}): ComponentType {
- function OverlayProvider({ children }: OverlayProviderProps) {
+ let owner: string | null = null;
+ const outletListeners = new Set<() => void>();
+
+ function subscribeOutlet(listener: () => void) {
+ outletListeners.add(listener);
+
+ return () => {
+ outletListeners.delete(listener);
+ };
+ }
+
+ function getOwner() {
+ return owner;
+ }
+
+ function notifyOutlet() {
+ for (const listener of outletListeners) {
+ listener();
+ }
+ }
+
+ function claim(id: string) {
+ if (owner === null) {
+ owner = id;
+ notifyOutlet();
+ }
+
+ return owner;
+ }
+
+ function release(id: string) {
+ if (owner !== id) {
+ return;
+ }
+
+ owner = null;
+ params.teardown();
+ notifyOutlet();
+ }
+
+ function OverlayProvider({ children, container }: OverlayProviderProps) {
+ const id = useId();
+ const outletOwner = useSyncExternalStore(subscribeOutlet, getOwner, getOwner);
+
+ useLayoutEffect(() => {
+ if (claim(id) !== id && isDev()) {
+ console.warn(
+ "Cue: OverlayProvider is already mounted for this Cue environment. This copy will not render overlays.",
+ );
+ }
+
+ return () => {
+ release(id);
+ };
+ }, [id]);
+
return (
<>
{children}
-
+ {outletOwner === id ? (
+
+
+
+ ) : null}
>
);
}
@@ -30,13 +97,29 @@ export function createOverlayProvider(params: {
return OverlayProvider;
}
-function OverlayOutlet({
+function CuePortal({
+ container,
+ children,
+}: {
+ container?: Element | DocumentFragment;
+ children: ReactNode;
+}) {
+ const target = container ?? (typeof document === "undefined" ? null : document.body);
+
+ if (target == null) {
+ return null;
+ }
+
+ return createPortal(children, target);
+}
+
+const OverlayOutlet = memo(function OverlayOutlet({
store,
components,
backdrop,
}: {
store: OverlayStore;
- components: C;
+ components: CueComponents;
backdrop?: CueBackdrop;
}) {
const overlays = useSyncExternalStore(store.subscribe, store.all, store.all);
@@ -45,16 +128,20 @@ function OverlayOutlet({
return (
<>
{stack.backdrop
- ? createElement(stack.backdrop, { key: "cue-backdrop", close: stack.close })
+ ? createElement(stack.backdrop, {
+ key: "cue-backdrop",
+ open: stack.backdropOpen,
+ close: stack.close,
+ })
: null}
- {stack.visible.map((overlay) => (
-
+ {stack.mounted.map((overlay) => (
+
))}
>
);
-}
+});
-function OverlayInstance({
+function OverlayView({
overlay,
components,
}: {
@@ -74,3 +161,8 @@ function OverlayInstance({
return overlay.definition.render(overlay.props, context);
}
+
+function isDev() {
+ const nodeProcess = (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process;
+ return nodeProcess?.env?.NODE_ENV !== "production";
+}
diff --git a/packages/react/src/overlay-stack.test.ts b/packages/react/src/overlay-stack.test.ts
deleted file mode 100644
index 44d1739..0000000
--- a/packages/react/src/overlay-stack.test.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { describe, expect, it, vi } from "vitest";
-import { createOverlayStack } from "./overlay-stack.js";
-import type { OverlayInstance } from "./overlay-manager.js";
-
-function Backdrop() {
- return null;
-}
-
-function overlay(id: string, options: { open?: boolean; visible?: boolean } = {}): OverlayInstance {
- return {
- id,
- open: options.open ?? true,
- visible: options.visible ?? true,
- props: {},
- definition: { render: () => null },
- close: vi.fn(),
- };
-}
-
-describe("overlay stack", () => {
- it("does not activate a backdrop for a stack without open instances", () => {
- const closing = overlay("closing", { open: false });
- const stack = createOverlayStack([closing], Backdrop);
-
- expect(stack.visible).toEqual([closing]);
- expect(stack.backdrop).toBeUndefined();
-
- stack.close({ strategy: "last" });
- expect(closing.close).not.toHaveBeenCalled();
- });
-
- it("closes the top instance or every open instance by strategy", () => {
- const first = overlay("first");
- const second = overlay("second");
- const stack = createOverlayStack([first, second], Backdrop);
-
- expect(stack.backdrop).toBe(Backdrop);
-
- stack.close({ strategy: "last" });
- expect(first.close).not.toHaveBeenCalled();
- expect(second.close).toHaveBeenCalledOnce();
-
- stack.close({ strategy: "all" });
- expect(first.close).toHaveBeenCalledOnce();
- expect(second.close).toHaveBeenCalledTimes(2);
- });
-});
diff --git a/packages/react/src/overlay-stack.ts b/packages/react/src/overlay-stack.ts
index e385010..bc045b5 100644
--- a/packages/react/src/overlay-stack.ts
+++ b/packages/react/src/overlay-stack.ts
@@ -2,18 +2,20 @@ import type { OverlayInstance } from "./overlay-manager.js";
import type { CueBackdrop, CueBackdropCloseOptions } from "./types.js";
export type OverlayStack = {
- visible: OverlayInstance[];
+ mounted: OverlayInstance[];
backdrop?: CueBackdrop;
+ backdropOpen: boolean;
close: (options: CueBackdropCloseOptions) => void;
};
export function createOverlayStack(
overlays: OverlayInstance[],
- backdrop?: CueBackdrop,
+ environmentBackdrop?: CueBackdrop,
): OverlayStack {
- const visible = overlays.filter((overlay) => overlay.visible);
- const open = visible.filter((overlay) => overlay.open);
- const activeBackdrop = open.length > 0 ? getBackdrop(open, backdrop) : undefined;
+ const mounted = overlays;
+ const open = mounted.filter((overlay) => overlay.open);
+ const source = open.length > 0 ? open : mounted;
+ const backdrop = resolveBackdrop(source, environmentBackdrop);
const top = open[open.length - 1];
function close(options: CueBackdropCloseOptions) {
@@ -28,20 +30,29 @@ export function createOverlayStack(
}
return {
- visible,
- backdrop: activeBackdrop,
+ mounted,
+ backdrop,
+ backdropOpen: open.length > 0 && backdrop !== undefined,
close,
};
}
-function getBackdrop(visible: OverlayInstance[], backdrop?: CueBackdrop) {
- for (let index = visible.length - 1; index >= 0; index -= 1) {
- const override = visible[index]?.definition.backdrop;
+function resolveBackdrop(instances: OverlayInstance[], environmentBackdrop?: CueBackdrop) {
+ let inherit = false;
+
+ for (let index = instances.length - 1; index >= 0; index -= 1) {
+ const override = instances[index]?.definition.backdrop;
+
+ if (override === false) {
+ continue;
+ }
if (override !== undefined) {
- return override || undefined;
+ return override;
}
+
+ inherit = true;
}
- return backdrop;
+ return inherit ? environmentBackdrop : undefined;
}
diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts
index 3b5d762..e286dba 100644
--- a/packages/react/src/types.ts
+++ b/packages/react/src/types.ts
@@ -7,20 +7,23 @@ export type CueBackdropCloseOptions = {
};
export type CueBackdrop = ComponentType<{
+ /** Whether the shared backdrop is open. It stays mounted with `false` during the last overlay instance's close delay. */
+ open: boolean;
+ /** Close overlay instances when the shared backdrop is dismissed. */
close: (options: CueBackdropCloseOptions) => void;
}>;
export type CloseOptions = {
/** Value passed to a pending `openAsync` Promise. Omit to resolve `undefined`. */
result?: R;
- /** Milliseconds before the instance is removed. Default `300`. */
+ /** Milliseconds before the instance is removed. Falls back to the Cue environment `delay`. */
delay?: number;
};
export type OverlayContext = {
/** Whether this instance is open. It remains mounted with `false` during its close delay. */
open: boolean;
- /** Close the instance when the wrapped overlay primitive reports dismissal. */
+ /** Close the instance when the wrapped overlay reports dismissal. */
onOpenChange: (isOpen: boolean) => void;
/** Close this instance and optionally settle its `openAsync` result. */
close: (options?: CloseOptions) => void;
@@ -33,8 +36,36 @@ export type OverlayRenderer,
) => ReactNode;
+export type OverlayDefinitionOptions = {
+ /** Replace the environment shared backdrop, or `false` to opt out of providing one. */
+ backdrop?: CueBackdrop | false;
+};
+
export type OverlayDefinition = {
render: (props: object, context: OverlayContext) => ReactNode;
- /** Reserved for the future per-overlay backdrop option. */
backdrop?: CueBackdrop | false;
};
+
+export type CueOptions = {
+ backdrop?: CueBackdrop;
+ /** Close delay in milliseconds. Default `300`. */
+ delay?: number;
+ components?: C;
+};
+
+type OpenArguments = {} extends P ? [props?: P] : [props: P];
+
+export type OverlayHandle
= {
+ /** Open one instance and return a close function bound to that instance. */
+ open: (...args: OpenArguments
) => (options?: CloseOptions) => void;
+ /** Open one instance and resolve when that instance closes. */
+ openAsync: (...args: OpenArguments) => Promise;
+ /** Close every live instance created from this overlay definition. */
+ closeAll: (options?: CloseOptions) => void;
+};
+
+export type OverlayProviderProps = {
+ children?: ReactNode;
+ /** Element the overlay outlet portals into. Default `document.body`. */
+ container?: Element | DocumentFragment;
+};
diff --git a/packages/react/vitest.config.ts b/packages/react/vitest.config.ts
index bd4bba2..04a6575 100644
--- a/packages/react/vitest.config.ts
+++ b/packages/react/vitest.config.ts
@@ -2,6 +2,7 @@ import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
+ environment: "jsdom",
include: ["src/**/*.test.ts"],
typecheck: {
include: ["src/**/*.test-d.ts"],
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index be892e4..9542418 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -101,7 +101,7 @@ importers:
version: 6.1.1(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0))
nitro:
specifier: 3.0.260610-beta
- version: 3.0.260610-beta(chokidar@5.0.0)(dotenv@17.4.2)(jiti@2.7.0)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0))
+ version: 3.0.260610-beta(chokidar@5.0.0)(dotenv@17.4.2)(jiti@2.7.0)(lru-cache@11.5.2)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0))
oxfmt:
specifier: ^0.66.0
version: 0.66.0
@@ -133,12 +133,21 @@ importers:
'@repo/typescript-config':
specifier: workspace:*
version: link:../typescript-config
+ '@testing-library/dom':
+ specifier: ^10.4.1
+ version: 10.4.1
+ '@testing-library/react':
+ specifier: ^16.3.3
+ version: 16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@types/react':
specifier: ^19.2.5
version: 19.2.18
'@types/react-dom':
specifier: ^19.2.3
version: 19.2.7(@types/react@19.2.18)
+ jsdom:
+ specifier: ^30.0.1
+ version: 30.0.1
oxfmt:
specifier: ^0.66.0
version: 0.66.0
@@ -150,12 +159,20 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.0.10
- version: 4.1.11(@types/node@26.4.1)(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0))
+ version: 4.1.11(@types/node@26.4.1)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0))
packages/typescript-config: {}
packages:
+ '@asamuzakjp/css-color@6.0.7':
+ resolution: {integrity: sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==}
+ engines: {node: ^22.13.0 || >=24.0.0}
+
+ '@asamuzakjp/dom-selector@8.3.2':
+ resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==}
+ engines: {node: ^22.13.0 || >=24.0.0}
+
'@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
engines: {node: '>=6.9.0'}
@@ -320,6 +337,10 @@ packages:
'@types/react':
optional: true
+ '@bramus/specificity@2.4.2':
+ resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
+ hasBin: true
+
'@changesets/apply-release-plan@8.1.0':
resolution: {integrity: sha512-M93HOGyX3ssg6He3b5NotaHtQVu6uajHS6UIQ28xKt2RzskCy73ayX4I914qBKjTJmrE4YFlELQjfcqxbmGLJw==}
engines: {node: ^22.11 || ^24 || >=26}
@@ -397,6 +418,42 @@ packages:
resolution: {integrity: sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==}
engines: {node: '>= 20.12.0'}
+ '@csstools/color-helpers@6.1.1':
+ resolution: {integrity: sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==}
+ engines: {node: '>=20.19.0'}
+
+ '@csstools/css-calc@3.3.0':
+ resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==}
+ engines: {node: '>=20.19.0'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^4.0.0
+ '@csstools/css-tokenizer': ^4.0.0
+
+ '@csstools/css-color-parser@4.2.2':
+ resolution: {integrity: sha512-3QKjR/vxyjcSXBLgb6lP0S3MGdvwbmqSsvLPbYdVORqPDc8FX1HAJ0Spk38bxaRXgvENTA47tlhhbb5Z2e8hEg==}
+ engines: {node: '>=20.19.0'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^4.0.0
+ '@csstools/css-tokenizer': ^4.0.0
+
+ '@csstools/css-parser-algorithms@4.0.0':
+ resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==}
+ engines: {node: '>=20.19.0'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^4.0.0
+
+ '@csstools/css-syntax-patches-for-csstree@1.1.13':
+ resolution: {integrity: sha512-i9ZylF5QNhmNfPA9l0vHAWK4kPrbIp6g9lKgaiIFsIBz2F/WNB7OLrzlNNcCOm+h42bkaSD2v1PG+IBPHhc3ZA==}
+ peerDependencies:
+ css-tree: ^3.2.1
+ peerDependenciesMeta:
+ css-tree:
+ optional: true
+
+ '@csstools/css-tokenizer@4.0.0':
+ resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
+ engines: {node: '>=20.19.0'}
+
'@dotenvx/dotenvx@1.75.1':
resolution: {integrity: sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==}
hasBin: true
@@ -560,6 +617,15 @@ packages:
cpu: [x64]
os: [win32]
+ '@exodus/bytes@1.15.1':
+ resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+ peerDependencies:
+ '@noble/hashes': ^1.8.0 || ^2.0.0
+ peerDependenciesMeta:
+ '@noble/hashes':
+ optional: true
+
'@floating-ui/core@1.8.0':
resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==}
@@ -1484,6 +1550,25 @@ packages:
resolution: {integrity: sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==}
engines: {node: '>=20.19'}
+ '@testing-library/dom@10.4.1':
+ resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
+ engines: {node: '>=18'}
+
+ '@testing-library/react@16.3.3':
+ resolution: {integrity: sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@testing-library/dom': ^10.0.0
+ '@types/react': ^18.0.0 || ^19.0.0
+ '@types/react-dom': ^18.0.0 || ^19.0.0
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@ts-morph/common@0.27.0':
resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==}
@@ -1517,6 +1602,9 @@ packages:
cpu: [arm64]
os: [win32]
+ '@types/aria-query@5.0.4':
+ resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
+
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
@@ -1846,6 +1934,10 @@ packages:
resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==}
engines: {node: '>=12'}
+ ansi-styles@5.2.0:
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+
ansis@4.3.1:
resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==}
engines: {node: '>=14'}
@@ -1853,6 +1945,9 @@ packages:
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ aria-query@5.3.0:
+ resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
+
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
@@ -1884,6 +1979,9 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
+ bidi-js@1.1.0:
+ resolution: {integrity: sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==}
+
body-parser@2.3.0:
resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==}
engines: {node: '>=18'}
@@ -2054,6 +2152,10 @@ packages:
srvx:
optional: true
+ css-tree@3.2.1:
+ resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
+
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@@ -2062,6 +2164,10 @@ packages:
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+ data-urls@7.0.0:
+ resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
dataloader@2.2.3:
resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==}
@@ -2101,6 +2207,9 @@ packages:
supports-color:
optional: true
+ decimal.js@10.6.0:
+ resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
decode-named-character-reference@1.3.0:
resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==}
@@ -2154,6 +2263,9 @@ packages:
resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==}
engines: {node: '>=0.3.1'}
+ dom-accessibility-api@0.5.16:
+ resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
+
dot-prop@6.0.1:
resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==}
engines: {node: '>=10'}
@@ -2191,6 +2303,10 @@ packages:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
+ entities@8.1.0:
+ resolution: {integrity: sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==}
+ engines: {node: '>=20.19.0'}
+
env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
@@ -2619,6 +2735,10 @@ packages:
hookable@6.1.1:
resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==}
+ html-encoding-sniffer@6.0.0:
+ resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
@@ -2732,6 +2852,9 @@ packages:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
+ is-potential-custom-element-name@1.0.1:
+ resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
is-promise@4.0.0:
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
@@ -2791,6 +2914,15 @@ packages:
resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==}
hasBin: true
+ jsdom@30.0.1:
+ resolution: {integrity: sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==}
+ engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
+ peerDependencies:
+ canvas: ^3.2.3
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
@@ -2992,6 +3124,10 @@ packages:
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
+ lru-cache@11.5.2:
+ resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==}
+ engines: {node: 20 || >=22}
+
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -3000,6 +3136,10 @@ packages:
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ lz-string@1.5.0:
+ resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+ hasBin: true
+
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
@@ -3065,6 +3205,9 @@ packages:
mdast-util-to-string@4.0.0:
resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
+ mdn-data@2.27.1:
+ resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
+
media-typer@1.1.1:
resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==}
engines: {node: '>= 0.8'}
@@ -3418,6 +3561,9 @@ packages:
parse5@7.3.0:
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+ parse5@8.0.1:
+ resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==}
+
parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
@@ -3483,6 +3629,10 @@ packages:
engines: {node: '>=14'}
hasBin: true
+ pretty-format@27.5.1:
+ resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
pretty-ms@9.3.1:
resolution: {integrity: sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==}
engines: {node: '>=18'}
@@ -3498,6 +3648,10 @@ packages:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
qs@6.16.0:
resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==}
engines: {node: '>=0.6'}
@@ -3523,6 +3677,9 @@ packages:
peerDependencies:
react: ^19.2.8
+ react-is@17.0.2:
+ resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+
react-remove-scroll-bar@2.3.8:
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
engines: {node: '>=10'}
@@ -3658,6 +3815,10 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ saxes@6.0.0:
+ resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+ engines: {node: '>=v12.22.7'}
+
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
@@ -3823,6 +3984,9 @@ packages:
style-to-object@1.0.14:
resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==}
+ symbol-tree@3.2.4:
+ resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
systeminformation@5.33.8:
resolution: {integrity: sha512-v4F6OGYGh7wDvV68YmjOmZwGixV9A/GQ7d2b84t0UF4CaOy9jipNWIJDkHqYDYiTPuiojqlwVQd0hfUKOUN7tQ==}
engines: {node: '>=10.0.0'}
@@ -3858,6 +4022,13 @@ packages:
resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==}
engines: {node: '>=14.0.0'}
+ tldts-core@7.4.12:
+ resolution: {integrity: sha512-nYNzS2WRf4QJmjzFFgAxLOBjyBxAGRbCy9PVBPaglcYyYajh40VBn+v5Ngr96ZMc7oM0+aCJdtQnNejvdBnXMQ==}
+
+ tldts@7.4.12:
+ resolution: {integrity: sha512-WylhSDKVeYnWXL3a+vKTaOxjnOeEGw938hImY8zoRWJjRRK/Jp1K+IihBzIONpUmW4e3WmXT6q5FW6vlESVZCA==}
+ hasBin: true
+
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@@ -3866,6 +4037,14 @@ packages:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
+ tough-cookie@6.0.2:
+ resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==}
+ engines: {node: '>=16'}
+
+ tr46@6.0.0:
+ resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==}
+ engines: {node: '>=20'}
+
trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
@@ -3913,6 +4092,10 @@ packages:
resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==}
engines: {node: '>=20.18.1'}
+ undici@8.10.2:
+ resolution: {integrity: sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==}
+ engines: {node: '>=22.19.0'}
+
unenv@2.0.0-rc.24:
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
@@ -4202,12 +4385,32 @@ packages:
jsdom:
optional: true
+ w3c-xmlserializer@5.0.0:
+ resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+ engines: {node: '>=18'}
+
web-namespaces@2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
+ webidl-conversions@8.0.1:
+ resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
+ engines: {node: '>=20'}
+
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
+ whatwg-mimetype@5.0.0:
+ resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==}
+ engines: {node: '>=20'}
+
+ whatwg-url@16.0.1:
+ resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
+ whatwg-url@17.1.1:
+ resolution: {integrity: sha512-ohjk1mdUebJVadRt3bAhQhx8lSnISq+GDttK79LFl8EHQkAPvzwctoasC4hs8tBt6kLAncBWWyq1N52qEfKvDw==}
+ engines: {node: ^22.14.0 || >=24.0.0}
+
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@@ -4230,10 +4433,17 @@ packages:
resolution: {integrity: sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==}
engines: {node: '>=20'}
+ xml-name-validator@5.0.0:
+ resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+ engines: {node: '>=18'}
+
xmlbuilder2@4.0.3:
resolution: {integrity: sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA==}
engines: {node: '>=20.0'}
+ xmlchars@2.2.0:
+ resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -4276,6 +4486,21 @@ packages:
snapshots:
+ '@asamuzakjp/css-color@6.0.7':
+ dependencies:
+ '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.2.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-tokenizer': 4.0.0
+ lru-cache: 11.5.2
+
+ '@asamuzakjp/dom-selector@8.3.2':
+ dependencies:
+ bidi-js: 1.1.0
+ css-tree: 3.2.1
+ is-potential-custom-element-name: 1.0.1
+ lru-cache: 11.5.2
+
'@babel/code-frame@7.27.1':
dependencies:
'@babel/helper-validator-identifier': 7.29.7
@@ -4493,6 +4718,10 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.18
+ '@bramus/specificity@2.4.2':
+ dependencies:
+ css-tree: 3.2.1
+
'@changesets/apply-release-plan@8.1.0':
dependencies:
'@changesets/config': 4.0.0
@@ -4618,6 +4847,30 @@ snapshots:
fast-wrap-ansi: 0.2.2
sisteransi: 1.0.5
+ '@csstools/color-helpers@6.1.1': {}
+
+ '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-tokenizer': 4.0.0
+
+ '@csstools/css-color-parser@4.2.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
+ dependencies:
+ '@csstools/color-helpers': 6.1.1
+ '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-tokenizer': 4.0.0
+
+ '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)':
+ dependencies:
+ '@csstools/css-tokenizer': 4.0.0
+
+ '@csstools/css-syntax-patches-for-csstree@1.1.13(css-tree@3.2.1)':
+ optionalDependencies:
+ css-tree: 3.2.1
+
+ '@csstools/css-tokenizer@4.0.0': {}
+
'@dotenvx/dotenvx@1.75.1':
dependencies:
'@dotenvx/primitives': 0.8.0
@@ -4717,6 +4970,8 @@ snapshots:
'@esbuild/win32-x64@0.28.2':
optional: true
+ '@exodus/bytes@1.15.1': {}
+
'@floating-ui/core@1.8.0':
dependencies:
'@floating-ui/utils': 0.2.12
@@ -5489,6 +5744,27 @@ snapshots:
'@tanstack/virtual-file-routes@1.162.0': {}
+ '@testing-library/dom@10.4.1':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/runtime': 7.29.7
+ '@types/aria-query': 5.0.4
+ aria-query: 5.3.0
+ dom-accessibility-api: 0.5.16
+ lz-string: 1.5.0
+ picocolors: 1.1.1
+ pretty-format: 27.5.1
+
+ '@testing-library/react@16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@testing-library/dom': 10.4.1
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.7(@types/react@19.2.18)
+
'@ts-morph/common@0.27.0':
dependencies:
fast-glob: 3.3.3
@@ -5513,6 +5789,8 @@ snapshots:
'@turbo/windows-arm64@2.10.12':
optional: true
+ '@types/aria-query@5.0.4': {}
+
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
@@ -5738,10 +6016,16 @@ snapshots:
ansi-regex@6.3.0: {}
+ ansi-styles@5.2.0: {}
+
ansis@4.3.1: {}
argparse@2.0.1: {}
+ aria-query@5.3.0:
+ dependencies:
+ dequal: 2.0.3
+
assertion-error@2.0.1: {}
ast-types@0.16.3:
@@ -5767,6 +6051,10 @@ snapshots:
baseline-browser-mapping@2.11.21: {}
+ bidi-js@1.1.0:
+ dependencies:
+ require-from-string: 2.0.2
+
body-parser@2.3.0:
dependencies:
bytes: 3.1.2
@@ -5916,10 +6204,22 @@ snapshots:
optionalDependencies:
srvx: 0.11.22
+ css-tree@3.2.1:
+ dependencies:
+ mdn-data: 2.27.1
+ source-map-js: 1.2.1
+
cssesc@3.0.0: {}
csstype@3.2.3: {}
+ data-urls@7.0.0:
+ dependencies:
+ whatwg-mimetype: 5.0.0
+ whatwg-url: 16.0.1
+ transitivePeerDependencies:
+ - '@noble/hashes'
+
dataloader@2.2.3: {}
db0@0.3.4: {}
@@ -5932,6 +6232,8 @@ snapshots:
dependencies:
ms: 2.1.3
+ decimal.js@10.6.0: {}
+
decode-named-character-reference@1.3.0:
dependencies:
character-entities: 2.0.2
@@ -5965,6 +6267,8 @@ snapshots:
diff@8.0.4: {}
+ dom-accessibility-api@0.5.16: {}
+
dot-prop@6.0.1:
dependencies:
is-obj: 2.0.0
@@ -5997,6 +6301,8 @@ snapshots:
entities@6.0.1: {}
+ entities@8.1.0: {}
+
env-paths@2.2.1: {}
env-runner@0.1.16:
@@ -6519,6 +6825,12 @@ snapshots:
hookable@6.1.1: {}
+ html-encoding-sniffer@6.0.0:
+ dependencies:
+ '@exodus/bytes': 1.15.1
+ transitivePeerDependencies:
+ - '@noble/hashes'
+
html-void-elements@3.0.0: {}
http-errors@2.0.1:
@@ -6597,6 +6909,8 @@ snapshots:
is-plain-obj@4.1.0: {}
+ is-potential-custom-element-name@1.0.1: {}
+
is-promise@4.0.0: {}
is-regexp@3.1.0: {}
@@ -6635,6 +6949,32 @@ snapshots:
dependencies:
argparse: 2.0.1
+ jsdom@30.0.1:
+ dependencies:
+ '@asamuzakjp/css-color': 6.0.7
+ '@asamuzakjp/dom-selector': 8.3.2
+ '@bramus/specificity': 2.4.2
+ '@csstools/css-syntax-patches-for-csstree': 1.1.13(css-tree@3.2.1)
+ '@exodus/bytes': 1.15.1
+ css-tree: 3.2.1
+ data-urls: 7.0.0
+ decimal.js: 10.6.0
+ html-encoding-sniffer: 6.0.0
+ is-potential-custom-element-name: 1.0.1
+ lru-cache: 11.5.2
+ parse5: 8.0.1
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 6.0.2
+ undici: 8.10.2
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 8.0.1
+ whatwg-mimetype: 5.0.0
+ whatwg-url: 17.1.1
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - '@noble/hashes'
+
jsesc@3.1.0: {}
json-parse-even-better-errors@2.3.1: {}
@@ -6776,6 +7116,8 @@ snapshots:
longest-streak@3.1.0: {}
+ lru-cache@11.5.2: {}
+
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
@@ -6784,6 +7126,8 @@ snapshots:
dependencies:
react: 19.2.8
+ lz-string@1.5.0: {}
+
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.6.0
@@ -6961,6 +7305,8 @@ snapshots:
dependencies:
'@types/mdast': 4.0.4
+ mdn-data@2.27.1: {}
+
media-typer@1.1.1: {}
merge-descriptors@2.0.0: {}
@@ -7285,7 +7631,7 @@ snapshots:
nf3@0.3.24: {}
- nitro@3.0.260610-beta(chokidar@5.0.0)(dotenv@17.4.2)(jiti@2.7.0)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)):
+ nitro@3.0.260610-beta(chokidar@5.0.0)(dotenv@17.4.2)(jiti@2.7.0)(lru-cache@11.5.2)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)):
dependencies:
consola: 3.4.2
crossws: 0.4.12(srvx@0.11.22)
@@ -7300,7 +7646,7 @@ snapshots:
rolldown: 1.2.7
srvx: 0.11.22
unenv: 2.0.0-rc.24
- unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4)(ofetch@2.0.0-alpha.3)
+ unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4)(lru-cache@11.5.2)(ofetch@2.0.0-alpha.3)
optionalDependencies:
dotenv: 17.4.2
jiti: 2.7.0
@@ -7503,6 +7849,10 @@ snapshots:
dependencies:
entities: 6.0.1
+ parse5@8.0.1:
+ dependencies:
+ entities: 8.1.0
+
parseurl@1.3.3: {}
path-browserify@1.0.1: {}
@@ -7546,6 +7896,12 @@ snapshots:
prettier@3.9.6: {}
+ pretty-format@27.5.1:
+ dependencies:
+ ansi-regex: 5.0.1
+ ansi-styles: 5.2.0
+ react-is: 17.0.2
+
pretty-ms@9.3.1:
dependencies:
parse-ms: 4.0.0
@@ -7562,6 +7918,8 @@ snapshots:
forwarded: 0.2.0
ipaddr.js: 1.9.1
+ punycode@2.3.1: {}
+
qs@6.16.0:
dependencies:
es-define-property: 1.0.1
@@ -7589,6 +7947,8 @@ snapshots:
react: 19.2.8
scheduler: 0.27.0
+ react-is@17.0.2: {}
+
react-remove-scroll-bar@2.3.8(@types/react@19.2.18)(react@19.2.8):
dependencies:
react: 19.2.8
@@ -7818,6 +8178,10 @@ snapshots:
safer-buffer@2.1.2: {}
+ saxes@6.0.0:
+ dependencies:
+ xmlchars: 2.2.0
+
scheduler@0.27.0: {}
scroll-into-view-if-needed@3.1.0:
@@ -8021,6 +8385,8 @@ snapshots:
dependencies:
inline-style-parser: 0.2.7
+ symbol-tree@3.2.4: {}
+
systeminformation@5.33.8: {}
tailwindcss@4.3.3: {}
@@ -8042,12 +8408,26 @@ snapshots:
tinyrainbow@3.1.1: {}
+ tldts-core@7.4.12: {}
+
+ tldts@7.4.12:
+ dependencies:
+ tldts-core: 7.4.12
+
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
toidentifier@1.0.1: {}
+ tough-cookie@6.0.2:
+ dependencies:
+ tldts: 7.4.12
+
+ tr46@6.0.0:
+ dependencies:
+ punycode: 2.3.1
+
trim-lines@3.0.1: {}
trough@2.2.0: {}
@@ -8117,6 +8497,8 @@ snapshots:
undici@7.29.1: {}
+ undici@8.10.2: {}
+
unenv@2.0.0-rc.24:
dependencies:
pathe: 2.0.3
@@ -8180,10 +8562,11 @@ snapshots:
rollup: 4.63.1
vite: 8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)
- unstorage@2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4)(ofetch@2.0.0-alpha.3):
+ unstorage@2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4)(lru-cache@11.5.2)(ofetch@2.0.0-alpha.3):
optionalDependencies:
chokidar: 5.0.0
db0: 0.3.4
+ lru-cache: 11.5.2
ofetch: 2.0.0-alpha.3
update-browserslist-db@1.3.2(browserslist@4.28.9):
@@ -8251,7 +8634,7 @@ snapshots:
optionalDependencies:
vite: 8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)
- vitest@4.1.11(@types/node@26.4.1)(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)):
+ vitest@4.1.11(@types/node@26.4.1)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)):
dependencies:
'@vitest/expect': 4.1.11
'@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0))
@@ -8275,13 +8658,38 @@ snapshots:
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 26.4.1
+ jsdom: 30.0.1
transitivePeerDependencies:
- msw
+ w3c-xmlserializer@5.0.0:
+ dependencies:
+ xml-name-validator: 5.0.0
+
web-namespaces@2.0.1: {}
+ webidl-conversions@8.0.1: {}
+
webpack-virtual-modules@0.6.2: {}
+ whatwg-mimetype@5.0.0: {}
+
+ whatwg-url@16.0.1:
+ dependencies:
+ '@exodus/bytes': 1.15.1
+ tr46: 6.0.0
+ webidl-conversions: 8.0.1
+ transitivePeerDependencies:
+ - '@noble/hashes'
+
+ whatwg-url@17.1.1:
+ dependencies:
+ '@exodus/bytes': 1.15.1
+ tr46: 6.0.0
+ webidl-conversions: 8.0.1
+ transitivePeerDependencies:
+ - '@noble/hashes'
+
which@2.0.2:
dependencies:
isexe: 2.0.0
@@ -8302,6 +8710,8 @@ snapshots:
is-wsl: 3.1.1
powershell-utils: 0.1.0
+ xml-name-validator@5.0.0: {}
+
xmlbuilder2@4.0.3:
dependencies:
'@oozcitak/dom': 2.0.2
@@ -8309,6 +8719,8 @@ snapshots:
'@oozcitak/util': 10.0.0
js-yaml: 4.3.2
+ xmlchars@2.2.0: {}
+
yallist@3.1.1: {}
yaml@2.9.0: {}
diff --git a/registry/cue.tsx b/registry/cue.tsx
index 8163924..5253ecd 100644
--- a/registry/cue.tsx
+++ b/registry/cue.tsx
@@ -1,7 +1,7 @@
"use client";
import type { ComponentProps } from "react";
-import { createCue } from "@vlkoss/cue";
+import { createCue, type CueBackdrop } from "@vlkoss/cue";
import {
Dialog,
DialogClose,
@@ -9,8 +9,6 @@ import {
DialogDescription,
DialogFooter,
DialogHeader,
- DialogOverlay,
- DialogPortal,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
@@ -21,15 +19,15 @@ function CueContent(props: CueContentProps) {
return ;
}
-function CueBackdrop({ close }: { close: (options: { strategy: "last" | "all" }) => void }) {
- return (
-
- );
-}
+const CueBackdrop: CueBackdrop = ({ open, close }) => (
+ close({ strategy: "last" })}
+ />
+);
export const cue = createCue({
backdrop: CueBackdrop,