import { effect } from './signal.js'; type Child = Node | string | number | boolean | null | undefined | (() => unknown); type Props = Record; /*** 响应式属性绑定:函数值属性在依赖变化时自动写回 DOM ***/ function bindProp(el: HTMLElement, key: string, fn: () => unknown): void { effect(() => { const v = fn(); if (key === 'value') (el as unknown as HTMLInputElement).value = String(v); else if (key === 'checked') (el as unknown as HTMLInputElement).checked = Boolean(v); else if (v === false || v == null) el.removeAttribute(key); else el.setAttribute(key, v === true ? '' : String(v)); }); } /*** 静态属性写入:false / null 移除属性,true 置空标记属性 ***/ function bindStatic(el: HTMLElement, key: string, value: unknown): void { if (value === false || value == null) el.removeAttribute(key); else if (value === true) el.setAttribute(key, ''); else el.setAttribute(key, String(value)); } /*** 挂载子节点:函数型子节点转为响应式文本节点 ***/ function appendChild(el: HTMLElement, child: unknown): void { if (child == null || child === false) return; if (typeof child === 'function') { const node = document.createTextNode(''); effect(() => { node.textContent = String(child()); }); el.append(node); } else if (typeof child === 'object') { el.append(child as Node); } else { el.append(String(child)); } } /*** hyperscript 入口:h(tag, props, ...children);on* 为事件,函数值为响应式属性 ***/ export function h(tag: string, props?: Props | null, ...children: unknown[]): HTMLElement { const el = document.createElement(tag); for (const [key, value] of Object.entries(props ?? {})) { if (key.startsWith('on') && typeof value === 'function') { el.addEventListener(key.slice(2).toLowerCase(), value as EventListener); } else if (typeof value === 'function') { bindProp(el, key, value as () => unknown); } else if (key === 'class') { el.className = String(value); } else { bindStatic(el, key, value); } } for (const child of children.flat(Infinity)) appendChild(el, child); return el; } /*** 列表渲染:信号变化时整体替换容器子节点(tbody / 列表等场景) ***/ export function renderList(el: HTMLElement, fn: () => Node[]): void { effect(() => { el.replaceChildren(...fn()); }); } /*** 挂载视图到容器;容器不存在时静默跳过 ***/ export function mount(target: Element | string, view: () => Node): void { const root = typeof target === 'string' ? document.querySelector(target) : target; if (!root) return; // 卫语句:容器不存在 root.replaceChildren(view()); }