UNPKG

ziko-wrapper

Version:

integrate zikojs elements within other ui framework like vue react solidjs svelte astro ...

285 lines (252 loc) 8.83 kB
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> ); }