@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
JavaScript
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.`);
}
}
/**
*