UNPKG

@opentui/solid

Version:
1,252 lines (1,230 loc) 35.6 kB
// @bun // index.ts import { CliRenderer, createCliRenderer, engine as engine2 } from "@opentui/core"; import { createTestRenderer } from "@opentui/core/testing"; // src/elements/index.ts import { ASCIIFontRenderable, BoxRenderable, CodeRenderable, DiffRenderable, InputRenderable as InputRenderable2, LineNumberRenderable, MarkdownRenderable, ScrollBoxRenderable as ScrollBoxRenderable2, SelectRenderable as SelectRenderable2, TabSelectRenderable as TabSelectRenderable2, TextareaRenderable, TextAttributes, TextNodeRenderable as TextNodeRenderable3, TextRenderable as TextRenderable3 } from "@opentui/core"; // src/elements/hooks.ts import { engine, Timeline } from "@opentui/core"; import { createContext, createSignal, onCleanup, onMount, useContext } from "solid-js"; var RendererContext = createContext(); var useRenderer = () => { const renderer = useContext(RendererContext); if (!renderer) { throw new Error("No renderer found"); } return renderer; }; var onResize = (callback) => { const renderer = useRenderer(); onMount(() => { renderer.on("resize", callback); }); onCleanup(() => { renderer.off("resize", callback); }); }; var useTerminalDimensions = () => { const renderer = useRenderer(); const [terminalDimensions, setTerminalDimensions] = createSignal({ width: renderer.width, height: renderer.height }); const callback = (width, height) => { setTerminalDimensions({ width, height }); }; onResize(callback); return terminalDimensions; }; var useKeyboard = (callback, options) => { const renderer = useRenderer(); const keyHandler = renderer.keyInput; onMount(() => { keyHandler.on("keypress", callback); if (options?.release) { keyHandler.on("keyrelease", callback); } }); onCleanup(() => { keyHandler.off("keypress", callback); if (options?.release) { keyHandler.off("keyrelease", callback); } }); }; var usePaste = (callback) => { const renderer = useRenderer(); const keyHandler = renderer.keyInput; onMount(() => { keyHandler.on("paste", callback); }); onCleanup(() => { keyHandler.off("paste", callback); }); }; var useKeyHandler = useKeyboard; var onFocus = (callback) => { const renderer = useRenderer(); onMount(() => { renderer.on("focus", callback); }); onCleanup(() => { renderer.off("focus", callback); }); }; var onBlur = (callback) => { const renderer = useRenderer(); onMount(() => { renderer.on("blur", callback); }); onCleanup(() => { renderer.off("blur", callback); }); }; var useSelectionHandler = (callback) => { const renderer = useRenderer(); onMount(() => { renderer.on("selection", callback); }); onCleanup(() => { renderer.off("selection", callback); }); }; var useTimeline = (options = {}) => { const timeline = new Timeline(options); onMount(() => { if (options.autoplay !== false) { timeline.play(); } engine.register(timeline); }); onCleanup(() => { timeline.pause(); engine.unregister(timeline); }); return timeline; }; // src/elements/extras.ts import { createEffect, createMemo as createMemo2, getOwner, onCleanup as onCleanup2, runWithOwner, splitProps, untrack as untrack2 } from "solid-js"; // src/reconciler.ts import { BaseRenderable, createTextAttributes, InputRenderable, InputRenderableEvents, isTextNodeRenderable, parseColor, Renderable, RootTextNodeRenderable, ScrollBoxRenderable, SelectRenderable, SelectRenderableEvents, TabSelectRenderable, TabSelectRenderableEvents, TextNodeRenderable, TextRenderable } from "@opentui/core"; import { decodeHTML } from "entities"; import { useContext as useContext2 } from "solid-js"; // src/renderer/universal.js import { createRoot, createRenderEffect, createMemo, createComponent, untrack, mergeProps } from "solid-js"; var memo = (fn) => createMemo(() => fn()); function createRenderer({ createElement, createTextNode, createSlotNode, isTextNode, replaceText, insertNode, removeNode, setProperty, getParentNode, getFirstChild, getNextSibling }) { function insert(parent, accessor, marker, initial) { if (marker !== undefined && !initial) initial = []; if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker); createRenderEffect((current) => insertExpression(parent, accessor(), current, marker), initial); } function insertExpression(parent, value, current, marker, unwrapArray) { while (typeof current === "function") current = current(); if (value === current) return current; const t = typeof value, multi = marker !== undefined; if (t === "string" || t === "number") { if (t === "number") value = value.toString(); if (multi) { let node = current[0]; if (node && isTextNode(node)) { replaceText(node, value); } else node = createTextNode(value); current = cleanChildren(parent, current, marker, node); } else { if (current !== "" && typeof current === "string") { replaceText(getFirstChild(parent), current = value); } else { cleanChildren(parent, current, marker, createTextNode(value)); current = value; } } } else if (value == null || t === "boolean") { current = cleanChildren(parent, current, marker); } else if (t === "function") { createRenderEffect(() => { let v = value(); while (typeof v === "function") v = v(); current = insertExpression(parent, v, current, marker); }); return () => current; } else if (Array.isArray(value)) { const array = []; if (normalizeIncomingArray(array, value, unwrapArray)) { createRenderEffect(() => current = insertExpression(parent, array, current, marker, true)); return () => current; } if (array.length === 0) { const replacement = cleanChildren(parent, current, marker); if (multi) return current = replacement; } else { if (Array.isArray(current)) { if (current.length === 0) { appendNodes(parent, array, marker); } else reconcileArrays(parent, current, array); } else if (current == null || current === "") { appendNodes(parent, array); } else { reconcileArrays(parent, multi && current || [getFirstChild(parent)], array); } } current = array; } else { if (Array.isArray(current)) { if (multi) return current = cleanChildren(parent, current, marker, value); cleanChildren(parent, current, null, value); } else if (current == null || current === "" || !getFirstChild(parent)) { insertNode(parent, value); } else replaceNode(parent, value, getFirstChild(parent)); current = value; } return current; } function normalizeIncomingArray(normalized, array, unwrap) { let dynamic = false; for (let i = 0, len = array.length;i < len; i++) { let item = array[i], t; if (item == null || item === true || item === false) ; else if (Array.isArray(item)) { dynamic = normalizeIncomingArray(normalized, item) || dynamic; } else if ((t = typeof item) === "string" || t === "number") { normalized.push(createTextNode(item)); } else if (t === "function") { if (unwrap) { while (typeof item === "function") item = item(); dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item]) || dynamic; } else { normalized.push(item); dynamic = true; } } else normalized.push(item); } return dynamic; } function reconcileArrays(parentNode, a, b) { let bLength = b.length, aEnd = a.length, bEnd = bLength, aStart = 0, bStart = 0, after = getNextSibling(a[aEnd - 1]), map = null; while (aStart < aEnd || bStart < bEnd) { if (a[aStart] === b[bStart]) { aStart++; bStart++; continue; } while (a[aEnd - 1] === b[bEnd - 1]) { aEnd--; bEnd--; } if (aEnd === aStart) { const node = bEnd < bLength ? bStart ? getNextSibling(b[bStart - 1]) : b[bEnd - bStart] : after; while (bStart < bEnd) insertNode(parentNode, b[bStart++], node); } else if (bEnd === bStart) { while (aStart < aEnd) { if (!map || !map.has(a[aStart])) removeNode(parentNode, a[aStart]); aStart++; } } else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) { const node = getNextSibling(a[--aEnd]); insertNode(parentNode, b[bStart++], getNextSibling(a[aStart++])); insertNode(parentNode, b[--bEnd], node); a[aEnd] = b[bEnd]; } else { if (!map) { map = new Map; let i = bStart; while (i < bEnd) map.set(b[i], i++); } const index = map.get(a[aStart]); if (index != null) { if (bStart < index && index < bEnd) { let i = aStart, sequence = 1, t; while (++i < aEnd && i < bEnd) { if ((t = map.get(a[i])) == null || t !== index + sequence) break; sequence++; } if (sequence > index - bStart) { const node = a[aStart]; while (bStart < index) insertNode(parentNode, b[bStart++], node); } else replaceNode(parentNode, b[bStart++], a[aStart++]); } else aStart++; } else removeNode(parentNode, a[aStart++]); } } } function cleanChildren(parent, current, marker, replacement) { if (marker === undefined) { let removed; while (removed = getFirstChild(parent)) removeNode(parent, removed); replacement && insertNode(parent, replacement); return replacement ?? ""; } const node = replacement || createSlotNode(); if (current.length) { let inserted = false; for (let i = current.length - 1;i >= 0; i--) { const el = current[i]; if (node !== el) { const isParent = getParentNode(el) === parent; if (!inserted && !i) isParent ? replaceNode(parent, node, el) : insertNode(parent, node, marker); else isParent && removeNode(parent, el); } else inserted = true; } } else insertNode(parent, node, marker); return [node]; } function appendNodes(parent, array, marker) { for (let i = 0, len = array.length;i < len; i++) insertNode(parent, array[i], marker); } function replaceNode(parent, newNode, oldNode) { insertNode(parent, newNode, oldNode); removeNode(parent, oldNode); } function spreadExpression(node, props, prevProps = {}, skipChildren) { props || (props = {}); if (!skipChildren) { createRenderEffect(() => prevProps.children = insertExpression(node, props.children, prevProps.children)); } createRenderEffect(() => props.ref && props.ref(node)); createRenderEffect(() => { for (const prop in props) { if (prop === "children" || prop === "ref") continue; const value = props[prop]; if (value === prevProps[prop]) continue; setProperty(node, prop, value, prevProps[prop]); prevProps[prop] = value; } }); return prevProps; } return { render(code, element) { let disposer; createRoot((dispose) => { disposer = dispose; insert(element, code()); }); return disposer; }, insert, spread(node, accessor, skipChildren) { if (typeof accessor === "function") { createRenderEffect((current) => spreadExpression(node, accessor(), current, skipChildren)); } else spreadExpression(node, accessor, undefined, skipChildren); }, createElement, createTextNode, insertNode, setProp(node, name, value, prev) { setProperty(node, name, value, prev); return value; }, mergeProps, effect: createRenderEffect, memo, createComponent, use(fn, element, arg) { return untrack(() => fn(element, arg)); } }; } // src/renderer/index.ts import { mergeProps as mergeProps2 } from "solid-js"; function createRenderer2(options) { const renderer = createRenderer(options); renderer.mergeProps = mergeProps2; return renderer; } // src/utils/id-counter.ts var idCounter = new Map; function getNextId(elementType) { if (!idCounter.has(elementType)) { idCounter.set(elementType, 0); } const value = idCounter.get(elementType) + 1; idCounter.set(elementType, value); return `${elementType}-${value}`; } // src/utils/log.ts var log = (...args) => { if (process.env.DEBUG) { console.log("[Reconciler]", ...args); } }; // src/reconciler.ts class TextNode extends TextNodeRenderable { static fromString(text, options = {}) { const node = new TextNode(options); node.add(text); return node; } } var logId = (node) => { if (!node) return; return node.id; }; var getNodeChildren = (node) => { let children; if (node instanceof TextRenderable) { children = node.getTextChildren(); } else { children = node.getChildren(); } return children; }; function _insertNode(parent, node, anchor) { log("Inserting node:", logId(node), "into parent:", logId(parent), "with anchor:", logId(anchor), node instanceof TextNode); if (node instanceof SlotRenderable) { node.parent = parent; node = node.getSlotChild(parent); } if (anchor && anchor instanceof SlotRenderable) { anchor = anchor.getSlotChild(parent); } if (isTextNodeRenderable(node)) { if (!(parent instanceof TextRenderable) && !isTextNodeRenderable(parent)) { throw new Error(`Orphan text error: "${node.toChunks().map((c) => c.text).join("")}" must have a <text> as a parent: ${parent.id} above ${node.id}`); } } if (!(parent instanceof BaseRenderable)) { console.error("[INSERT]", "Tried to mount a non base renderable"); throw new Error("Tried to mount a non base renderable"); } if (!anchor) { parent.add(node); return; } const children = getNodeChildren(parent); const anchorIndex = children.findIndex((el) => el.id === anchor.id); if (anchorIndex === -1) { log("[INSERT]", "Could not find anchor", logId(parent), logId(anchor), "[children]", ...children.map((c) => c.id)); } parent.add(node, anchorIndex); } function _removeNode(parent, node) { log("Removing node:", logId(node), "from parent:", logId(parent)); if (node instanceof SlotRenderable) { node.parent = null; node = node.getSlotChild(parent); } parent.remove(node.id); process.nextTick(() => { if (node instanceof BaseRenderable && !node.parent) { node.destroyRecursively(); return; } }); } function _createTextNode(value) { log("Creating text node:", value); const id = getNextId("text-node"); if (typeof value === "number") { value = value.toString(); } return TextNode.fromString(decodeHTML(value), { id }); } function createSlotNode() { const id = getNextId("slot-node"); log("Creating slot node", id); return new SlotRenderable(id); } function _getParentNode(childNode) { log("Getting parent of node:", logId(childNode)); let parent = childNode.parent ?? undefined; if (parent instanceof RootTextNodeRenderable) { parent = parent.textParent ?? undefined; } const scrollBoxCandidate = parent?.parent?.parent?.parent; if (scrollBoxCandidate instanceof ScrollBoxRenderable && scrollBoxCandidate.content === parent) { parent = scrollBoxCandidate; } return parent; } var { render: _render, effect, memo: memo2, createComponent: createComponent2, createElement, createTextNode, insertNode, insert, spread, setProp, mergeProps: mergeProps3, use } = createRenderer2({ createElement(tagName) { log("Creating element:", tagName); const id = getNextId(tagName); const solidRenderer = useContext2(RendererContext); if (!solidRenderer) { throw new Error("No renderer found"); } const elements = getComponentCatalogue(); if (!elements[tagName]) { throw new Error(`[Reconciler] Unknown component type: ${tagName}`); } const element = new elements[tagName](solidRenderer, { id }); log("Element created with id:", id); return element; }, createTextNode: _createTextNode, createSlotNode, replaceText(textNode, value) { log("Replacing text:", value, "in node:", logId(textNode)); if (!(textNode instanceof TextNode)) return; textNode.replace(decodeHTML(value), 0); }, setProperty(node, name, value, prev) { if (name.startsWith("on:")) { const eventName = name.slice(3); if (value) { node.on(eventName, value); } if (prev) { node.off(eventName, prev); } return; } if (isTextNodeRenderable(node)) { if (name === "href") { node.link = { url: value }; return; } if (name === "style") { node.attributes |= createTextAttributes(value); node.fg = value.fg ? parseColor(value.fg) : node.fg; node.bg = value.bg ? parseColor(value.bg) : node.bg; return; } return; } switch (name) { case "id": log("Id mapped", node.id, "=", value); node[name] = value; break; case "focused": if (!(node instanceof Renderable)) return; if (value) { node.focus(); } else { node.blur(); } break; case "onChange": let event = undefined; if (node instanceof SelectRenderable) { event = SelectRenderableEvents.SELECTION_CHANGED; } else if (node instanceof TabSelectRenderable) { event = TabSelectRenderableEvents.SELECTION_CHANGED; } else if (node instanceof InputRenderable) { event = InputRenderableEvents.CHANGE; } if (!event) break; if (value) { node.on(event, value); } if (prev) { node.off(event, prev); } break; case "onInput": if (node instanceof InputRenderable) { if (value) { node.on(InputRenderableEvents.INPUT, value); } if (prev) { node.off(InputRenderableEvents.INPUT, prev); } } break; case "onSubmit": if (node instanceof InputRenderable) { if (value) { node.on(InputRenderableEvents.ENTER, value); } if (prev) { node.off(InputRenderableEvents.ENTER, prev); } } else { node[name] = value; } break; case "onSelect": if (node instanceof SelectRenderable) { if (value) { node.on(SelectRenderableEvents.ITEM_SELECTED, value); } if (prev) { node.off(SelectRenderableEvents.ITEM_SELECTED, prev); } } else if (node instanceof TabSelectRenderable) { if (value) { node.on(TabSelectRenderableEvents.ITEM_SELECTED, value); } if (prev) { node.off(TabSelectRenderableEvents.ITEM_SELECTED, prev); } } break; case "style": for (const prop in value) { const propVal = value[prop]; if (prev !== undefined && propVal === prev[prop]) continue; node[prop] = propVal; } break; case "text": case "content": { const textValue = typeof value === "string" ? value : Array.isArray(value) ? value.join("") : `${value}`; node[name] = decodeHTML(textValue); break; } default: node[name] = value; } }, isTextNode(node) { return node instanceof TextNode; }, insertNode: _insertNode, removeNode: _removeNode, getParentNode: _getParentNode, getFirstChild(node) { log("Getting first child of node:", logId(node)); const firstChild = getNodeChildren(node)[0]; if (!firstChild) { log("No first child found for node:", logId(node)); return; } log("First child found:", logId(firstChild), "for node:", logId(node)); return firstChild; }, getNextSibling(node) { log("Getting next sibling of node:", logId(node)); const parent = _getParentNode(node); if (!parent) { log("No parent found for node:", logId(node)); return; } const siblings = getNodeChildren(parent); const index = siblings.indexOf(node); if (index === -1 || index === siblings.length - 1) { log("No next sibling found for node:", logId(node)); return; } const nextSibling = siblings[index + 1]; if (!nextSibling) { log("Next sibling is null for node:", logId(node)); return; } log("Next sibling found:", logId(nextSibling), "for node:", logId(node)); return nextSibling; } }); // src/elements/extras.ts function Portal(props) { const renderer = useRenderer(); const marker = createSlotNode(), mount = () => props.mount || renderer.root, owner = getOwner(); let content; createEffect(() => { content || (content = runWithOwner(owner, () => createMemo2(() => props.children))); const el = mount(); const container = createElement("box"), renderRoot = container; Object.defineProperty(container, "_$host", { get() { return marker.parent; }, configurable: true }); insert(renderRoot, content); el.add(container); props.ref && props.ref(container); onCleanup2(() => el.remove(container.id)); }, undefined, { render: true }); return marker; } function createDynamic(component, props) { const cached = createMemo2(component); return createMemo2(() => { const component2 = cached(); switch (typeof component2) { case "function": return untrack2(() => component2(props)); case "string": const el = createElement(component2); spread(el, props); return el; default: break; } }); } function Dynamic(props) { const [, others] = splitProps(props, ["component"]); return createDynamic(() => props.component, others); } // src/elements/slot.ts import { BaseRenderable as BaseRenderable2, isTextNodeRenderable as isTextNodeRenderable2, TextNodeRenderable as TextNodeRenderable2, TextRenderable as TextRenderable2, Yoga } from "@opentui/core"; class SlotBaseRenderable extends BaseRenderable2 { constructor(id) { super({ id }); } add(obj, index) { throw new Error("Can't add children on an Slot renderable"); } getChildren() { return []; } remove(id) {} insertBefore(obj, anchor) { throw new Error("Can't add children on an Slot renderable"); } getRenderable(id) { return; } getChildrenCount() { return 0; } requestRender() {} findDescendantById(id) { return; } } class TextSlotRenderable extends TextNodeRenderable2 { slotParent; destroyed = false; constructor(id, parent) { super({ id }); this._visible = false; this.slotParent = parent; } destroy() { if (this.destroyed) { return; } this.destroyed = true; this.slotParent?.destroy(); super.destroy(); } } class LayoutSlotRenderable extends SlotBaseRenderable { yogaNode; slotParent; destroyed = false; constructor(id, parent) { super(id); this._visible = false; this.slotParent = parent; this.yogaNode = Yoga.default.Node.create(); this.yogaNode.setDisplay(Yoga.Display.None); } getLayoutNode() { return this.yogaNode; } updateFromLayout() {} updateLayout() {} onRemove() {} destroy() { if (this.destroyed) { return; } this.destroyed = true; super.destroy(); this.slotParent?.destroy(); } } class SlotRenderable extends SlotBaseRenderable { layoutNode; textNode; destroyed = false; constructor(id) { super(id); this._visible = false; } getSlotChild(parent) { if (isTextNodeRenderable2(parent) || parent instanceof TextRenderable2) { if (!this.textNode) { this.textNode = new TextSlotRenderable(`slot-text-${this.id}`, this); } return this.textNode; } if (!this.layoutNode) { this.layoutNode = new LayoutSlotRenderable(`slot-layout-${this.id}`, this); } return this.layoutNode; } destroy() { if (this.destroyed) { return; } this.destroyed = true; if (this.layoutNode) { this.layoutNode.destroy(); } if (this.textNode) { this.textNode.destroy(); } } } // src/elements/index.ts class SpanRenderable extends TextNodeRenderable3 { _ctx; constructor(_ctx, options) { super(options); this._ctx = _ctx; } } var textNodeKeys = ["span", "b", "strong", "i", "em", "u", "a"]; class TextModifierRenderable extends SpanRenderable { constructor(options, modifier) { super(null, options); if (modifier === "b" || modifier === "strong") { this.attributes = (this.attributes || 0) | TextAttributes.BOLD; } else if (modifier === "i" || modifier === "em") { this.attributes = (this.attributes || 0) | TextAttributes.ITALIC; } else if (modifier === "u") { this.attributes = (this.attributes || 0) | TextAttributes.UNDERLINE; } } } class BoldSpanRenderable extends TextModifierRenderable { constructor(options) { super(options, "b"); } } class ItalicSpanRenderable extends TextModifierRenderable { constructor(options) { super(options, "i"); } } class UnderlineSpanRenderable extends TextModifierRenderable { constructor(options) { super(options, "u"); } } class LineBreakRenderable extends SpanRenderable { constructor(_ctx, options) { super(null, options); this.add(); } add() { return super.add(` `); } } class LinkRenderable extends SpanRenderable { constructor(_ctx, options) { const linkOptions = { ...options, link: { url: options.href } }; super(null, linkOptions); } } var baseComponents = { box: BoxRenderable, text: TextRenderable3, input: InputRenderable2, select: SelectRenderable2, textarea: TextareaRenderable, ascii_font: ASCIIFontRenderable, tab_select: TabSelectRenderable2, scrollbox: ScrollBoxRenderable2, code: CodeRenderable, diff: DiffRenderable, line_number: LineNumberRenderable, markdown: MarkdownRenderable, span: SpanRenderable, strong: BoldSpanRenderable, b: BoldSpanRenderable, em: ItalicSpanRenderable, i: ItalicSpanRenderable, u: UnderlineSpanRenderable, br: LineBreakRenderable, a: LinkRenderable }; var componentCatalogue = { ...baseComponents }; function extend(objects) { Object.assign(componentCatalogue, objects); } function getComponentCatalogue() { return componentCatalogue; } // src/time-to-first-draw.tsx import { TimeToFirstDrawRenderable } from "@opentui/core"; extend({ time_to_first_draw: TimeToFirstDrawRenderable }); var TimeToFirstDraw = (props) => { return (() => { var _el$ = createElement("time_to_first_draw"); spread(_el$, props, false); return _el$; })(); }; // src/plugins/slot.tsx import { createSlotRegistry } from "@opentui/core"; import { children, createMemo as createMemo3, createSignal as createSignal2, ErrorBoundary, For, onCleanup as onCleanup3, splitProps as splitProps2 } from "solid-js"; var EMPTY_ENTRY_IDS = []; function createSolidSlotRegistry(renderer, context, options = {}) { return createSlotRegistry(renderer, "solid:slot-registry", context, options); } function createSlot(registry, options = {}) { return function BoundSlot(props) { return createComponent2(Slot, mergeProps3(props, { registry, get pluginFailurePlaceholder() { return options.pluginFailurePlaceholder; } })); }; } function Slot(props) { const [local, slotProps] = splitProps2(props, ["registry", "name", "mode", "children", "pluginFailurePlaceholder"]); const registry = () => local.registry; const pluginFailurePlaceholder = () => local.pluginFailurePlaceholder; const [version, setVersion] = createSignal2(0); let queued = false; let disposed = false; const unsubscribe = registry().subscribe(() => { if (queued) return; queued = true; setVersion((current) => current + 1); queueMicrotask(() => { queued = false; if (disposed) return; }); }); onCleanup3(() => { disposed = true; unsubscribe(); }); const entries = createMemo3((previousEntries = []) => { version(); const resolvedEntries = registry().resolveEntries(local.name); if (resolvedEntries.length === 0) { if (previousEntries.length === 0) return previousEntries; return []; } const previousById = new Map(previousEntries.map((entry) => [entry.id, entry])); const nextEntries = resolvedEntries.map((entry) => { const previousEntry = previousById.get(entry.id); if (previousEntry && previousEntry.renderer === entry.renderer) { return previousEntry; } return entry; }); const unchanged = nextEntries.length === previousEntries.length && nextEntries.every((entry, index) => entry === previousEntries[index]); if (unchanged) return previousEntries; return nextEntries; }); const entryIds = createMemo3(() => entries().map((entry) => entry.id)); const entriesById = createMemo3(() => new Map(entries().map((entry) => [entry.id, entry]))); const slotName = () => String(local.name); const renderFallback = () => { const value = children(() => local.children)(); return value ?? null; }; const resolveFallback = (fallbackValue) => fallbackValue?.() ?? null; const renderPluginFailurePlaceholder = (failure, fallbackValue) => { if (!pluginFailurePlaceholder()) { return resolveFallback(fallbackValue); } try { return pluginFailurePlaceholder()(failure); } catch (error) { registry().reportPluginError({ pluginId: failure.pluginId, slot: failure.slot ?? slotName(), phase: "error_placeholder", source: "solid", error }); return resolveFallback(fallbackValue); } }; const renderEntry = (entry, fallbackOnError) => { let initialRender; try { initialRender = entry.renderer(registry().context, slotProps); } catch (error) { const failure = registry().reportPluginError({ pluginId: entry.id, slot: slotName(), phase: "render", source: "solid", error }); return renderPluginFailurePlaceholder(failure, fallbackOnError); } const resolvedInitialRender = children(() => initialRender); const hasInitialOutput = resolvedInitialRender.toArray().some((node) => node !== null && node !== undefined && node !== false); if (!hasInitialOutput) { return resolveFallback(fallbackOnError); } return createComponent2(ErrorBoundary, { fallback: (error) => { const failure = registry().reportPluginError({ pluginId: entry.id, slot: slotName(), phase: "render", source: "solid", error }); return renderPluginFailurePlaceholder(failure, fallbackOnError); }, get children() { return resolvedInitialRender(); } }); }; const AppendEntry = (appendProps) => { const entry = createMemo3(() => entriesById().get(appendProps.entryId)); return memo2(() => { const resolvedEntry = entry(); if (!resolvedEntry) { return null; } return renderEntry(resolvedEntry); }); }; const appendEntryIds = createMemo3(() => { const mode = local.mode ?? "append"; if (mode !== "append") { return EMPTY_ENTRY_IDS; } return entryIds(); }); const appendView = [renderFallback, createComponent2(For, { get each() { return appendEntryIds(); }, children: (entryId) => createComponent2(AppendEntry, { entryId }) })]; return memo2(() => { const resolvedEntries = entries(); const mode = local.mode ?? "append"; if (resolvedEntries.length === 0) { return renderFallback(); } if (mode === "single_winner") { const winner = resolvedEntries[0]; if (!winner) { return renderFallback(); } return renderEntry(winner, renderFallback); } if (mode === "replace") { const renderedEntries = resolvedEntries.map((entry) => renderEntry(entry)); const hasPluginOutput = renderedEntries.some((entry) => entry !== null && entry !== undefined && entry !== false); if (!hasPluginOutput) { return renderFallback(); } return renderedEntries; } return appendView; }); } // index.ts var mountSolidRoot = (renderer, node) => { let dispose; let disposeRequested = false; let disposed = false; let mounting = true; let destroyRequested = false; const originalDestroy = renderer.destroy.bind(renderer); const runDispose = () => { if (disposed) { return; } if (!dispose) { disposeRequested = true; return; } disposed = true; dispose(); }; renderer.once("destroy", runDispose); renderer.destroy = () => { if (mounting) { destroyRequested = true; return; } originalDestroy(); }; try { dispose = _render(() => createComponent2(RendererContext.Provider, { get value() { return renderer; }, get children() { return createComponent2(node, {}); } }), renderer.root); } finally { mounting = false; renderer.destroy = originalDestroy; } if (disposeRequested) { runDispose(); } if (destroyRequested) { originalDestroy(); } }; var render = async (node, rendererOrConfig = {}) => { const renderer = rendererOrConfig instanceof CliRenderer ? rendererOrConfig : await createCliRenderer({ ...rendererOrConfig, onDestroy: () => { rendererOrConfig.onDestroy?.(); } }); engine2.attach(renderer); mountSolidRoot(renderer, node); }; var testRender = async (node, renderConfig = {}) => { const testSetup = await createTestRenderer({ ...renderConfig, onDestroy: () => { renderConfig.onDestroy?.(); } }); engine2.attach(testSetup.renderer); mountSolidRoot(testSetup.renderer, node); return testSetup; }; export { useTimeline, useTerminalDimensions, useSelectionHandler, useRenderer, usePaste, useKeyboard, useKeyHandler, use, textNodeKeys, testRender, spread, setProp, render, onResize, onFocus, onBlur, mergeProps3 as mergeProps, memo2 as memo, insertNode, insert, getComponentCatalogue, extend, effect, createTextNode, createSolidSlotRegistry, createSlotNode, createSlot, createElement, createDynamic, createComponent2 as createComponent, componentCatalogue, baseComponents, _render, UnderlineSpanRenderable, TimeToFirstDraw, TextSlotRenderable, SlotRenderable, Slot, RendererContext, Portal, LinkRenderable, LineBreakRenderable, LayoutSlotRenderable, ItalicSpanRenderable, Dynamic, BoldSpanRenderable };