UNPKG

juris

Version:

JavaScript Unified Reactive Interface Solution - Transforms web development through comprehensive object-first architecture that makes reactivity an intentional choice rather than automatic behavior

1,319 lines (1,101 loc) 111 kB
/** * Caution: This is not just a framework, its a paradigm-shifting platform. * Juris (JavaScript Unified Reactive Interface Solution) * Transforms web development through its comprehensive object-first architecture that makes * reactivity an intentional choice rather than an automatic behavior. By expressing interfaces * as pure JavaScript objects where functions explicitly define reactivity, Juris delivers a * complete solution for applications that are universally deployable, precisely controlled, * and designed from the ground up for seamless AI collaboration—all while maintaining the * simplicity and debuggability of native JavaScript patterns. * * Author: Resti Guay * Maintained by: Juris Github Team * Version: 0.5.2 (stable) * License: MIT * GitHub: https://github.com/jurisjs/juris * Website: https://jurisjs.com * Documentation: https://jurisjs.com/#docs */ (function () { 'use strict'; /** * Utility functions */ function isValidPath(path) { return typeof path === 'string' && path.trim().length > 0 && !path.includes('..'); } function getPathParts(path) { return path.split('.').filter(Boolean); } function deepEquals(a, b) { if (a === b) return true; if (a == null || b == null) return false; if (typeof a !== typeof b) return false; if (typeof a === 'object') { if (Array.isArray(a) !== Array.isArray(b)) return false; const keysA = Object.keys(a); const keysB = Object.keys(b); if (keysA.length !== keysB.length) return false; for (let key of keysA) { if (!keysB.includes(key) || !deepEquals(a[key], b[key])) { return false; } } return true; } return false; } /** * State Manager - Handles reactive state with middleware support */ class StateManager { constructor(initialState = {}, middleware = []) { this.state = { ...initialState }; this.middleware = [...middleware]; this.subscribers = new Map(); this.externalSubscribers = new Map(); this.currentTracking = null; this.isUpdating = false; // Batch update system this.updateQueue = []; this.batchTimeout = null; this.batchUpdateInProgress = false; this.maxBatchSize = 50; this.batchDelayMs = 0; // ✅ FIXED: Non-zero default for batching this.batchingEnabled = true; // ✅ FIXED: Add batching control this.initialState = JSON.parse(JSON.stringify(initialState)); } reset(preserve = []) { // Collect preserved values const preserved = {}; preserve.forEach(path => { const value = this.getState(path); if (value !== null) { preserved[path] = value; } }); // Clear current state this.state = {}; // Restore initial state Object.entries(this.initialState).forEach(([path, value]) => { this.setState(path, JSON.parse(JSON.stringify(value))); }); // Restore preserved values Object.entries(preserved).forEach(([path, value]) => { this.setState(path, value); }); } getState(path, defaultValue = null) { if (!isValidPath(path)) { console.warn('Invalid state path:', path); return defaultValue; } if (this.currentTracking) { this.currentTracking.add(path); } const parts = getPathParts(path); let current = this.state; for (const part of parts) { if (current == null || current[part] === undefined) { return defaultValue; } current = current[part]; } return current; } setState(path, value, context = {}) { if (!isValidPath(path)) { console.warn('Invalid state path:', path); return; } if (this._hasCircularUpdate(path)) { return; } // Route to batching if enabled if (this.batchingEnabled && this.batchDelayMs > 0) { this._queueUpdate(path, value, context); return; } this._setStateImmediate(path, value, context); } _setStateImmediate(path, value, context = {}) { const oldValue = this.getState(path); let finalValue = value; for (const middleware of this.middleware) { try { const result = middleware({ path, oldValue, newValue: finalValue, context, state: this.state }); if (result !== undefined) { finalValue = result; } } catch (error) { console.error('Middleware error:', error); } } if (deepEquals(oldValue, finalValue)) { return; } const parts = getPathParts(path); let current = this.state; for (let i = 0; i < parts.length - 1; i++) { const part = parts[i]; if (current[part] == null || typeof current[part] !== 'object') { current[part] = {}; } current = current[part]; } current[parts[parts.length - 1]] = finalValue; if (!this.isUpdating) { this.isUpdating = true; if (!this.currentlyUpdating) { this.currentlyUpdating = new Set(); } this.currentlyUpdating.add(path); this._notifySubscribers(path, finalValue, oldValue); this._notifyExternalSubscribers(path, finalValue, oldValue); // ✅ Now hierarchical this.currentlyUpdating.delete(path); this.isUpdating = false; } } _processBatchedUpdates() { if (this.batchUpdateInProgress || this.updateQueue.length === 0) { return; } this.batchUpdateInProgress = true; if (this.batchTimeout) { clearTimeout(this.batchTimeout); this.batchTimeout = null; } const batchSize = Math.min(this.maxBatchSize, this.updateQueue.length); const currentBatch = this.updateQueue.splice(0, batchSize); try { // ✅ FIXED: Group updates by path and take latest value const pathGroups = new Map(); currentBatch.forEach(update => { pathGroups.set(update.path, update); // Latest update wins }); // ✅ FIXED: Process unique paths only const affectedPaths = new Set(); pathGroups.forEach((update) => { this._setStateImmediate(update.path, update.value, update.context); affectedPaths.add(update.path); }); console.log(`Batched ${currentBatch.length} updates into ${pathGroups.size} unique state changes`); } catch (error) { console.error('Error processing batched updates:', error); } finally { this.batchUpdateInProgress = false; if (this.updateQueue.length > 0) { setTimeout(() => this._processBatchedUpdates(), 0); } } } configureBatching(options = {}) { this.maxBatchSize = options.maxBatchSize || this.maxBatchSize; this.batchDelayMs = options.batchDelayMs !== undefined ? options.batchDelayMs : this.batchDelayMs; if (options.enabled !== undefined) { this.batchingEnabled = options.enabled; } console.log(`Batching configured: enabled=${this.batchingEnabled}, delay=${this.batchDelayMs}ms, maxSize=${this.maxBatchSize}`); } getBatchStatus() { return { enabled: this.batchingEnabled, queueLength: this.updateQueue.length, inProgress: this.batchUpdateInProgress, hasTimeout: !!this.batchTimeout, delayMs: this.batchDelayMs, maxBatchSize: this.maxBatchSize }; } _queueUpdate(path, value, context) { this.updateQueue.push({ path, value, context, timestamp: Date.now() }); if (this.updateQueue.length > this.maxBatchSize * 2) { console.warn('Update queue is getting large, processing immediately'); this._processBatchedUpdates(); return; } if (!this.batchTimeout) { this.batchTimeout = setTimeout(() => { this._processBatchedUpdates(); }, this.batchDelayMs); } } subscribe(path, callback, hierarchical = true) { if (!this.externalSubscribers.has(path)) { this.externalSubscribers.set(path, new Set()); } // Store callback with hierarchical flag const subscription = { callback, hierarchical }; this.externalSubscribers.get(path).add(subscription); return () => { const subs = this.externalSubscribers.get(path); if (subs) { subs.delete(subscription); if (subs.size === 0) { this.externalSubscribers.delete(path); } } }; } subscribeExact(path, callback) { return this.subscribe(path, callback, false); } subscribeInternal(path, callback) { if (!this.subscribers.has(path)) { this.subscribers.set(path, new Set()); } this.subscribers.get(path).add(callback); return () => { const subs = this.subscribers.get(path); if (subs) { subs.delete(callback); if (subs.size === 0) { this.subscribers.delete(path); } } }; } _notifySubscribers(path, newValue, oldValue) { this._triggerPathSubscribers(path); const parts = getPathParts(path); for (let i = parts.length - 1; i > 0; i--) { const parentPath = parts.slice(0, i).join('.'); this._triggerPathSubscribers(parentPath); } const prefix = path ? path + '.' : ''; const allSubscriberPaths = new Set([ ...this.subscribers.keys(), ...this.externalSubscribers.keys() ]); allSubscriberPaths.forEach(subscriberPath => { if (subscriberPath.startsWith(prefix) && subscriberPath !== path) { this._triggerPathSubscribers(subscriberPath); } }); } _notifyExternalSubscribers(changedPath, newValue, oldValue) { // Check all external subscribers this.externalSubscribers.forEach((subscriptions, subscribedPath) => { subscriptions.forEach(subscription => { const { callback, hierarchical } = subscription; let shouldNotify = false; if (hierarchical) { // Hierarchical: notify if changedPath is under subscribedPath if (changedPath === subscribedPath || changedPath.startsWith(subscribedPath + '.')) { shouldNotify = true; } } else { // Exact: notify only if paths match exactly if (changedPath === subscribedPath) { shouldNotify = true; } } if (shouldNotify) { try { callback(newValue, oldValue, changedPath); } catch (error) { console.error('External subscriber error:', error); } } }); }); } _triggerPathSubscribers(path) { const subs = this.subscribers.get(path); if (subs) { const subscribersCopy = new Set(subs); subscribersCopy.forEach(callback => { try { const oldTracking = this.currentTracking; const newTracking = new Set(); this.currentTracking = newTracking; callback(); this.currentTracking = oldTracking; newTracking.forEach(newPath => { const existingSubs = this.subscribers.get(newPath); if (!existingSubs || !existingSubs.has(callback)) { this.subscribeInternal(newPath, callback); } }); } catch (error) { console.error('Subscriber error:', error); this.currentTracking = oldTracking; } }); } } _hasCircularUpdate(path) { if (!this.currentlyUpdating) { this.currentlyUpdating = new Set(); } if (this.currentlyUpdating.has(path)) { console.warn(`Circular dependency detected for path: ${path}`); return true; } return false; } startTracking() { const dependencies = new Set(); this.currentTracking = dependencies; return dependencies; } endTracking() { const tracking = this.currentTracking; this.currentTracking = null; return tracking || new Set(); } } /** * Headless Manager - Enhanced with better lifecycle management */ class HeadlessManager { constructor(juris) { this.juris = juris; this.components = new Map(); this.instances = new Map(); this.context = {}; this.initQueue = new Set(); this.lifecycleHooks = new Map(); } register(name, componentFn, options = {}) { this.components.set(name, { fn: componentFn, options }); if (options.autoInit) { this.initQueue.add(name); } } initialize(name, props = {}) { const component = this.components.get(name); if (!component) { console.warn(`Headless component '${name}' not found`); return null; } try { const context = this.juris.createHeadlessContext(); const instance = component.fn(props, context); if (!instance || typeof instance !== 'object') { console.warn(`Headless component '${name}' must return an object`); return null; } this.instances.set(name, instance); if (instance.hooks) { this.lifecycleHooks.set(name, instance.hooks); } if (instance.api) { this.context[name] = instance.api; if (!this.juris.headlessAPIs) { this.juris.headlessAPIs = {}; } this.juris.headlessAPIs[name] = instance.api; this.juris._updateComponentContexts(); } if (instance.hooks?.onRegister) { try { instance.hooks.onRegister(); } catch (error) { console.error(`Error in onRegister for headless component '${name}':`, error); } } return instance; } catch (error) { console.error(`Error initializing headless component '${name}':`, error); return null; } } initializeQueued() { this.initQueue.forEach(name => { if (!this.instances.has(name)) { const component = this.components.get(name); this.initialize(name, component.options || {}); } }); this.initQueue.clear(); } getInstance(name) { return this.instances.get(name); } getAPI(name) { return this.context[name]; } getAllAPIs() { return { ...this.context }; } reinitialize(name, props = {}) { if (this.instances.has(name)) { const instance = this.instances.get(name); if (instance.hooks?.onUnregister) { try { instance.hooks.onUnregister(); } catch (error) { console.error(`Error in onUnregister for '${name}':`, error); } } } if (this.context[name]) { delete this.context[name]; } if (this.juris.headlessAPIs?.[name]) { delete this.juris.headlessAPIs[name]; } this.instances.delete(name); this.lifecycleHooks.delete(name); return this.initialize(name, props); } cleanup() { this.instances.forEach((instance, name) => { if (instance.hooks?.onUnregister) { try { instance.hooks.onUnregister(); } catch (error) { console.error(`Error in onUnregister for '${name}':`, error); } } }); this.instances.clear(); this.context = {}; this.lifecycleHooks.clear(); if (this.juris.headlessAPIs) { this.juris.headlessAPIs = {}; } } getStatus() { return { registered: Array.from(this.components.keys()), initialized: Array.from(this.instances.keys()), queued: Array.from(this.initQueue), apis: Object.keys(this.context) }; } } /** * Component Manager - Handles UI components with lifecycle */ class ComponentManager { constructor(juris) { this.juris = juris; this.components = new Map(); this.instances = new WeakMap(); this.componentCounters = new Map(); // Track instances per component name this.componentStates = new WeakMap(); } register(name, componentFn) { this.components.set(name, componentFn); } create(name, props = {}) { const componentFn = this.components.get(name); if (!componentFn) { console.error(`Component '${name}' not found`); return null; } try { if (!this.componentCounters.has(name)) { this.componentCounters.set(name, 0); } const currentCount = this.componentCounters.get(name); const instanceIndex = currentCount + 1; this.componentCounters.set(name, instanceIndex); const componentId = `${name}_${instanceIndex}`; const componentStates = new Set(); const context = this.juris.createContext(); // Add newState function to context context.newState = (key, initialValue) => { const statePath = `__local.${componentId}.${key}`; // Set initial value if not exists if (this.juris.stateManager.getState(statePath, Symbol('not-found')) === Symbol('not-found')) { this.juris.stateManager.setState(statePath, initialValue); } // Track this state for cleanup componentStates.add(statePath); const getter = () => this.juris.stateManager.getState(statePath, initialValue); const setter = (value) => this.juris.stateManager.setState(statePath, value); return [getter, setter]; }; const result = componentFn(props, context); if (result && typeof result === 'object') { // Check for lifecycle component first if (result.onMount || result.onUpdate || result.onUnmount || (typeof result.render === 'function' && (result.onMount !== undefined || result.onUpdate !== undefined || result.onUnmount !== undefined))) { return this._createLifecycleComponent(result, name, props, componentStates); } // Check for render function pattern if (typeof result.render === 'function' && !result.onMount && !result.onUpdate && !result.onUnmount) { const renderResult = result.render(); console.log(`Render function for '${name}' returned:`, renderResult); const element = this.juris.domRenderer.render(renderResult); if (element && componentStates.size > 0) { this.componentStates.set(element, componentStates); } return element; } // Direct VDOM return - check if it has valid tag names const keys = Object.keys(result); if (keys.length === 1) { const tagName = keys[0]; // Valid HTML tag names or registered components if (typeof tagName === 'string' && tagName.length > 0) { const element = this.juris.domRenderer.render(result); if (element && componentStates.size > 0) { this.componentStates.set(element, componentStates); } return element; } } } // Fallback console.warn(`Component '${name}' returned unexpected structure, attempting to render:`, result); const element = this.juris.domRenderer.render(result); if (element && componentStates.size > 0) { this.componentStates.set(element, componentStates); } return element; } catch (error) { console.error(`Error creating component '${name}':`, error); return this._createErrorElement(error); } } // Helper method to detect VDOM structure _isVDOMStructure(obj) { if (!obj || typeof obj !== 'object') return false; const keys = Object.keys(obj); if (keys.length !== 1) return false; const tagName = keys[0]; // Check if it's a valid HTML tag name or component name return typeof tagName === 'string' && (this.juris.componentManager.components.has(tagName) || /^[a-zA-Z][a-zA-Z0-9-]*$/.test(tagName)); } _createLifecycleComponent(componentResult, name, props, componentStates) { const instance = { name, props, hooks: componentResult.hooks || {}, api: componentResult.api || {}, render: componentResult.render }; const element = this.juris.domRenderer.render(instance.render()); if (element) { this.instances.set(element, instance); // Store component states for cleanup if (componentStates && componentStates.size > 0) { this.componentStates.set(element, componentStates); } if (instance.hooks.onMount) { setTimeout(() => { if (element.isConnected) { try { instance.hooks.onMount(); } catch (error) { console.error(`onMount error in ${name}:`, error); } } }, 0); } } return element; } updateInstance1(element, newProps) { const instance = this.instances.get(element); if (!instance) return; const oldProps = instance.props; // ✅ NEW: Check if props have actually changed using deep comparison if (deepEquals(oldProps, newProps)) { console.log(`Skipping re-render for ${instance.name} - props unchanged`); return; // Skip re-render if props are identical } instance.props = newProps; if (instance.hooks.onUpdate) { try { instance.hooks.onUpdate(oldProps, newProps); } catch (error) { console.error(`onUpdate error in ${instance.name}:`, error); } } try { const newContent = instance.render(); this.juris.domRenderer.updateElementContent(element, newContent); } catch (error) { console.error(`Re-render error in ${instance.name}:`, error); } } cleanup(element) { const instance = this.instances.get(element); if (instance && instance.hooks.onUnmount) { try { instance.hooks.onUnmount(); } catch (error) { console.error(`onUnmount error in ${instance.name}:`, error); } } // Cleanup component local states const states = this.componentStates.get(element); if (states) { states.forEach(statePath => { // Remove from global state const pathParts = statePath.split('.'); let current = this.juris.stateManager.state; for (let i = 0; i < pathParts.length - 1; i++) { if (current[pathParts[i]]) { current = current[pathParts[i]]; } else { return; // Path doesn't exist } } delete current[pathParts[pathParts.length - 1]]; }); this.componentStates.delete(element); } this.instances.delete(element); } _createErrorElement(error) { const element = document.createElement('div'); element.style.cssText = 'color: red; border: 1px solid red; padding: 8px; background: #ffe6e6;'; element.textContent = `Component Error: ${error.message}`; return element; } } /** * OPTIMIZED DOM Renderer with renderMode support */ class DOMRenderer { constructor(juris) { this.juris = juris; this.subscriptions = new WeakMap(); this.eventMap = { ondoubleclick: 'dblclick', onmousedown: 'mousedown', onmouseup: 'mouseup', onmouseover: 'mouseover', onmouseout: 'mouseout', onmousemove: 'mousemove', onkeydown: 'keydown', onkeyup: 'keyup', onkeypress: 'keypress', onfocus: 'focus', onblur: 'blur', onchange: 'change', oninput: 'input', onsubmit: 'submit', onload: 'load', onresize: 'resize', onscroll: 'scroll' }; // VDOM-style optimizations this.elementCache = new Map(); this.recyclePool = new Map(); this.renderQueue = []; this.isRendering = false; this.scheduledRender = null; // Performance settings this.batchSize = 20; this.recyclePoolSize = 100; // RENDER MODE: Choose between fine-grained and batch rendering this.renderMode = 'fine-grained'; // 'batch' or 'fine-grained' this.failureCount = 0; this.maxFailures = 3; this.renderStats = { totalUpdates: 0, skippedUpdates: 0, lastReset: Date.now() }; } getRenderStats() { const now = Date.now(); const duration = (now - this.renderStats.lastReset) / 1000; const skipRate = this.renderStats.totalUpdates > 0 ? (this.renderStats.skippedUpdates / this.renderStats.totalUpdates * 100).toFixed(1) : 0; return { totalUpdates: this.renderStats.totalUpdates, skippedUpdates: this.renderStats.skippedUpdates, skipRate: `${skipRate}%`, duration: `${duration.toFixed(1)}s` }; } resetRenderStats() { this.renderStats = { totalUpdates: 0, skippedUpdates: 0, lastReset: Date.now() }; } // PUBLIC: Set render mode setRenderMode(mode) { if (mode === 'fine-grained' || mode === 'batch') { this.renderMode = mode; console.log(`Juris: Render mode set to '${mode}'`); if (mode === 'fine-grained') { console.log(' → Using direct DOM updates (more compatible)'); } else { console.log(' → Using VDOM-style reconciliation (higher performance)'); } } else { console.warn(`Invalid render mode '${mode}'. Use 'fine-grained' or 'batch'`); } } getRenderMode() { return this.renderMode; } isFineGrained() { return this.renderMode === 'fine-grained'; } isBatchMode() { return this.renderMode === 'batch'; } // DEPRECATED: Legacy method names for backward compatibility setLegacyMode(enabled) { console.warn('setLegacyMode() is deprecated. Use setRenderMode() instead.'); this.setRenderMode(enabled ? 'fine-grained' : 'batch'); } isLegacyMode() { console.warn('isLegacyMode() is deprecated. Use isFineGrained() instead.'); return this.isFineGrained(); } render(vnode) { if (!vnode || typeof vnode !== 'object') { return null; } // ✅ NEW: Handle arrays of vnodes if (Array.isArray(vnode)) { //console.log('DOMRenderer.render received array:', vnode); const fragment = document.createDocumentFragment(); vnode.forEach(child => { const childElement = this.render(child); if (childElement) { fragment.appendChild(childElement); } }); return fragment; } // Debug log //console.log('DOMRenderer.render received:', vnode); const tagName = Object.keys(vnode)[0]; const props = vnode[tagName] || {}; // Debug log //console.log('Extracted tagName:', tagName, 'type:', typeof tagName); // Check if it's a registered component if (this.juris.componentManager.components.has(tagName)) { const parentTracking = this.juris.stateManager.currentTracking; this.juris.stateManager.currentTracking = null; const result = this.juris.componentManager.create(tagName, props); this.juris.stateManager.currentTracking = parentTracking; return result; } // Validate tagName before creating element if (typeof tagName !== 'string' || tagName.length === 0) { console.error('Invalid tagName:', tagName, 'from vnode:', vnode); return null; } // FINE-GRAINED MODE: Use direct DOM updates if (this.renderMode === 'fine-grained') { return this._createElementFineGrained(tagName, props); } // BATCH MODE: Try optimized reconciliation with automatic fallback try { const key = props.key || this._generateKey(tagName, props); const cachedElement = this.elementCache.get(key); if (cachedElement && this._canReuseElement(cachedElement, tagName, props)) { this._updateElementProperties(cachedElement, props); return cachedElement; } return this._createElementOptimized(tagName, props, key); } catch (error) { console.warn('Batch rendering failed, falling back to fine-grained mode:', error.message); this.failureCount++; if (this.failureCount >= this.maxFailures) { console.log('Too many batch failures, switching to fine-grained mode permanently'); this.renderMode = 'fine-grained'; } return this._createElementFineGrained(tagName, props); } } // FINE-GRAINED: Direct DOM manipulation method _createElementFineGrained(tagName, props) { // Debug logging to catch the issue /*console.log('_createElementFineGrained called with:', { tagName: tagName, tagNameType: typeof tagName, props: props });*/ // Validate inputs if (typeof tagName !== 'string') { console.error('Invalid tagName in _createElementFineGrained:', tagName); return null; } const element = document.createElement(tagName); const subscriptions = []; const eventListeners = []; Object.keys(props).forEach(key => { const value = props[key]; if (key === 'children') { this._handleChildrenFineGrained(element, value, subscriptions); } else if (key === 'text') { this._handleText(element, value, subscriptions); } else if (key === 'style') { this._handleStyleFineGrained(element, value, subscriptions); } else if (key.startsWith('on')) { this._handleEvent(element, key, value, eventListeners); } else if (typeof value === 'function') { this._handleReactiveAttribute(element, key, value, subscriptions); } else if (key !== 'key') { this._setStaticAttribute(element, key, value); } }); if (subscriptions.length > 0 || eventListeners.length > 0) { this.subscriptions.set(element, { subscriptions, eventListeners }); } return element; } _handleChildrenFineGrained(element, children, subscriptions) { if (typeof children === 'function') { let lastChildrenResult = null; let isInitialized = false; const updateChildren = () => { try { const result = children(); // Only update if children result has actually changed if (isInitialized && deepEquals(result, lastChildrenResult)) { return; // Skip update if children haven't changed } if (result !== "ignore") { this._updateChildrenFineGrained(element, result); lastChildrenResult = result; isInitialized = true; } } catch (error) { console.error('Error in children function:', error); } }; // Set up reactive subscription this._createReactiveUpdate(element, updateChildren, subscriptions); // Initial render - call updateChildren directly instead of duplicating children() call //updateChildren(); // To be removed when all test is } else { this._updateChildrenFineGrained(element, children); } } _updateChildrenFineGrained(element, children) { if (children === "ignore") { return; } const childrenToRemove = Array.from(element.children); childrenToRemove.forEach(child => { this.cleanup(child); }); element.textContent = ''; const fragment = document.createDocumentFragment(); if (Array.isArray(children)) { children.forEach(child => { const childElement = this.render(child); if (childElement) { fragment.appendChild(childElement); } }); } else if (children) { const childElement = this.render(children); if (childElement) { fragment.appendChild(childElement); } } if (fragment.hasChildNodes()) { element.appendChild(fragment); } } _handleStyleFineGrained(element, style, subscriptions) { if (typeof style === 'function') { let lastStyleValue = null; let isInitialized = false; const updateStyle = () => { const styleObj = style(); // Only update if style has actually changed if (isInitialized && deepEquals(styleObj, lastStyleValue)) { return; // Skip update if style hasn't changed } if (typeof styleObj === 'object') { Object.assign(element.style, styleObj); lastStyleValue = { ...styleObj }; isInitialized = true; } }; // Set up reactive subscription this._createReactiveUpdate(element, updateStyle, subscriptions); // Initial render - call updateStyle directly instead of duplicating style() call //updateStyle(); } else if (typeof style === 'object') { Object.assign(element.style, style); } } // BATCH MODE: Optimized element creation _createElementOptimized(tagName, props, key) { let element = this._getRecycledElement(tagName); if (!element) { element = document.createElement(tagName); } if (key) { this.elementCache.set(key, element); element._jurisKey = key; } const subscriptions = []; const eventListeners = []; this._processProperties(element, props, subscriptions, eventListeners); if (subscriptions.length > 0 || eventListeners.length > 0) { this.subscriptions.set(element, { subscriptions, eventListeners }); } return element; } _processProperties(element, props, subscriptions, eventListeners) { Object.keys(props).forEach(key => { const value = props[key]; if (key === 'children') { this._handleChildrenOptimized(element, value, subscriptions); } else if (key === 'text') { this._handleText(element, value, subscriptions); } else if (key === 'innerHTML') { if (typeof value === 'function') { this._handleReactiveAttribute(element, key, value, subscriptions); } else { element.innerHTML = value; } } else if (key === 'style') { this._handleStyle(element, value, subscriptions); } else if (key.startsWith('on')) { this._handleEvent(element, key, value, eventListeners); } else if (typeof value === 'function') { this._handleReactiveAttribute(element, key, value, subscriptions); } else if (key !== 'key') { this._setStaticAttribute(element, key, value); } }); } _handleChildrenOptimized(element, children, subscriptions) { if (typeof children === 'function') { let lastChildrenState = null; let childElements = []; let useOptimizedPath = true; const updateChildren = () => { try { const newChildren = children(); if (newChildren !== "ignore" && !this._childrenEqual(lastChildrenState, newChildren)) { if (useOptimizedPath) { try { childElements = this._reconcileChildren(element, childElements, newChildren); lastChildrenState = newChildren; } catch (error) { console.warn('Reconciliation failed, falling back to safe rendering:', error.message); useOptimizedPath = false; this._updateChildrenSafe(element, newChildren); lastChildrenState = newChildren; } } else { this._updateChildrenSafe(element, newChildren); lastChildrenState = newChildren; } } } catch (error) { console.error('Error in children function:', error); useOptimizedPath = false; try { this._updateChildrenSafe(element, []); } catch (fallbackError) { console.error('Even safe fallback failed:', fallbackError); } } }; this._createReactiveUpdate(element, updateChildren, subscriptions); try { const initialChildren = children(); childElements = this._reconcileChildren(element, [], initialChildren); lastChildrenState = initialChildren; } catch (error) { console.warn('Initial reconciliation failed, using safe method:', error.message); useOptimizedPath = false; const initialChildren = children(); this._updateChildrenSafe(element, initialChildren); lastChildrenState = initialChildren; } } else { try { this._reconcileChildren(element, [], children); } catch (error) { console.warn('Static reconciliation failed, using safe method:', error.message); this._updateChildrenSafe(element, children); } } } _updateChildrenSafe(element, children) { if (children === "ignore") { return; } const childrenToRemove = Array.from(element.children); childrenToRemove.forEach(child => { try { this.cleanup(child); } catch (error) { console.warn('Error cleaning up child:', error); } }); element.textContent = ''; const fragment = document.createDocumentFragment(); if (Array.isArray(children)) { children.forEach(child => { try { const childElement = this.render(child); if (childElement && childElement !== element) { fragment.appendChild(childElement); } } catch (error) { console.warn('Error rendering child:', error); } }); } else if (children) { try { const childElement = this.render(children); if (childElement && childElement !== element) { fragment.appendChild(childElement); } } catch (error) { console.warn('Error rendering single child:', error); } } try { if (fragment.hasChildNodes()) { element.appendChild(fragment); } } catch (error) { console.error('Failed to append fragment, trying individual children:', error); Array.from(fragment.children).forEach(child => { try { if (child && child !== element) { element.appendChild(child); } } catch (individualError) { console.warn('Failed to append individual child:', individualError); } }); } } _reconcileChildren(parent, oldChildren, newChildren) { if (!Array.isArray(newChildren)) { newChildren = newChildren ? [newChildren] : []; } const newChildElements = []; const fragment = document.createDocumentFragment(); const oldChildrenByKey = new Map(); oldChildren.forEach((child, index) => { const key = child._jurisKey || `auto-${index}`; oldChildrenByKey.set(key, child); }); const usedElements = new Set(); newChildren.forEach((newChild, index) => { if (!newChild || typeof newChild !== 'object') return; const tagName = Object.keys(newChild)[0]; const props = newChild[tagName] || {}; const key = props.key || this._generateKey(tagName, props, index); const existingElement = oldChildrenByKey.get(key); if (existingElement && !usedElements.has(existingElement) &&