diff --git a/package.json b/package.json index 0a3d26d..e473128 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@switchify/scanning", - "version": "0.2.0", + "version": "0.3.0", "description": "Switch scanning for React and React Native, ported from Switchify PC.", "license": "AGPL-3.0-only", "type": "module", diff --git a/src/react/index.tsx b/src/react/index.tsx index 5dbd9ec..3bd059c 100644 --- a/src/react/index.tsx +++ b/src/react/index.tsx @@ -90,10 +90,17 @@ export function ScanProvider({ return {children}; } +let inert: ScanController | undefined; + +/** + * Outside a ScanProvider the hooks use one controller that is never started, so a + * shared component can call them whether or not its screen is scanned. + */ export function useScanController(): ScanController { const controller = useContext(ControllerContext); - if (!controller) throw new Error("Scanning hooks must be used inside a ScanProvider."); - return controller; + if (controller) return controller; + inert ??= new ScanController(); + return inert; } export function useScanSnapshot(): ScanSnapshot { diff --git a/test/dom.test.tsx b/test/dom.test.tsx index f08963d..d7c58fa 100644 --- a/test/dom.test.tsx +++ b/test/dom.test.tsx @@ -129,3 +129,9 @@ test("the latest handler runs without re-registering", async () => { act(() => scanner.dispatch("select")); expect(screen.getByTestId("count").textContent).toBe("2"); }); + +test("hooks outside a provider render without scanning", async () => { + render(); + await flush(); + expect(highlighted("alone")).toBe(false); +});