mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-08 17:34:51 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c361835bfc | ||
|
|
61c9e803f1 |
@@ -0,0 +1,65 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
createFloatingPanelHosts,
|
||||
type FloatingPanelHostRect,
|
||||
type MeasurableFloatingPanelHost,
|
||||
} from "./floating-panel-hosts";
|
||||
|
||||
function createHost(rect: FloatingPanelHostRect): MeasurableFloatingPanelHost {
|
||||
return {
|
||||
measureInWindow: (callback) => callback(rect.x, rect.y, rect.width, rect.height),
|
||||
};
|
||||
}
|
||||
|
||||
const WORKSPACE_RECT: FloatingPanelHostRect = { x: 10, y: 20, width: 800, height: 600 };
|
||||
const RETAINED_VIEWPORT: FloatingPanelHostRect = { x: 0, y: 0, width: 1200, height: 900 };
|
||||
|
||||
describe("floating panel hosts", () => {
|
||||
it("measures the newest host registered under the name", async () => {
|
||||
const hosts = createFloatingPanelHosts();
|
||||
hosts.register("workspace", createHost(WORKSPACE_RECT));
|
||||
hosts.register("workspace", createHost(RETAINED_VIEWPORT));
|
||||
|
||||
expect(await hosts.measure("workspace")).toEqual(RETAINED_VIEWPORT);
|
||||
});
|
||||
|
||||
it("keeps measuring a survivor after the newest host unregisters", async () => {
|
||||
const hosts = createFloatingPanelHosts();
|
||||
const unregisterSurvivor = hosts.register("workspace", createHost(WORKSPACE_RECT));
|
||||
const unregisterNewest = hosts.register("workspace", createHost(RETAINED_VIEWPORT));
|
||||
|
||||
unregisterNewest();
|
||||
|
||||
expect(await hosts.measure("workspace")).toEqual(WORKSPACE_RECT);
|
||||
unregisterSurvivor();
|
||||
});
|
||||
|
||||
it("returns null once every host under the name is gone", async () => {
|
||||
const hosts = createFloatingPanelHosts();
|
||||
const unregisterFirst = hosts.register("workspace", createHost(WORKSPACE_RECT));
|
||||
const unregisterSecond = hosts.register("workspace", createHost(RETAINED_VIEWPORT));
|
||||
|
||||
unregisterFirst();
|
||||
unregisterSecond();
|
||||
|
||||
expect(await hosts.measure("workspace")).toBeNull();
|
||||
});
|
||||
|
||||
it("skips hosts that are not laid out", async () => {
|
||||
const hosts = createFloatingPanelHosts();
|
||||
hosts.register("workspace", createHost(WORKSPACE_RECT));
|
||||
hosts.register("workspace", createHost({ x: 0, y: 0, width: 0, height: 0 }));
|
||||
|
||||
expect(await hosts.measure("workspace")).toEqual(WORKSPACE_RECT);
|
||||
});
|
||||
|
||||
it("ignores a repeated unregister of the same host", async () => {
|
||||
const hosts = createFloatingPanelHosts();
|
||||
const unregister = hosts.register("workspace", createHost(WORKSPACE_RECT));
|
||||
|
||||
unregister();
|
||||
unregister();
|
||||
|
||||
expect(await hosts.measure("workspace")).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
export interface FloatingPanelHostRect {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface MeasurableFloatingPanelHost {
|
||||
measureInWindow(callback: (x: number, y: number, width: number, height: number) => void): void;
|
||||
}
|
||||
|
||||
export interface FloatingPanelHosts<T extends MeasurableFloatingPanelHost> {
|
||||
register(name: string, host: T): () => void;
|
||||
measure(name: string): Promise<FloatingPanelHostRect | null>;
|
||||
}
|
||||
|
||||
function measureHost(host: MeasurableFloatingPanelHost): Promise<FloatingPanelHostRect> {
|
||||
return new Promise((resolve) => {
|
||||
host.measureInWindow((x, y, width, height) => resolve({ x, y, width, height }));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hosts are addressed by name, and one name can have several live hosts: a workspace
|
||||
* screen mounts its own host while retained copies of that screen stay mounted. Track
|
||||
* every host so unmounting one leaves the others measurable.
|
||||
*/
|
||||
export function createFloatingPanelHosts<
|
||||
T extends MeasurableFloatingPanelHost,
|
||||
>(): FloatingPanelHosts<T> {
|
||||
const hostsByName = new Map<string, T[]>();
|
||||
|
||||
function register(name: string, host: T): () => void {
|
||||
const hosts = hostsByName.get(name) ?? [];
|
||||
hostsByName.set(name, hosts);
|
||||
hosts.push(host);
|
||||
|
||||
return () => {
|
||||
const index = hosts.indexOf(host);
|
||||
if (index === -1) return;
|
||||
hosts.splice(index, 1);
|
||||
if (hosts.length === 0 && hostsByName.get(name) === hosts) {
|
||||
hostsByName.delete(name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function measure(name: string): Promise<FloatingPanelHostRect | null> {
|
||||
const hosts = hostsByName.get(name);
|
||||
if (!hosts || hosts.length === 0) return null;
|
||||
|
||||
const rects = await Promise.all(hosts.toReversed().map(measureHost));
|
||||
return rects.find((rect) => rect.width > 0 && rect.height > 0) ?? rects[0];
|
||||
}
|
||||
|
||||
return { register, measure };
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import React, { act } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { Portal, PortalProvider } from "@gorhom/portal";
|
||||
import { FloatingPanelPortalHost, measureFloatingPanelPortalHost } from "./floating-panel-portal";
|
||||
|
||||
describe("Floating panel host lifetime", () => {
|
||||
test("removing the newest host preserves the surviving host measurement", async () => {
|
||||
const container = document.createElement("div");
|
||||
container.style.cssText = "position:relative;width:800px;height:600px";
|
||||
document.body.appendChild(container);
|
||||
const root = createRoot(container);
|
||||
function Content({ overlap }: { overlap: boolean }) {
|
||||
return (
|
||||
<PortalProvider shouldAddRootHost={false}>
|
||||
<FloatingPanelPortalHost name="workspace" />
|
||||
{overlap && <FloatingPanelPortalHost name="workspace" />}
|
||||
<Portal hostName="workspace" name="picker">
|
||||
<div data-testid="picker">Commands</div>
|
||||
</Portal>
|
||||
</PortalProvider>
|
||||
);
|
||||
}
|
||||
try {
|
||||
await act(async () => root.render(<Content overlap={true} />));
|
||||
expect(await measureFloatingPanelPortalHost("workspace")).toMatchObject({
|
||||
width: 800,
|
||||
height: 600,
|
||||
});
|
||||
await act(async () => root.render(<Content overlap={false} />));
|
||||
expect(await measureFloatingPanelPortalHost("workspace")).toMatchObject({
|
||||
width: 800,
|
||||
height: 600,
|
||||
});
|
||||
} finally {
|
||||
await act(async () => root.unmount());
|
||||
container.remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -8,32 +8,21 @@ import {
|
||||
} from "react";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
import { PortalHost } from "@gorhom/portal";
|
||||
import {
|
||||
createFloatingPanelHosts,
|
||||
type FloatingPanelHostRect,
|
||||
} from "@/components/ui/floating-panel-hosts";
|
||||
|
||||
export const DEFAULT_FLOATING_PANEL_PORTAL_HOST = "content-floating-panels";
|
||||
|
||||
const FloatingPanelPortalHostNameContext = createContext(DEFAULT_FLOATING_PANEL_PORTAL_HOST);
|
||||
|
||||
interface Rect {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
const portalHosts = createFloatingPanelHosts<View>();
|
||||
|
||||
const portalHostElements = new Map<string, View>();
|
||||
|
||||
function measureElement(element: View): Promise<Rect> {
|
||||
return new Promise((resolve) => {
|
||||
element.measureInWindow((x, y, width, height) => {
|
||||
resolve({ x, y, width, height });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function measureFloatingPanelPortalHost(name: string): Promise<Rect | null> {
|
||||
const element = portalHostElements.get(name);
|
||||
if (!element) return Promise.resolve(null);
|
||||
return measureElement(element);
|
||||
export function measureFloatingPanelPortalHost(
|
||||
name: string,
|
||||
): Promise<FloatingPanelHostRect | null> {
|
||||
return portalHosts.measure(name);
|
||||
}
|
||||
|
||||
export function FloatingPanelPortalHostNameProvider({
|
||||
@@ -64,12 +53,7 @@ export function FloatingPanelPortalHost({
|
||||
useEffect(() => {
|
||||
const host = hostRef.current;
|
||||
if (!host) return undefined;
|
||||
portalHostElements.set(name, host);
|
||||
return () => {
|
||||
if (portalHostElements.get(name) === host) {
|
||||
portalHostElements.delete(name);
|
||||
}
|
||||
};
|
||||
return portalHosts.register(name, host);
|
||||
}, [name]);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user