ziko-wrapper
Version:
integrate zikojs elements within other ui framework like vue react solidjs svelte astro ...
285 lines (252 loc) • 8.83 kB
JSX
import { useRef, useEffect, isValidElement, createElement } from 'react';
import { createRoot } from 'react-dom/client';
const isDOMElement = (obj) => obj instanceof HTMLElement;
const isReactElement = (child) => isValidElement(child);
// Transform React elements to DOM, or return DOM elements as-is
const domify = (element, ...args) => {
// If it's already a DOM element, return it directly
if (isDOMElement(element)) return element;
// If it's a React element, bridge it and return the DOM container
if (isReactElement(element)) {
const container = document.createElement('div');
container.style.display = 'contents';
const root = createRoot(container);
root.render(element);
// Store root for potential cleanup
container._reactRoot = root;
return container;
}
// If it's a function (React component), create element with props and children
if (typeof element === 'function') {
const [props, ...children] = args;
const reactElement = createElement(element, props, ...children);
return domify(reactElement);
}
return null;
};
// Process mixed children (React elements or DOM elements)
const processChild = (child) => {
// If it's a function, call it and process the result
if (typeof child === 'function') {
const result = child();
return processChild(result);
}
// If it's an object with .element property (like ZikoJS UIElement)
if (
child &&
typeof child === 'object' &&
'element' in child &&
isDOMElement(child.element)
) return child.element;
// Use domify for all other cases (DOM elements, React elements, or React components)
return domify(child);
};
export function DOMWrapper({ children }) {
const containerRef = useRef(null);
const mountedRootsRef = useRef([]);
useEffect(() => {
if (!containerRef.current || !children) return;
// Cleanup previous React roots
mountedRootsRef.current.forEach((root) => {
if (root?.unmount) {
root.unmount();
}
});
mountedRootsRef.current = [];
containerRef.current.innerHTML = '';
const childArray = Array.isArray(children) ? children : [children];
childArray.forEach((child) => {
const processedElement = processChild(child);
if (processedElement instanceof HTMLElement) {
containerRef.current.appendChild(processedElement);
// Track React roots for cleanup
if (processedElement._reactRoot) {
mountedRootsRef.current.push(processedElement._reactRoot);
}
}
});
// Cleanup function
return () => {
mountedRootsRef.current.forEach((root) => {
if (root?.unmount) {
root.unmount();
}
});
mountedRootsRef.current = [];
};
}, [children]);
return <div ref={containerRef} style={{ display: 'contents' }} />;
}
// Pure DOM element creators
const createButton = (text, onClick) => {
const btn = document.createElement('button');
btn.textContent = text;
btn.style.cssText =
'padding: 8px 16px; margin: 5px; background: #4CAF50; color: white; border: none; cursor: pointer; border-radius: 4px;';
if (onClick) btn.onclick = onClick;
return btn;
};
const createParagraph = (text) => {
const p = document.createElement('p');
p.textContent = text;
p.style.margin = '10px 0';
return p;
};
const createCard = (title, ...children) => {
const card = document.createElement('div');
card.style.cssText =
'padding: 15px; border: 2px solid #4CAF50; margin: 10px; border-radius: 8px; background: #f0f0f0;';
const titleEl = document.createElement('h4');
titleEl.textContent = title;
titleEl.style.marginTop = '0';
card.appendChild(titleEl);
children.forEach((child) => {
const element = domify(child);
if (element) {
card.appendChild(element);
}
});
return card;
};
// You can also use domify directly with React components
const createReactButton = (onClick, ...children) => {
return domify(ReactButton, { onClick }, ...children);
};
// React component
function ReactCard({ title, children, color = '#2196F3' }) {
return (
<div
style={{
padding: '15px',
border: `2px solid ${color}`,
margin: '10px',
borderRadius: '8px',
background: color === '#2196F3' ? '#e3f2fd' : '#fff3e0',
}}
>
<h4 style={{ marginTop: 0 }}>{title}</h4>
{children}
</div>
);
}
function ReactButton({ children, onClick }) {
return (
<button
onClick={onClick}
style={{
padding: '8px 16px',
margin: '5px',
background: '#2196F3',
color: 'white',
border: 'none',
cursor: 'pointer',
borderRadius: '4px',
}}
>
{children}
</button>
);
}
// Demo
export default function Demo() {
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h2>DOMWrapper - Interleaving Demo (Refactored)</h2>
<p style={{ color: '#666' }}>
Seamlessly mix DOM elements and React components with domify()
</p>
<div style={{ marginTop: '20px' }}>
<h3>Example 1: Simple DOM Elements</h3>
<DOMWrapper>
{createParagraph('This is a pure DOM paragraph')}
{createButton('DOM Button', () => alert('DOM button clicked!'))}
{createReactButton(() => alert('domified'), 'Domified Button')}
</DOMWrapper>
</div>
<div style={{ marginTop: '20px' }}>
<h3>Example 2: React Components with Props & Children</h3>
<DOMWrapper>
{createCard(
'DOM Card with React Inside',
createParagraph('DOM paragraph'),
<ReactCard title="Props Work!" color="#FF9800">
<p>This ReactCard has props: title and color</p>
<ReactButton onClick={() => alert('Children work too!')}>
Click Me - I'm a child!
</ReactButton>
</ReactCard>,
<ReactCard title="Multiple React Components">
<p>You can domify multiple React components</p>
<p>Each with their own props and children</p>
</ReactCard>
)}
</DOMWrapper>
</div>
<div style={{ marginTop: '20px' }}>
<h3>Example 3: DOM → React</h3>
<DOMWrapper>
{createCard(
'DOM Card',
createParagraph('This is DOM content'),
<ReactCard title="Nested React Card">
<p>This is React content inside a DOM element!</p>
<ReactButton onClick={() => alert('React button!')}>
React Button
</ReactButton>
</ReactCard>
)}
</DOMWrapper>
</div>
<div style={{ marginTop: '20px' }}>
<h3>Example 4: React → DOM</h3>
<DOMWrapper>
<ReactCard title="React Card" color="#FF9800">
<p>React component here</p>
<DOMWrapper>
{createParagraph('DOM content inside React!')}
{createButton('DOM Button Inside React', () => alert('Works!'))}
</DOMWrapper>
</ReactCard>
</DOMWrapper>
</div>
<div style={{ marginTop: '20px' }}>
<h3>Example 5: Deep Nesting (DOM → React → DOM → React)</h3>
<DOMWrapper>
{createCard(
'Level 1: DOM',
createParagraph('DOM content'),
<ReactCard title="Level 2: React">
<p>React component</p>
<DOMWrapper>
{createCard(
'Level 3: DOM',
createParagraph('Back to DOM!'),
<ReactCard title="Level 4: React" color="#FF9800">
<p>🎉 Full interleaving works!</p>
<ReactButton onClick={() => alert('Deep nested button!')}>
Click Me
</ReactButton>
</ReactCard>
)}
</DOMWrapper>
</ReactCard>
)}
</DOMWrapper>
</div>
<div style={{ marginTop: '20px' }}>
<h3>Example 6: Mixed Array</h3>
<DOMWrapper>
{[
createParagraph('First DOM element'),
<ReactCard title="React in the middle">
<DOMWrapper>
{createButton('Nested DOM Button', () => alert('Nested!'))}
</DOMWrapper>
</ReactCard>,
createParagraph('Last DOM element'),
]}
</DOMWrapper>
</div>
</div>
);
}