UNPKG

omi-reactify

Version:

Bridge between omi and react

268 lines (267 loc) 9.33 kB
import h, { forwardRef as C, createElement as E, Component as x, createRef as A } from "react"; import g from "react-dom"; import { createRoot as b } from "react-dom/client"; const $ = () => typeof b < "u", P = () => parseInt(h.version.split(".")[0], 10) >= 19, p = /* @__PURE__ */ new WeakMap(), R = (n) => { if ($()) { let c = p.get(n); return c || (c = { root: b(n) }, p.set(n, c)), { render: (a) => { c.lastElement !== a && (c.root.render(a), c.lastElement = a); }, unmount: () => { c.root.unmount(), p.delete(n); } }; } return { render: (c) => { g.render(c, n); }, unmount: () => { g.unmountComponentAtNode(n); } }; }, H = (n) => n && typeof n == "object" && n.$$typeof && n.$$typeof.toString().includes("react"), u = (n) => n != null && (typeof n == "string" || typeof n == "number" || typeof n == "boolean" || H(n) || Array.isArray(n)), w = /\B([A-Z])/g; function S(n) { return n.replace(w, "-$1").toLowerCase(); } const O = (n) => { if (!n || typeof n != "object") return ""; const c = /* @__PURE__ */ new Set([ "animationIterationCount", "boxFlex", "boxFlexGroup", "boxOrdinalGroup", "columnCount", "fillOpacity", "flex", "flexGrow", "flexShrink", "fontWeight", "lineClamp", "lineHeight", "opacity", "order", "orphans", "tabSize", "widows", "zIndex", "zoom" ]); return Object.entries(n).filter(([, a]) => a != null && a !== "").map(([a, e]) => { const t = a.replace(/[A-Z]/g, (s) => `-${s.toLowerCase()}`); let r = e; return typeof e == "number" && e !== 0 && !c.has(a) && (r = `${e}px`), `${t}:${r};`; }).join(" "); }, m = { "t-chatbot": (n) => n.startsWith("sender-"), "t-chat-item": (n) => n === "actionbar" }, T = (n) => { class c extends x { constructor(e) { super(e), this.processingSlots = /* @__PURE__ */ new Set(), this.lastRenderedElements = /* @__PURE__ */ new Map(), this.eventHandlers = [], this.slotRenderers = /* @__PURE__ */ new Map(); const { innerRef: t } = e; this.ref = t || A(); } setEvent(e, t) { var r; this.eventHandlers.push([e, t]), (r = this.ref.current) == null || r.addEventListener(e, t); } // 处理slot相关的prop handleSlotProp(e, t) { const r = this.ref.current; if (!r || this.processingSlots.has(e)) return; let s = e; e.endsWith("Slot") && (s = e.slice(0, -4)), s = S(s); const o = this.slotRenderers.get(s); if (!(o && this.isSameReactElement(s, t))) if (this.processingSlots.add(e), u(t) && this.lastRenderedElements.set(s, t), typeof t == "function") { o && this.cleanupSlotRenderer(s); const i = (l) => { const f = t(l); return this.renderReactNodeToSlot(f, s); }; r[e] = i, this.processingSlots.delete(e); } else u(t) && (r[e] = !0, Promise.resolve().then(() => { var i; o ? this.updateSlotContent(s, t) : !o && ((i = m[n]) != null && i.call(m, s)) ? (this.renderReactNodeToSlot(t, s), r.update && r.update()) : (r.update && r.update(), this.renderReactNodeToSlot(t, s)), this.processingSlots.delete(e); })); } // 清理slot渲染器的统一方法 cleanupSlotRenderer(e) { const t = this.slotRenderers.get(e); t && (this.clearSlotContainers(e), Promise.resolve().then(() => { c.safeCleanupRenderer(t); }), this.slotRenderers.delete(e)); } updateSlotContent(e, t) { const r = this.ref.current; if (!r) return; const s = r.querySelector(`[slot="${e}"]`); if (s && u(t)) try { h.isValidElement(t) ? R(s).render(t) : (typeof t == "string" || typeof t == "number") && (s.textContent = String(t)); } catch (o) { console.warn("[reactify] Error updating slot content:", o), this.cleanupSlotRenderer(e), this.renderReactNodeToSlot(t, e); } else this.renderReactNodeToSlot(t, e); } // 安全清理渲染器 static safeCleanupRenderer(e) { try { e(); } catch (t) { console.warn("Error cleaning up React renderer:", t); } } // 立即清理指定slot的所有容器 clearSlotContainers(e) { const t = this.ref.current; if (t) try { t.querySelectorAll(`[slot="${e}"]`).forEach((s) => { if (s.parentNode && s.parentNode.contains(s)) try { s.parentNode.removeChild(s); } catch (o) { console.warn(`[reactify] Error removing slot container for "${e}":`, o); } }); } catch (r) { console.warn(`[reactify] Error clearing slot containers for "${e}":`, r); } } // 检查是否是相同的React元素 isSameReactElement(e, t) { const r = this.lastRenderedElements.get(e); return !r || !u(t) ? !1 : r === t ? !0 : (h.isValidElement(r) && h.isValidElement(t), !1); } // 将React节点渲染到slot中 renderReactNodeToSlot(e, t) { const r = this.ref.current; if (!r || r.querySelectorAll(`[slot="${t}"]`).length > 0) return; const o = document.createElement("div"); o.style.display = "contents", o.setAttribute("slot", t), r.appendChild(o); let i = null; if (u(e)) { if (h.isValidElement(e)) try { const l = R(o); l.render(e), i = () => { try { l.unmount(); } catch (f) { console.warn("Error unmounting React renderer:", f); } }; } catch (l) { console.warn("Error creating React renderer:", l); } else if (typeof e == "string" || typeof e == "number") o.textContent = String(e), i = () => { o.textContent = ""; }; else if (Array.isArray(e)) try { const l = R(o), f = h.createElement( "div", { style: { display: "contents" } }, ...e.filter(u) ); l.render(f), i = () => { try { l.unmount(); } catch (d) { console.warn("Error unmounting React renderer:", d); } }; } catch (l) { console.warn("Error creating React renderer for array:", l); } } this.slotRenderers.set(t, () => { this.lastRenderedElements.delete(t), Promise.resolve().then(() => { i && i(), o.parentNode && o.parentNode.removeChild(o); }); }); } update() { this.clearEventHandlers(), this.ref.current && Object.entries(this.props).forEach(([e, t]) => { var r, s, o, i, l; if (!["innerRef", "children"].includes(e)) { if (typeof t == "function" && e.match(/^on[A-Za-z]/)) { const f = e.slice(2), d = f[0].toLowerCase() + f.slice(1); this.setEvent(d, t); return; } if (typeof t == "function" && e.match(/^render[A-Za-z]/)) { this.handleSlotProp(e, t); return; } if (u(t) && !e.match(/^on[A-Za-z]/) && !e.match(/^render[A-Za-z]/)) { const f = e.endsWith("Slot"), d = S(f ? e.slice(0, -4) : e); let y = ((s = (r = this.ref.current) == null ? void 0 : r.shadowRoot) == null ? void 0 : s.querySelector(`slot[name="${d}"]`)) !== null; if (!y && m[n] && (y = m[n](d)), y) { this.handleSlotProp(e, t); return; } } if (typeof t == "object" && t !== null) { if (e === "style") { (o = this.ref.current) == null || o.setAttribute("style", O(t)); return; } this.ref.current[e] = t; return; } if (typeof t == "function") { this.ref.current[e] = t; return; } if (e.match(w)) { (i = this.ref.current) == null || i.setAttribute(S(e), t), (l = this.ref.current) == null || l.removeAttribute(e); return; } P() || (this.ref.current[e] = t); } }); } componentDidUpdate() { this.update(); } componentDidMount() { this.update(); } componentWillUnmount() { this.clearEventHandlers(), this.clearSlotRenderers(); } clearEventHandlers() { this.eventHandlers.forEach(([e, t]) => { var r; (r = this.ref.current) == null || r.removeEventListener(e, t); }), this.eventHandlers = []; } clearSlotRenderers() { this.slotRenderers.forEach((e) => { c.safeCleanupRenderer(e); }), this.slotRenderers.clear(), this.processingSlots.clear(); } render() { const { children: e, className: t, ...r } = this.props, s = {}; return Object.keys(r).forEach((o) => { const i = r[o]; (typeof i == "string" || typeof i == "number" || typeof i == "boolean") && (s[o] = i); }), E(n, { class: t, ...s, ref: this.ref }, e); } } return C( (a, e) => E(c, { ...a, innerRef: e }) ); }; export { T as default, S as hyphenate };