omi-reactify
Version:
Bridge between omi and react
268 lines (267 loc) • 9.33 kB
JavaScript
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
};