UNPKG

ziko-wrapper

Version:

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

286 lines (252 loc) 8.95 kB
import { useRef, useEffect } from 'preact/hooks'; import { render, createElement, isValidElement } from 'preact'; const isDOMElement = (obj) => obj instanceof HTMLElement; const isPreactElement = (child) => isValidElement(child); // Transform Preact 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 Preact element, bridge it and return the DOM container if (isPreactElement(element)) { const container = document.createElement('div'); container.style.display = 'contents'; render(element, container); // Mark for cleanup tracking container._preactRendered = true; return container; } // If it's a function (Preact component), create element with props and children if (typeof element === 'function') { const [props, ...children] = args; const preactElement = createElement(element, props, ...children); return domify(preactElement); } return null; }; // Process mixed children (Preact 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, Preact elements, or Preact components) return domify(child); }; export function DOMWrapper({ children }) { const containerRef = useRef(null); const mountedContainersRef = useRef([]); useEffect(() => { if (!containerRef.current || !children) return; // Cleanup previous Preact renders mountedContainersRef.current.forEach((container) => { if (container?._preactRendered) { render(null, container); // Unmount Preact components } }); mountedContainersRef.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 Preact-rendered containers for cleanup if (processedElement._preactRendered) { mountedContainersRef.current.push(processedElement); } } }); // Cleanup function return () => { mountedContainersRef.current.forEach((container) => { if (container?._preactRendered) { render(null, container); } }); mountedContainersRef.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 Preact components const createPreactButton = (onClick, ...children) => { return domify(PreactButton, { onClick }, ...children); }; // Preact component function PreactCard({ 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 PreactButton({ 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 function App() { return ( <div style={{ padding: '20px', fontFamily: 'sans-serif' }}> <h2>DOMWrapper - Preact Version</h2> <p style={{ color: '#666' }}> Seamlessly mix DOM elements and Preact 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!'))} {createPreactButton(() => alert('domified'), 'Domified Button')} </DOMWrapper> </div> <div style={{ marginTop: '20px' }}> <h3>Example 2: Preact Components with Props & Children</h3> <DOMWrapper> {CreateCard( 'DOM Card with Preact Inside', CreateParagraph('DOM paragraph'), <PreactCard title="Props Work!" color="#FF9800"> <p>This PreactCard has props: title and color</p> <PreactButton onClick={() => alert('Children work too!')}> Click Me - I'm a child! </PreactButton> </PreactCard>, <PreactCard title="Multiple Preact Components"> <p>You can domify multiple Preact components</p> <p>Each with their own props and children</p> </PreactCard> )} </DOMWrapper> </div> <div style={{ marginTop: '20px' }}> <h3>Example 3: DOM → Preact</h3> <DOMWrapper> {CreateCard( 'DOM Card', CreateParagraph('This is DOM content'), <PreactCard title="Nested Preact Card"> <p>This is Preact content inside a DOM element!</p> <PreactButton onClick={() => alert('Preact button!')}> Preact Button </PreactButton> </PreactCard> )} </DOMWrapper> </div> <div style={{ marginTop: '20px' }}> <h3>Example 4: Preact → DOM</h3> <DOMWrapper> <PreactCard title="Preact Card" color="#FF9800"> <p>Preact component here</p> <DOMWrapper> {CreateParagraph('DOM content inside Preact!')} {CreateButton('DOM Button Inside Preact', () => alert('Works!'))} </DOMWrapper> </PreactCard> </DOMWrapper> </div> <div style={{ marginTop: '20px' }}> <h3>Example 5: Deep Nesting (DOM → Preact → DOM → Preact)</h3> <DOMWrapper> {CreateCard( 'Level 1: DOM', CreateParagraph('DOM content'), <PreactCard title="Level 2: Preact"> <p>Preact component</p> <DOMWrapper> {CreateCard( 'Level 3: DOM', CreateParagraph('Back to DOM!'), <PreactCard title="Level 4: Preact" color="#FF9800"> <p>🎉 Full interleaving works!</p> <PreactButton onClick={() => alert('Deep nested button!')}> Click Me </PreactButton> </PreactCard> )} </DOMWrapper> </PreactCard> )} </DOMWrapper> </div> <div style={{ marginTop: '20px' }}> <h3>Example 6: Mixed Array</h3> <DOMWrapper> {[ CreateParagraph('First DOM element'), <PreactCard title="Preact in the middle"> <DOMWrapper> {CreateButton('Nested DOM Button', () => alert('Nested!'))} </DOMWrapper> </PreactCard>, CreateParagraph('Last DOM element'), ]} </DOMWrapper> </div> </div> ); }