UNPKG

rynex

Version:

A minimalist TypeScript framework for building reactive web applications with no virtual DOM

256 lines 7.99 kB
/** * Rynex DOM Manipulation * Direct DOM element creation and manipulation (vanilla JavaScript) * No Virtual DOM - just real DOM elements */ import { debugLog } from './debug.js'; /** * Create a real DOM element (vanilla JavaScript) * This is the core function that replaces the h() virtual DOM function */ export function createElement(tag, props = null, ...children) { const element = document.createElement(tag); // Apply props if (props) { applyProps(element, props); } // Append children appendChildren(element, children); debugLog('DOM', `Created element: ${tag}`); return element; } /** * Create a text node */ export function createTextNode(text) { return document.createTextNode(String(text)); } /** * Apply properties to a DOM element */ export function applyProps(element, props) { for (const [key, value] of Object.entries(props)) { if (value === null || value === undefined) { continue; } // Handle event listeners if (key.startsWith('on') && typeof value === 'function') { const eventName = key.slice(2).toLowerCase(); element.addEventListener(eventName, value); debugLog('DOM', `Added event listener: ${eventName}`); } // Handle class/className else if (key === 'class' || key === 'className') { element.className = value; } // Handle style else if (key === 'style') { if (typeof value === 'string') { element.setAttribute('style', value); } else if (typeof value === 'object') { // Apply styles with proper camelCase to kebab-case conversion for (const [styleKey, styleValue] of Object.entries(value)) { if (styleValue !== null && styleValue !== undefined) { // Convert camelCase to kebab-case for CSS properties const cssKey = styleKey.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`); element.style[styleKey] = styleValue; } } } } // Handle hover styles else if (key === 'onHover' && typeof value === 'object') { const hoverStyles = value; element.addEventListener('mouseenter', () => { for (const [styleKey, styleValue] of Object.entries(hoverStyles)) { if (styleValue !== null && styleValue !== undefined) { element.style[styleKey] = styleValue; } } }); // Store original styles to restore on mouse leave const originalStyles = {}; for (const styleKey of Object.keys(hoverStyles)) { originalStyles[styleKey] = element.style[styleKey]; } element.addEventListener('mouseleave', () => { for (const [styleKey, styleValue] of Object.entries(originalStyles)) { element.style[styleKey] = styleValue; } }); } // Handle ref else if (key === 'ref' && typeof value === 'object' && 'current' in value) { value.current = element; } // Handle special properties else if (key === 'value') { element.value = value; } else if (key === 'checked') { element.checked = value; } // Handle data attributes else if (key.startsWith('data-')) { element.setAttribute(key, String(value)); } // Handle aria attributes else if (key.startsWith('aria-')) { element.setAttribute(key, String(value)); } // Handle other attributes else { element.setAttribute(key, String(value)); } } } /** * Update properties on a DOM element */ export function updateProps(element, oldProps, newProps) { // Remove old props for (const key in oldProps) { if (!(key in newProps)) { removeProp(element, key, oldProps[key]); } } // Add/update new props for (const key in newProps) { if (oldProps[key] !== newProps[key]) { // Remove old event listener if it's an event if (key.startsWith('on') && typeof oldProps[key] === 'function') { const eventName = key.slice(2).toLowerCase(); element.removeEventListener(eventName, oldProps[key]); } // Apply new prop applyProps(element, { [key]: newProps[key] }); } } } /** * Remove a property from a DOM element */ export function removeProp(element, key, value) { if (key.startsWith('on') && typeof value === 'function') { const eventName = key.slice(2).toLowerCase(); element.removeEventListener(eventName, value); } else if (key === 'class' || key === 'className') { element.className = ''; } else if (key === 'style') { element.removeAttribute('style'); } else if (key !== 'ref') { element.removeAttribute(key); } } /** * Append children to a DOM element */ export function appendChildren(parent, children) { const flatChildren = children.flat(Infinity); for (const child of flatChildren) { if (child === null || child === undefined || child === false || child === true) { continue; } if (typeof child === 'string' || typeof child === 'number') { parent.appendChild(createTextNode(child)); } else if (child instanceof HTMLElement || child instanceof SVGElement || child instanceof Text) { parent.appendChild(child); } } } /** * Replace all children of an element */ export function replaceChildren(parent, children) { // Clear existing children while (parent.firstChild) { parent.removeChild(parent.firstChild); } // Append new children appendChildren(parent, children); } /** * Mount an element to a container */ export function mount(element, container) { container.appendChild(element); debugLog('DOM', 'Mounted element to container'); } /** * Unmount an element from its parent */ export function unmount(element) { if (element.parentElement) { element.parentElement.removeChild(element); debugLog('DOM', 'Unmounted element'); } } /** * Create a ref object for accessing DOM elements */ export function createRef() { return { current: null }; } /** * Query selector helper */ export function $(selector, parent = document) { return parent.querySelector(selector); } /** * Query selector all helper */ export function $$(selector, parent = document) { return Array.from(parent.querySelectorAll(selector)); } /** * Add class to element */ export function addClass(element, ...classNames) { element.classList.add(...classNames); } /** * Remove class from element */ export function removeClass(element, ...classNames) { element.classList.remove(...classNames); } /** * Toggle class on element */ export function toggleClass(element, className, force) { element.classList.toggle(className, force); } /** * Set styles on element */ export function setStyle(element, styles) { Object.assign(element.style, styles); } /** * Set attributes on element */ export function setAttributes(element, attrs) { for (const [key, value] of Object.entries(attrs)) { element.setAttribute(key, value); } } /** * Add event listener helper */ export function on(element, event, handler, options) { element.addEventListener(event, handler, options); return () => element.removeEventListener(event, handler, options); } /** * Remove event listener helper */ export function off(element, event, handler) { element.removeEventListener(event, handler); } //# sourceMappingURL=dom.js.map