{"version":3,"file":"slots.cjs","names":[],"sources":["../src/slots.ts"],"sourcesContent":["/**\n * Slot observation and reactive slot signals.\n *\n * `slots.has(name?)`: Signal<boolean> — whether a named slot has assigned elements.\n * `slots.elements(name?)`: Signal<Element[]> — assigned elements for a slot (flattened).\n */\n\nimport { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { onCleanup, onMounted, requireSetupContext } from './runtime';\n\nexport type ComponentSlots<SlotNames extends string = string> = {\n  elements: (name?: SlotNames) => Readable<Element[]>;\n  has: (name?: SlotNames) => Readable<boolean>;\n};\n\nconst SLOT_DEFAULT = 'default';\nconst normalizeSlotName = (slotName: string | null | undefined): string => slotName || SLOT_DEFAULT;\n\nconst createSlots = (host: HTMLElement): ComponentSlots<string> => {\n  type SlotEntry = {\n    elements: Signal<Element[]>;\n    presence: Signal<boolean>;\n  };\n\n  const slotSignals = new Map<string, SlotEntry>();\n  const slotNodesByName = new Map<string, Set<HTMLSlotElement>>();\n  const slotCleanupMap = new Map<HTMLSlotElement, () => void>();\n\n  const ensureSlotEntry = (normalizedName: string): SlotEntry => {\n    let entry = slotSignals.get(normalizedName);\n\n    if (!entry) {\n      entry = {\n        elements: signal<Element[]>([]),\n        presence: signal(false),\n      };\n      slotSignals.set(normalizedName, entry);\n    }\n\n    return entry;\n  };\n\n  const areElementsEqual = (prev: Element[], next: Element[]): boolean => {\n    if (prev.length !== next.length) return false;\n\n    for (let i = 0; i < prev.length; i++) {\n      if (prev[i] !== next[i]) return false;\n    }\n\n    return true;\n  };\n\n  const recomputeSlot = (name: string): void => {\n    const normalized = normalizeSlotName(name);\n    const slotsForName = slotNodesByName.get(normalized);\n    const assigned: Element[] = [];\n\n    if (slotsForName) {\n      for (const slotEl of slotsForName) {\n        assigned.push(...slotEl.assignedElements({ flatten: true }));\n      }\n    }\n\n    const entry = ensureSlotEntry(normalized);\n\n    if (!areElementsEqual(entry.elements.value, assigned)) entry.elements.value = assigned;\n\n    const hasElements = assigned.length > 0;\n\n    if (entry.presence.value !== hasElements) entry.presence.value = hasElements;\n  };\n\n  const bindSlot = (slotEl: HTMLSlotElement): void => {\n    if (slotCleanupMap.has(slotEl)) return;\n\n    const name = normalizeSlotName(slotEl.getAttribute('name'));\n    const setForName = slotNodesByName.get(name) ?? new Set<HTMLSlotElement>();\n\n    setForName.add(slotEl);\n    slotNodesByName.set(name, setForName);\n\n    const onChange = () => recomputeSlot(name);\n\n    slotEl.addEventListener('slotchange', onChange);\n\n    slotCleanupMap.set(slotEl, () => {\n      slotEl.removeEventListener('slotchange', onChange);\n    });\n\n    recomputeSlot(name);\n  };\n\n  const unbindSlot = (slotEl: HTMLSlotElement): void => {\n    const cleanup = slotCleanupMap.get(slotEl);\n\n    if (!cleanup) return;\n\n    cleanup();\n    slotCleanupMap.delete(slotEl);\n\n    const name = normalizeSlotName(slotEl.getAttribute('name'));\n    const setForName = slotNodesByName.get(name);\n\n    if (setForName) {\n      setForName.delete(slotEl);\n\n      if (setForName.size === 0) slotNodesByName.delete(name);\n    }\n\n    recomputeSlot(name);\n  };\n\n  const bindAllSlots = (): void => {\n    host.shadowRoot?.querySelectorAll('slot').forEach((slotEl) => {\n      bindSlot(slotEl);\n    });\n  };\n\n  const recomputeAllSlots = (): void => {\n    for (const name of slotNodesByName.keys()) {\n      recomputeSlot(name);\n    }\n  };\n\n  // Watch for dynamically-inserted <slot> elements (e.g. inside when(), each()).\n  let observer: MutationObserver | null = null;\n\n  // Single init pass, run after the first render: binds slots already present\n  // (pre-upgrade markup and template-rendered ones alike) and starts observation\n  // for slots inserted later. The observer must stay connected for the component's\n  // lifetime — it is the only way to detect a *first* <slot> appearing dynamically\n  // (e.g. a when() branch that renders a slot), so it cannot be disconnected when\n  // the bound-slot count drops to zero.\n  const initSlots = (): undefined => {\n    bindAllSlots();\n    recomputeAllSlots();\n\n    if (!observer && host.shadowRoot) {\n      observer = new MutationObserver((mutations) => {\n        for (const mutation of mutations) {\n          for (const node of mutation.removedNodes) {\n            if (node instanceof HTMLSlotElement) unbindSlot(node);\n          }\n        }\n\n        bindAllSlots();\n\n        if (slotCleanupMap.size > 0) recomputeAllSlots();\n      });\n      observer.observe(host.shadowRoot, { childList: true, subtree: true });\n    }\n\n    return undefined;\n  };\n\n  onMounted(initSlots);\n\n  onCleanup(() => {\n    observer?.disconnect();\n    observer = null;\n\n    for (const cleanup of slotCleanupMap.values()) cleanup();\n\n    slotCleanupMap.clear();\n    slotNodesByName.clear();\n    slotSignals.clear();\n\n    // The element instance survives disconnect/reconnect (custom elements aren't recreated),\n    // but this registry's observer/listeners are torn down above — drop the cache entry so a\n    // subsequent reconnect's setup() rebuilds a live registry instead of reusing a dead one.\n    slotsByElement.delete(host);\n  });\n\n  return {\n    elements: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).elements,\n    has: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).presence,\n  };\n};\n\n// Keyed by the host element, not the ephemeral `RuntimeContext` — `onMounted()` callbacks each\n// run with their own freshly-created context object (see base-element.ts's\n// `_scheduleMountCallbacks`), so keying this on `RuntimeContext` would silently create a second,\n// independent registry (a second `MutationObserver`, a second signal set) every time `useSlots()`\n// was called from inside `onMounted()` rather than directly in `setup()` — a real bug the\n// \"one registry per instance\" doc comment below never actually held for that (common) case.\n/** One slot registry per component instance — reused across repeated `useSlots()` calls. */\nconst slotsByElement = new WeakMap<HTMLElement, ComponentSlots<string>>();\n\n/**\n * Returns reactive slot presence / element signals for the current component.\n * Safe to call multiple times during `setup()` — the underlying slot registry\n * (MutationObserver + `slotchange` listeners) is created once per instance.\n *\n * Pass a `SlotNames` type parameter for typed slot names:\n * ```ts\n * const slots = useSlots<'header' | 'footer'>();\n * slots.has('header'); // typed ✓\n * ```\n */\nexport const useSlots = <SlotNames extends string = string>(): ComponentSlots<SlotNames> => {\n  const ctx = requireSetupContext('useSlots');\n  let entry = slotsByElement.get(ctx.element);\n\n  if (!entry) {\n    entry = createSlots(ctx.element);\n    slotsByElement.set(ctx.element, entry);\n  }\n\n  return entry as ComponentSlots<SlotNames>;\n};\n"],"mappings":"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"}