import { spread } from "@solidjs/web"; import { createRenderEffect, createRoot, createSignal, getOwner, runWithOwner, } from "solid-js"; /** * Build a directive-shaped ref callback that spreads a slot prop bag onto * an element exactly once on mount. Used by every compound primitive's * `c.triggerRef()` / `c.panelRef()` factories. * * RFC 07 apply-phase is unowned, so a slot-spreading directive can't create * signals at apply time. The captured `getProps` is invoked exactly once * inside an owned render-effect, then `spread()` writes the (still-reactive, * thanks to getter-bag semantics) attributes onto the node. Subsequent ref * callbacks on the same logical slot are no-ops — single binding per slot. * * Returned callback semantics: * - First non-null `el` value latches and runs `spread`. Subsequent values * (mount → unmount → remount) do NOT re-bind; the original spread keeps * its reactive subscriptions alive against the original node. * * If a slot needs to support remounting, use `c.(userProps)` from the * compound's own JSX instead — the slot factory builds a fresh getter-bag * per call and the wrapper's reactive scope handles the lifecycle. */ export function makeSlotRefDirective( getProps: () => object, ): (el: E | null) => void { const [el, setEl] = createSignal(null, { name: "slot-ref-directive.el", ownedWrite: true, }); let bound = false; // The effect callback runs with no ambient owner, so capture the factory's // owner here and parent the spread root to it explicitly. const owner = getOwner(); createRenderEffect( () => el(), (node) => { if (!node || bound) return; bound = true; // `spread()` sets up per-key reactive bindings; the first // synchronous read pass would otherwise inherit our enclosing // effect-apply scope and trip `[STRICT_READ_UNTRACKED]` on // every getter on the prop bag. `createRoot` gives spread its // own owned scope so its internal effects' compute reads // subscribe correctly under their own tracking context, with // no strictRead inheritance from the outer apply. Running it // under the captured `owner` disposes the root along with the // directive's owning scope. runWithOwner(owner, () => createRoot(() => spread(node, getProps()))); }, ); return (node: E | null) => setEl(() => node); }