UNPKG

@kalxjs/core

Version:

A modern JavaScript framework for building user interfaces with reactive state, composition API, and built-in performance optimizations

1,520 lines (1,338 loc) 482 kB
import * as fs from 'fs'; import * as path from 'path'; import { Readable } from 'stream'; // packages/core/src/reactivity/reactive.js // Current active effect let activeEffect = null; /** * Creates a reactive object * @param {Object} target - Object to make reactive * @returns {Proxy} Reactive object */ function reactive$1(target) { const handlers = { get(target, key, receiver) { const result = Reflect.get(target, key, receiver); track(target, key); return result; }, set(target, key, value, receiver) { const oldValue = target[key]; const result = Reflect.set(target, key, value, receiver); if (oldValue !== value) { trigger(target, key); } return result; } }; return new Proxy(target, handlers); } /** * Creates a reactive reference * @param {any} value - Initial value * @returns {Object} Reactive reference */ function ref$1(value) { const r = { _value: value, get value() { track(r, 'value'); return this._value; }, set value(newValue) { if (this._value !== newValue) { this._value = newValue; trigger(r, 'value'); } } }; return r; } /** * Creates a computed property * @param {Function} getter - Getter function * @returns {Object} Computed property */ function computed$2(getter) { let value; let dirty = true; const runner = effect$2(getter, { lazy: true, scheduler: () => { if (!dirty) { dirty = true; trigger(computedRef, 'value'); } } }); const computedRef = { get value() { if (dirty) { value = runner(); dirty = false; } track(computedRef, 'value'); return value; } }; return computedRef; } /** * Creates a reactive effect * @param {Function} fn - Effect function * @param {Object} options - Effect options * @returns {Function} Effect runner */ function effect$2(fn, options = {}) { const effect = createReactiveEffect(fn, options); if (!options.lazy) { effect(); } return effect; } // Internal helpers const targetMap = new WeakMap(); function track(target, key) { // Only track if we have an active effect and a valid target if (activeEffect && target) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = new Set())); } dep.add(activeEffect); } } function trigger(target, key) { // Check if target is valid if (!target) return; const depsMap = targetMap.get(target); if (!depsMap) return; const effects = depsMap.get(key); if (effects) { // Create a new set to avoid infinite loops if an effect triggers itself const effectsToRun = new Set(effects); effectsToRun.forEach(effect => { if (effect.scheduler) { effect.scheduler(); } else { effect(); } }); } } function createReactiveEffect(fn, options) { const effect = function reactiveEffect() { if (!effect.active) return fn(); if (!effectStack.includes(effect)) { cleanup(effect); try { effectStack.push(effect); activeEffect = effect; return fn(); } finally { effectStack.pop(); // Set activeEffect to the previous effect in the stack or null if empty activeEffect = effectStack.length > 0 ? effectStack[effectStack.length - 1] : null; } } }; effect.active = true; effect.deps = []; effect.options = options; return effect; } const effectStack = []; function cleanup(effect) { const { deps } = effect; if (deps.length) { for (let i = 0; i < deps.length; i++) { deps[i].delete(effect); } deps.length = 0; } } // @kalxjs/core - Virtual DOM diffing algorithm /** * Patches a DOM node to match a new virtual DOM node * @param {HTMLElement} domNode - DOM node to patch * @param {Object} oldVNode - Old virtual DOM node * @param {Object} newVNode - New virtual DOM node * @returns {HTMLElement} Updated DOM node */ function patch(domNode, oldVNode, newVNode) { // Check if domNode is valid if (!domNode) { console.warn('Cannot patch: domNode is undefined or null'); return createDOMNode(newVNode); // Return a new node but don't attach it } // Check if domNode has a parent if (!domNode.parentNode) { console.warn('Cannot patch: domNode has no parent'); return createDOMNode(newVNode); // Return a new node but don't attach it } // If the old vnode is the same as the new vnode, do nothing if (oldVNode === newVNode) { return domNode; } // If the new vnode is null or undefined, remove the node if (!newVNode) { domNode.parentNode.removeChild(domNode); return null; } // If the old vnode is null or undefined, create a new node if (!oldVNode) { const newNode = createDOMNode(newVNode); domNode.parentNode.appendChild(newNode); return newNode; } // If the nodes are of different types, replace the node if (nodeTypesAreDifferent(oldVNode, newVNode)) { const newNode = createDOMNode(newVNode); domNode.parentNode.replaceChild(newNode, domNode); return newNode; } // If the nodes are of the same type, update the node return updateDOMNode(domNode, oldVNode, newVNode); } /** * Checks if two nodes are of different types * @private * @param {Object} oldVNode - Old virtual DOM node * @param {Object} newVNode - New virtual DOM node * @returns {boolean} Whether the nodes are of different types */ function nodeTypesAreDifferent(oldVNode, newVNode) { // Handle primitive values if (typeof oldVNode !== 'object' || typeof newVNode !== 'object') { return typeof oldVNode !== typeof newVNode; } // Handle component nodes if (oldVNode.isComponent || newVNode.isComponent) { return oldVNode.tag !== newVNode.tag; } // Handle regular nodes return oldVNode.tag !== newVNode.tag; } /** * Creates a DOM node from a virtual DOM node * @private * @param {Object} vnode - Virtual DOM node * @returns {HTMLElement} DOM node */ function createDOMNode(vnode) { // Handle primitive values if (typeof vnode === 'string' || typeof vnode === 'number') { return document.createTextNode(vnode); } // Handle null or undefined if (!vnode) { // Create a fallback element instead of a comment node const fallbackElement = document.createElement('div'); fallbackElement.className = 'kalxjs-fallback-element'; fallbackElement.innerHTML = ` <div style="padding: 10px; border: 1px solid #f0ad4e; background-color: #fcf8e3; color: #8a6d3b; border-radius: 4px;"> <h4 style="margin-top: 0;">KalxJS Rendering Notice</h4> <p>The framework attempted to render a component but received empty content.</p> <p>This is a fallback element to ensure something is displayed.</p> </div> `; return fallbackElement; } // Handle component nodes if (vnode.isComponent) { // This would be handled by the component system return document.createComment('component node'); } // Handle regular nodes const element = document.createElement(vnode.tag); // Set attributes updateAttributes(element, {}, vnode.props || {}); // Create and append children (vnode.children || []).forEach(child => { if (child !== null && child !== undefined) { element.appendChild(createDOMNode(child)); } }); return element; } /** * Updates a DOM node to match a new virtual DOM node * @private * @param {HTMLElement} domNode - DOM node to update * @param {Object} oldVNode - Old virtual DOM node * @param {Object} newVNode - New virtual DOM node * @returns {HTMLElement} Updated DOM node */ function updateDOMNode(domNode, oldVNode, newVNode) { // Check if domNode is valid if (!domNode) { console.warn('Cannot update DOM node: domNode is undefined or null'); return createDOMNode(newVNode); } // Check if domNode has a parent if (!domNode.parentNode) { console.warn('Cannot update DOM node: domNode has no parent'); return createDOMNode(newVNode); } // Handle text nodes if (typeof oldVNode === 'string' || typeof newVNode === 'string' || typeof oldVNode === 'number' || typeof newVNode === 'number') { if (oldVNode !== newVNode) { const newNode = document.createTextNode(newVNode); domNode.parentNode.replaceChild(newNode, domNode); return newNode; } return domNode; } // Update attributes updateAttributes(domNode, oldVNode.props || {}, newVNode.props || {}); // Update children updateChildren$1(domNode, oldVNode.children || [], newVNode.children || []); return domNode; } /** * Updates the attributes of a DOM node * @private * @param {HTMLElement} domNode - DOM node to update * @param {Object} oldAttrs - Old attributes * @param {Object} newAttrs - New attributes */ function updateAttributes(domNode, oldAttrs, newAttrs) { // Remove old attributes for (const key in oldAttrs) { if (!(key in newAttrs)) { if (key.startsWith('on')) { const eventName = key.slice(2).toLowerCase(); domNode.removeEventListener(eventName, oldAttrs[key]); } else { domNode.removeAttribute(key); } } } // Set new attributes for (const key in newAttrs) { if (oldAttrs[key] !== newAttrs[key]) { if (key.startsWith('on')) { // Handle both camelCase (onClick) and lowercase (onclick) event handlers const eventName = key.slice(2).toLowerCase(); if (oldAttrs[key]) { domNode.removeEventListener(eventName, oldAttrs[key]); } domNode.addEventListener(eventName, newAttrs[key]); } else if (key === 'style' && typeof newAttrs[key] === 'object') { // Handle style objects const styleObj = newAttrs[key]; for (const styleKey in styleObj) { domNode.style[styleKey] = styleObj[styleKey]; } } else if (key === 'class' || key === 'className') { // Handle class names domNode.className = newAttrs[key]; } else if (key === 'dangerouslySetInnerHTML') { // Handle innerHTML domNode.innerHTML = newAttrs[key].__html; } else { // Handle regular attributes domNode.setAttribute(key, newAttrs[key]); } } } } /** * Updates the children of a DOM node * @private * @param {HTMLElement} domNode - DOM node to update * @param {Array} oldChildren - Old children * @param {Array} newChildren - New children */ function updateChildren$1(domNode, oldChildren, newChildren) { // Check if domNode is valid if (!domNode) { console.warn('Cannot update children: domNode is undefined or null'); return; } // Optimize for common cases if (oldChildren.length === 0) { // If there were no old children, append all new children newChildren.forEach(child => { domNode.appendChild(createDOMNode(child)); }); return; } if (newChildren.length === 0) { // If there are no new children, remove all old children domNode.innerHTML = ''; return; } // Use key-based reconciliation if keys are present const oldKeyedChildren = {}; const newKeyedChildren = {}; let hasKeys = false; // Check if keys are present for (let i = 0; i < oldChildren.length; i++) { const child = oldChildren[i]; if (child && child.props && child.props.key != null) { hasKeys = true; oldKeyedChildren[child.props.key] = { vnode: child, index: i }; } } for (let i = 0; i < newChildren.length; i++) { const child = newChildren[i]; if (child && child.props && child.props.key != null) { hasKeys = true; newKeyedChildren[child.props.key] = { vnode: child, index: i }; } } if (hasKeys) { // Use key-based reconciliation const domChildren = Array.from(domNode.childNodes); const keysToRemove = Object.keys(oldKeyedChildren).filter(key => !(key in newKeyedChildren)); // Remove nodes that are no longer needed keysToRemove.forEach(key => { const { index } = oldKeyedChildren[key]; if (index < domChildren.length && domChildren[index]) { domNode.removeChild(domChildren[index]); } }); // Update or insert nodes let lastIndex = 0; Object.keys(newKeyedChildren).forEach(key => { const { vnode: newChild, index: newIndex } = newKeyedChildren[key]; const oldChild = oldKeyedChildren[key]; if (oldChild) { // Update existing node const oldIndex = oldChild.index; const oldVNode = oldChild.vnode; // Make sure the DOM node exists before patching if (oldIndex < domChildren.length && domChildren[oldIndex]) { patch(domChildren[oldIndex], oldVNode, newChild); // Move node if needed if (oldIndex < lastIndex) { const node = domChildren[oldIndex]; if (lastIndex < domChildren.length) { domNode.insertBefore(node, domChildren[lastIndex]); } else { domNode.appendChild(node); } } lastIndex = Math.max(oldIndex, lastIndex); } else { // DOM node doesn't exist, create a new one const newNode = createDOMNode(newChild); if (newIndex < domChildren.length) { domNode.insertBefore(newNode, domChildren[newIndex]); } else { domNode.appendChild(newNode); } } } else { // Insert new node const newNode = createDOMNode(newChild); if (newIndex < domChildren.length) { domNode.insertBefore(newNode, domChildren[newIndex]); } else { domNode.appendChild(newNode); } } }); } else { // Use simple reconciliation const maxLength = Math.max(oldChildren.length, newChildren.length); for (let i = 0; i < maxLength; i++) { const oldChild = oldChildren[i]; const newChild = newChildren[i]; // Check if the DOM child exists const domChild = i < domNode.childNodes.length ? domNode.childNodes[i] : null; if (!oldChild && newChild) { // Insert new node domNode.appendChild(createDOMNode(newChild)); } else if (oldChild && !newChild) { // Remove old node if it exists in the DOM if (domChild) { domNode.removeChild(domChild); } else { console.warn('Attempted to remove a child node that does not exist in the DOM'); } } else if (oldChild && newChild) { if (domChild) { // Update existing node patch(domChild, oldChild, newChild); } else { // DOM node doesn't exist, create a new one domNode.appendChild(createDOMNode(newChild)); } } } } } // packages/core/src/vdom/vdom.js /** * Creates a DOM element with the given tag * @param {string} tag - HTML tag name * @returns {HTMLElement} The created DOM element */ function createDOMElement$1(tag) { // Ensure tag is a string if (typeof tag !== 'string') { console.warn(`createDOMElement: Invalid tag type: ${typeof tag}. Using 'div' instead.`); tag = 'div'; } // Ensure tag is not empty if (!tag) { console.warn('createDOMElement: Empty tag provided. Using div instead.'); tag = 'div'; } try { return document.createElement(tag); } catch (error) { console.error(`createDOMElement: Error creating element with tag "${tag}":`, error); return document.createElement('div'); } } /** * Flattens an array (polyfill for Array.prototype.flat) * @private * @param {Array} arr - Array to flatten * @param {number} depth - Maximum recursion depth * @returns {Array} Flattened array */ function flattenArray(arr, depth = 1) { // Ensure arr is always an array if (!Array.isArray(arr)) { return arr ? [arr] : []; } const result = []; arr.forEach(item => { if (Array.isArray(item) && depth > 0) { result.push(...flattenArray(item, depth - 1)); } else { result.push(item); } }); return result; } /** * Creates a virtual DOM node * @param {string|function} tag - HTML tag name or component function * @param {Object} props - Node properties * @param {Array} children - Child nodes */ function h$1(tag, props = {}, children = []) { // Handle null or undefined tag if (!tag) { console.warn('Invalid tag provided to h function'); return null; } // Ensure children is always an array const childArray = Array.isArray(children) ? children : (children ? [children] : []); // If tag is a component function, mark it as a component if (typeof tag === 'function') { return { tag: 'component-placeholder', // Use a placeholder tag for the vnode props: props || {}, children: flattenArray(childArray), component: tag, // Store the component function isComponent: true // Mark as a component }; } return { tag, props: props || {}, children: flattenArray(childArray) }; } /** * Creates a real DOM element from a virtual node * @param {Object} vnode - Virtual DOM node * @returns {HTMLElement} Real DOM element */ function createElement$1(vnode) { // Handle primitive values (string, number, etc.) if (typeof vnode === 'string' || typeof vnode === 'number') { return document.createTextNode(String(vnode)); } // Handle null or undefined if (!vnode) { console.error('Attempted to create element from null/undefined vnode'); // Instead of returning a comment node, create a fallback element const fallbackElement = document.createElement('div'); fallbackElement.className = 'kalxjs-fallback-element'; fallbackElement.innerHTML = ` <div style="padding: 10px; border: 1px solid #f0ad4e; background-color: #fcf8e3; color: #8a6d3b; border-radius: 4px;"> <h4 style="margin-top: 0;">KalxJS Rendering Notice</h4> <p>The framework attempted to render a component but received empty content.</p> <p>This is a fallback element to ensure something is displayed.</p> </div> `; // Add a debug property to help with troubleshooting fallbackElement._debug = { error: 'Null or undefined vnode', timestamp: new Date().toISOString() }; return fallbackElement; } // Handle case where vnode might be a router view component if (vnode._isRouterView && vnode._componentRef) { try { console.log('Rendering router view component:', vnode._componentRef.name || 'anonymous'); // Create a container element const container = document.createElement(vnode.tag || 'div'); // Apply props to the container for (const [key, value] of Object.entries(vnode.props || {})) { if (value === undefined || value === null) continue; if (key.startsWith('on')) { const eventName = key.slice(2).toLowerCase(); if (typeof value === 'function') { container.addEventListener(eventName, value); } } else if (key === 'style' && typeof value === 'object') { Object.assign(container.style, value); } else if (key === 'class' || key === 'className') { container.className = value; } else { container.setAttribute(key, value); } } // Add a data attribute to identify this as a router view container container.setAttribute('data-router-view', 'true'); // Store the component reference for later use container._componentRef = vnode._componentRef; // Return the container element return container; } catch (error) { console.error('Error preparing router view component:', error); const errorElement = document.createElement('div'); errorElement.style.color = 'red'; errorElement.style.border = '1px solid red'; errorElement.style.padding = '10px'; errorElement.style.margin = '10px 0'; errorElement.innerHTML = ` <h4>Router View Error</h4> <p>${error.message}</p> <pre style="font-size: 12px; overflow: auto; max-height: 200px; background: #f5f5f5; padding: 5px;">${error.stack}</pre> `; return errorElement; } } // Handle case where vnode might be a component if (vnode.isComponent && vnode.component) { try { console.log('Rendering component:', vnode.component.name || 'anonymous'); // Check if this is a component factory from defineComponent if (vnode.component.options) { console.log('Detected component factory with options:', vnode.component.options.name); } // Call the component function with the props const result = vnode.component(vnode.props); if (!result) { throw new Error('Component function returned null or undefined'); } console.log('Component function result:', result); // If the result is a component instance with a render method, call it if (result.render && typeof result.render === 'function') { const renderResult = result.render(); console.log('Component render result:', renderResult); return createElement$1(renderResult); } // If the result doesn't have a tag property, add one if (typeof result === 'object' && !result.tag) { console.warn('Component returned object without tag property, adding div tag'); result.tag = 'div'; } return createElement$1(result); } catch (error) { console.error('Error rendering component:', error); const errorElement = document.createElement('div'); errorElement.style.color = 'red'; errorElement.style.border = '1px solid red'; errorElement.style.padding = '10px'; errorElement.style.margin = '10px 0'; errorElement.innerHTML = ` <h4>Component Error</h4> <p>${error.message}</p> <pre style="font-size: 12px; overflow: auto; max-height: 200px; background: #f5f5f5; padding: 5px;">${error.stack}</pre> `; return errorElement; } } // Handle case where vnode might be a function (legacy support) if (typeof vnode === 'function') { console.warn('Function passed directly to createElement. This is deprecated, use h(Component, props, children) instead.'); try { // Call the function with empty props const result = vnode({}); return createElement$1(result); } catch (error) { console.error('Error rendering function component:', error); const errorElement = document.createElement('div'); errorElement.style.color = 'red'; errorElement.innerHTML = `<p>Error: ${error.message}</p>`; return errorElement; } } // Handle case where vnode.tag is a component object with setup function if (vnode.tag && typeof vnode.tag === 'object' && typeof vnode.tag.setup === 'function') { try { console.log('Handling component object with setup function'); // Call the setup function to get the actual component const setupResult = vnode.tag.setup(vnode.props || {}); // Handle different types of setup results if (setupResult) { if (typeof setupResult === 'object') { // Case 1: Setup returned an object (hopefully a vnode) if (!setupResult.tag) { console.warn('Component setup returned object without tag, creating a default vnode'); // Create a default vnode with the setup result as content const defaultVNode = { tag: 'div', props: { class: 'kal-component-default' }, children: [ typeof setupResult === 'object' ? JSON.stringify(setupResult) : String(setupResult) ] }; return createElement$1(defaultVNode); } else if (typeof setupResult.tag !== 'string') { console.warn('Component setup returned object with invalid tag, setting to div'); setupResult.tag = 'div'; } // Create the element from the setup result return createElement$1(setupResult); } else if (typeof setupResult === 'function') { // Case 2: Setup returned a function (render function) console.log('Component setup returned a function, trying to call it'); try { const renderResult = setupResult(); if (renderResult && typeof renderResult === 'object') { if (!renderResult.tag) { renderResult.tag = 'div'; } return createElement$1(renderResult); } else { throw new Error('Render function did not return a valid vnode'); } } catch (renderError) { console.error('Error calling render function:', renderError); const errorElement = document.createElement('div'); errorElement.style.color = 'red'; errorElement.innerHTML = `<p>Error in render function: ${renderError.message}</p>`; return errorElement; } } else { // Case 3: Setup returned a primitive value console.warn('Component setup returned a primitive value:', setupResult); const defaultVNode = { tag: 'div', props: { class: 'kal-component-primitive' }, children: [String(setupResult)] }; return createElement$1(defaultVNode); } } else { // Case 4: Setup returned null or undefined console.error('Component setup did not return a valid value', setupResult); const errorElement = document.createElement('div'); errorElement.style.color = 'red'; errorElement.innerHTML = '<p>Component setup did not return a valid view</p>'; return errorElement; } } catch (error) { console.error('Error in component setup:', error); const errorElement = document.createElement('div'); errorElement.style.color = 'red'; errorElement.style.border = '1px solid red'; errorElement.style.padding = '10px'; errorElement.innerHTML = ` <h4>Component Setup Error</h4> <p>${error.message}</p> <pre style="font-size: 12px; overflow: auto; max-height: 200px; background: #f5f5f5; padding: 5px;">${error.stack}</pre> `; return errorElement; } } // Handle case where vnode might not be a proper virtual node object if (!vnode.tag) { console.error('Invalid vnode (missing tag):', vnode); // Debug information console.log('vnode type:', typeof vnode); console.log('vnode keys:', Object.keys(vnode || {})); // Create a more informative error element const errorElement = document.createElement('div'); errorElement.style.color = 'red'; errorElement.style.border = '1px solid red'; errorElement.style.padding = '10px'; errorElement.style.margin = '10px 0'; try { errorElement.innerHTML = ` <h4>Invalid Virtual DOM Node</h4> <p>A virtual DOM node is missing the required 'tag' property</p> <pre style="font-size: 12px; overflow: auto; max-height: 200px; background: #f5f5f5; padding: 5px;"> ${JSON.stringify(vnode, null, 2)} </pre> `; } catch (e) { errorElement.textContent = `Invalid vnode: Cannot stringify for display`; } return errorElement; } // Create the DOM element let element; try { // Ensure tag is a string const tag = typeof vnode.tag === 'string' ? vnode.tag : 'div'; if (typeof vnode.tag !== 'string') { console.warn(`Invalid tag type: ${typeof vnode.tag}. Using 'div' instead.`, vnode); } element = createDOMElement$1(tag); } catch (error) { console.error(`Error creating element with tag "${vnode.tag}":`, error); const errorElement = document.createElement('div'); errorElement.style.color = 'red'; errorElement.style.padding = '5px'; errorElement.textContent = `Invalid tag: ${vnode.tag}`; return errorElement; } // Set properties - ensure props is a proper object and not an array try { const props = vnode.props || {}; // Safety check: if props is an array, it's likely a mistake - ignore it if (Array.isArray(props)) { console.warn('Props should be an object, not an array. Ignoring props:', props); } else { for (const [key, value] of Object.entries(props)) { if (value === undefined || value === null) { continue; // Skip null/undefined props } // Skip numeric keys which are likely array indices accidentally passed as props if (/^\d+$/.test(key)) { console.warn(`Numeric key "${key}" found in props - this is likely an array passed as props. Skipping.`); continue; } if (key.startsWith('on')) { // Handle both camelCase (onClick) and lowercase (onclick) event handlers const eventName = key.slice(2).toLowerCase(); if (typeof value === 'function') { element.addEventListener(eventName, value); } else { console.warn(`Event handler for ${eventName} is not a function:`, value); } } else if (key === 'style') { // Handle style objects and strings if (typeof value === 'object') { Object.assign(element.style, value); } else if (typeof value === 'string') { element.style.cssText = value; } } else if (key === 'class' || key === 'className') { // Handle class names element.className = value; } else if (key === 'dangerouslySetInnerHTML') { // Handle innerHTML if (value && value.__html !== undefined) { element.innerHTML = value.__html; } } else { // Handle regular attributes - validate attribute name and value try { // Validate attribute name (must be valid HTML attribute name) if (typeof key === 'string' && key.length > 0 && /^[a-zA-Z][\w-]*$/.test(key)) { // Convert value to string and ensure it's valid const stringValue = String(value); element.setAttribute(key, stringValue); } else { console.warn(`Invalid attribute name: ${key}`); } } catch (attrError) { console.warn(`Failed to set attribute ${key}=${value}:`, attrError.message); } } } } } catch (error) { console.error('Error setting properties:', error); // Continue despite property errors } // Create and append children const children = Array.isArray(vnode.children) ? vnode.children : (vnode.children ? [vnode.children] : []); children.forEach(child => { if (child !== null && child !== undefined) { try { const childElement = createElement$1(child); if (childElement) { element.appendChild(childElement); } } catch (error) { console.error('Error creating child element:', error); const errorComment = document.createComment(`Error creating child: ${error.message}`); element.appendChild(errorComment); } } }); return element; } /** * Updates an existing DOM element to match a new virtual DOM node * @param {HTMLElement} element - DOM element to update * @param {Object} oldVNode - Previous virtual DOM node * @param {Object} newVNode - New virtual DOM node * @returns {HTMLElement} Updated DOM element */ function updateElement(element, oldVNode, newVNode) { // Use the new diffing algorithm return patch(element, oldVNode, newVNode); } /** * Updates the properties of a DOM element * @param {HTMLElement} element - DOM element to update * @param {Object} oldProps - Previous properties * @param {Object} newProps - New properties */ function updateProps(element, oldProps, newProps) { // Remove old properties for (const [key, value] of Object.entries(oldProps)) { if (!(key in newProps)) { if (key.startsWith('on')) { const eventName = key.slice(2).toLowerCase(); element.removeEventListener(eventName, value); } else { element.removeAttribute(key); } } } // Set new properties for (const [key, value] of Object.entries(newProps)) { if (oldProps[key] !== value) { if (key.startsWith('on')) { const eventName = key.slice(2).toLowerCase(); if (oldProps[key]) { element.removeEventListener(eventName, oldProps[key]); } element.addEventListener(eventName, value); } else { element.setAttribute(key, value); } } } } /** * Updates a child element * @param {HTMLElement} parentElement - Parent DOM element * @param {Object} oldChild - Previous virtual DOM node * @param {Object} newChild - New virtual DOM node * @param {number} index - Child index */ function updateChild(parentElement, oldChild, newChild, index) { const childElement = parentElement.childNodes[index]; // Remove extra children if (!newChild) { parentElement.removeChild(childElement); return; } // Add missing children if (!oldChild) { const newElement = createElement$1(newChild); parentElement.appendChild(newElement); return; } // Update existing children updateElement(childElement, oldChild, newChild); } /** * Updates all children of a DOM element * @param {HTMLElement} parentElement - Parent DOM element * @param {Array} oldChildren - Previous virtual DOM nodes * @param {Array} newChildren - New virtual DOM nodes */ function updateChildren(parentElement, oldChildren, newChildren) { const maxLength = Math.max(oldChildren.length, newChildren.length); for (let i = 0; i < maxLength; i++) { updateChild( parentElement, oldChildren[i], newChildren[i], i ); } } // @kalxjs/core - Component instance management for Composition API // Current component instance let currentInstance = null; /** * Sets the current component instance * @param {Object} instance - Component instance */ function setCurrentInstance(instance) { currentInstance = instance; } /** * Gets the current component instance * @returns {Object} Current component instance */ function getCurrentInstance$1() { if (!currentInstance) { console.warn('getCurrentInstance() can only be used inside setup()'); return {}; } return currentInstance; } // @kalxjs/core - Composition API /** * Creates a reactive object that can be used in the setup function * @param {Object} target - Object to make reactive * @returns {Proxy} Reactive object */ function useReactive(target) { return reactive$1(target); } /** * Creates a reactive reference that can be used in the setup function * @param {any} value - Initial value * @returns {Object} Reactive reference */ function useRef(value) { return ref$1(value); } /** * Creates a computed property that can be used in the setup function * @param {Function} getter - Getter function * @returns {Object} Computed property */ function useComputed(getter) { return computed$2(getter); } /** * Watches for changes in a reactive source and runs a callback * @param {Object|Function} source - Reactive object or getter function * @param {Function} callback - Callback function * @param {Object} options - Watch options * @returns {Function} Function to stop watching */ function watch$1(source, callback, options = {}) { const { immediate = false } = options; // Handle ref or reactive objects const getter = typeof source === 'function' ? source : () => { // Handle ref if (source && 'value' in source) { return source.value; } // Handle reactive object return source; }; let oldValue; const runner = effect$2(() => getter(), { lazy: true, scheduler: () => { const newValue = runner(); callback(newValue, oldValue); oldValue = newValue; } }); if (immediate) { oldValue = runner(); callback(oldValue, undefined); } else { oldValue = runner(); } return () => { // Cleanup effect runner.active = false; }; } /** * Runs a callback once when the component is mounted * @param {Function} callback - Callback function */ function onMounted$1(callback) { getCurrentInstance$1().mounted.push(callback); } /** * Runs a callback before the component is unmounted * @param {Function} callback - Callback function */ function onUnmounted$1(callback) { getCurrentInstance$1().unmounted.push(callback); } /** * Runs a callback before the component is updated * @param {Function} callback - Callback function */ function onBeforeUpdate(callback) { getCurrentInstance$1().beforeUpdate.push(callback); } /** * Runs a callback after the component is updated * @param {Function} callback - Callback function */ function onUpdated(callback) { getCurrentInstance$1().updated.push(callback); } // @kalxjs/core - Lifecycle hooks for Composition API /** * Runs a callback when the component is created * @param {Function} callback - Callback function */ function onCreated(callback) { const instance = getCurrentInstance$1(); if (!instance.created) { instance.created = []; } instance.created.push(callback); } /** * Runs a callback before the component is mounted * @param {Function} callback - Callback function */ function onBeforeMount(callback) { const instance = getCurrentInstance$1(); if (!instance.beforeMount) { instance.beforeMount = []; } instance.beforeMount.push(callback); } /** * Runs a callback before the component is unmounted * @param {Function} callback - Callback function */ function onBeforeUnmount(callback) { const instance = getCurrentInstance$1(); if (!instance.beforeUnmount) { instance.beforeUnmount = []; } instance.beforeUnmount.push(callback); } /** * Runs a callback when an error occurs in the component * @param {Function} callback - Callback function */ function onErrorCaptured(callback) { const instance = getCurrentInstance$1(); if (!instance.errorCaptured) { instance.errorCaptured = []; } instance.errorCaptured.push(callback); } // @kalxjs/core - Utility functions for Composition API /** * Creates a reactive reference with a getter and setter * @param {any} initialValue - Initial value * @param {Function} getter - Custom getter function * @param {Function} setter - Custom setter function * @returns {Object} Reactive reference */ function customRef(factory) { const track = () => { }; const trigger = () => { }; const { get, set } = factory(track, trigger); return Object.defineProperty({}, 'value', { get, set }); } /** * Creates a readonly reference * @param {Object} source - Source reference * @returns {Object} Readonly reference */ function readonly(source) { // Handle ref if (source && typeof source === 'object' && 'value' in source) { return Object.defineProperty({}, 'value', { get: () => source.value, set: () => { console.warn('Cannot set a readonly ref'); } }); } // Handle reactive object const handler = { get(target, key, receiver) { const result = Reflect.get(target, key, receiver); return result; }, set() { console.warn('Cannot set a readonly reactive object'); return true; }, deleteProperty() { console.warn('Cannot delete from a readonly reactive object'); return true; } }; return new Proxy(source, handler); } /** * Creates a computed reference that can be both read and written * @param {Object} options - Options with get and set functions * @returns {Object} Computed reference */ function writableComputed(options) { const { get, set } = options; const computedRef = { get value() { return get(); }, set value(newValue) { set(newValue); } }; return computedRef; } /** * Creates a reactive reference that is synchronized with localStorage * @param {string} key - localStorage key * @param {any} defaultValue - Default value if key doesn't exist * @returns {Object} Reactive reference */ function useLocalStorage(key, defaultValue) { // Get initial value from localStorage or use default const initialValue = (() => { if (typeof window === 'undefined') return defaultValue; try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : defaultValue; } catch (error) { console.warn(`Error reading localStorage key "${key}":`, error); return defaultValue; } })(); // Create a ref with the initial value const valueRef = ref$1(initialValue); // Watch for changes and update localStorage if (typeof window !== 'undefined') { effect$2(() => { try { window.localStorage.setItem(key, JSON.stringify(valueRef.value)); } catch (error) { console.warn(`Error setting localStorage key "${key}":`, error); } }); } return valueRef; } /** * Creates a debounced version of a function * @param {Function} fn - Function to debounce * @param {number} delay - Delay in milliseconds * @returns {Function} Debounced function */ function useDebounce(fn, delay) { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => { fn(...args); }, delay); }; } /** * Creates a throttled version of a function * @param {Function} fn - Function to throttle * @param {number} limit - Limit in milliseconds * @returns {Function} Throttled function */ function useThrottle(fn, limit) { let inThrottle; return (...args) => { if (!inThrottle) { fn(...args); inThrottle = true; setTimeout(() => { inThrottle = false; }, limit); } }; } /** * Creates a reactive reference that tracks mouse position * @returns {Object} Reactive mouse position */ function useMouse() { const position = reactive$1({ x: 0, y: 0 }); const update = (event) => { position.x = event.clientX; position.y = event.clientY; }; if (typeof window !== 'undefined') { window.addEventListener('mousemove', update); // Clean up event listener when component is unmounted const instance = getCurrentInstance$1(); if (instance) { if (!instance.unmounted) { instance.unmounted = []; } instance.unmounted.push(() => { window.removeEventListener('mousemove', update); }); } } return position; } // Symbol for internal provide/inject const PROVIDE_KEY = Symbol('provide'); /** * Provide a value that can be injected by descendant components * @param {string|symbol} key - The key to provide the value under * @param {*} value - The value to provide */ function provide(key, value) { const instance = getCurrentInstance$1(); if (!instance) { if (process.env.NODE_ENV !== 'production') { console.warn('provide() can only be used inside a setup function.'); } return; } // Initialize provides map if it doesn't exist if (!instance[PROVIDE_KEY]) { instance[PROVIDE_KEY] = {}; } instance[PROVIDE_KEY][key] = value; } /** * Inject a value provided by an ancestor component * @param {string|symbol} key - The key to inject * @param {*} defaultValue - Default value if key is not found * @param {boolean} treatDefaultAsFactory - Whether to treat defaultValue as a factory function * @returns {*} The injected value or default value */ function inject(key, defaultValue, treatDefaultAsFactory = false) { const instance = getCurrentInstance$1(); if (!instance) { if (process.env.NODE_ENV !== 'production') { console.warn('inject() can only be used inside a setup function.'); } return defaultValue; } // Walk up the component tree to find the provided value let current = instance.parent; while (current) { if (current[PROVIDE_KEY] && key in current[PROVIDE_KEY]) { return current[PROVIDE_KEY][key]; } current = current.parent; } // Check if the app instance has the provided value if (instance.appContext && instance.appContext.provides && key in instance.appContext.provides) { return instance.appContext.provides[key]; } // Return default value if (arguments.length > 1) { return treatDefaultAsFactory && typeof defaultValue === 'function' ? defaultValue() : defaultValue; } else if (process.env.NODE_ENV !== 'production') { console.warn(`Injection "${String(key)}" not found.`); } } /** *