import type { JSX } from "@solidjs/web"; /** * `` — global coordinator used by multi-drawer layouts * (e.g. apps where a side sheet + bottom sheet share the same page chrome). * * 1:1 port of Base UI React's `DrawerProvider` * (`packages/react/src/drawer/provider/DrawerProvider.tsx`). Solid notes: * * - `openById` lives in a mutable `Map` + a "version" signal — React's * `setOpenById(prev => new Map(...))` is a React-render-trigger trick * that doesn't translate. We keep the map in place, bump the version * on every write, and derive `active` from the version signal. * - `visualStateStore` is imperative / non-reactive (React's version uses * `useSyncExternalStore` semantics) — preserved verbatim. */ import { createMemo, createSignal } from "solid-js"; import { DrawerProviderContext, type DrawerProviderContextValue, type DrawerVisualState, type DrawerVisualStateStore, } from "./drawer-provider-context"; export interface DrawerProviderProps { children?: JSX.Element; } function createVisualStateStore(): DrawerVisualStateStore { let state: DrawerVisualState = { swipeProgress: 0, frontmostHeight: 0 }; const listeners = new Set<() => void>(); return { getSnapshot: () => state, set(nextState) { let nextSwipeProgress = state.swipeProgress; if (nextState.swipeProgress !== undefined) { nextSwipeProgress = Number.isFinite(nextState.swipeProgress) ? nextState.swipeProgress : 0; } let nextFrontmostHeight = state.frontmostHeight; if (nextState.frontmostHeight !== undefined) { nextFrontmostHeight = Number.isFinite(nextState.frontmostHeight) ? nextState.frontmostHeight : 0; } if ( nextSwipeProgress === state.swipeProgress && nextFrontmostHeight === state.frontmostHeight ) { return; } state = { swipeProgress: nextSwipeProgress, frontmostHeight: nextFrontmostHeight, }; for (const listener of listeners) listener(); }, subscribe(listener) { listeners.add(listener); return () => { listeners.delete(listener); }; }, }; } /** * Provides a shared context for coordinating global Drawer UI (indent / * background effects based on whether any drawer is open). Doesn't render * its own HTML element. */ export function DrawerProvider(props: DrawerProviderProps): JSX.Element { const openById = new Map(); const [version, setVersion] = createSignal(0, { ownedWrite: true }); const visualStateStore = createVisualStateStore(); // Functional setter avoids reading `version()` — `setDrawerOpen` can be // called from any scope (event handlers, ``'s effect-apply // callbacks downstream of open transitions); a direct read would trip // `[STRICT_READ_UNTRACKED]` from those callers. const setDrawerOpen = (drawerId: string, open: boolean): void => { const prev = openById.get(drawerId); if (prev === open) return; openById.set(drawerId, open); setVersion((v) => v + 1); }; const removeDrawer = (drawerId: string): void => { if (!openById.has(drawerId)) return; openById.delete(drawerId); setVersion((v) => v + 1); }; const active = createMemo(() => { // Read `version` so we re-compute on every change. version(); for (const open of openById.values()) if (open) return true; return false; }); const contextValue: DrawerProviderContextValue = { setDrawerOpen, removeDrawer, active, visualStateStore, }; return ( {props.children} ); }