import type { ElementNode, DirectiveNode, JsxContext } from "../types"; import { unwrapExpression } from "./utils"; /** * Process a element into JSX. * - `` → `{slots.default?.()}` * - `` → `{slots.header?.()}` * - `` → `{slots.item?.({ item, index: i })}` * - `fallback` → `{slots.default?.() ?? <>fallback}` */ export function processSlot( node: ElementNode, ctx: JsxContext, renderChildren: (node: ElementNode, ctx: JsxContext) => string, ): string { const raw = processSlotRaw(node, ctx, renderChildren); return `{${raw}}`; } /** * Process a element into a raw JSX expression (no {} wrapping). * Used by renderFullElement so conditional chains can wrap correctly. */ export function processSlotRaw( node: ElementNode, ctx: JsxContext, renderChildren: (node: ElementNode, ctx: JsxContext) => string, ): string { // Determine slot name let slotName = "default"; const nameProp = node.props.find((p) => p.type === 6 && (p as any).name === "name"); if (nameProp && nameProp.type === 6) { slotName = (nameProp as any).value?.content ?? "default"; } const dynamicName = node.props.find( (p): p is DirectiveNode => p.type === 7 && p.name === "bind" && p.arg != null && (p.arg as any).content === "name", ); let isDynamicName = false; if (dynamicName) { slotName = unwrapExpression(dynamicName.exp as any); isDynamicName = true; } const slotProps: string[] = []; for (const prop of node.props) { if (prop.type === 6) { if ((prop as any).name === "name") continue; const val = (prop as any).value?.content; if (val != null) { const propName = (prop as any).name; const needsQuotes = !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(propName); const safeName = needsQuotes ? `'${propName}'` : propName; slotProps.push(`${safeName}: "${val}"`); } } else if (prop.type === 7 && prop.name === "bind") { const dir = prop as DirectiveNode; if (dir.arg && (dir.arg as any).content === "name") continue; if (!dir.arg) { const expr = unwrapExpression(dir.exp as any, ctx); if (expr) slotProps.push(`...${expr}`); continue; } const propName = (dir.arg as any).content; const needsQuotes = !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(propName); const safeName = needsQuotes ? `'${propName}'` : propName; const expr = unwrapExpression(dir.exp as any, ctx); slotProps.push(`${safeName}: ${expr}`); } } ctx.usedContextMembers.add("slots"); const propsArg = slotProps.length > 0 ? `{ ${slotProps.join(", ")} }` : ""; const needsBracket = isDynamicName || !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(slotName); const slotAccess = needsBracket ? isDynamicName ? `slots[${slotName}]` : `slots['${slotName}']` : `slots.${slotName}`; const call = `${slotAccess}?.(${propsArg})`; const fallback = renderChildren(node, ctx); if (fallback.trim()) { return `(${call} ?? <>${fallback})`; } return call; } /** * Extract slot content from a component's children. * Handles v-slot directives on