-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
/**
- @fileoverview HTML Elements Manager with Proxy-based State Management
- @Version 1.1.0
- @example [html] <script> console.log(GUI.createElement('div', { id: 'my-div' }, 'Hello, World!')); </script>
*/
const namespace = 'GUI';
if (namespace in window) {
throw new Error("${namespace}" already defined in window);
}
window[namespace] = (function (window) {
'use strict';
const document = window.document;
const stateMap = new WeakMap();
const listenersMap = new WeakMap();
/**
- Appends an array of children to a parent node.
*/
function appendChildren(parent, children) {
if (!(parent instanceof Node)) {
throw new Error('Parent must be a Node');
}
for (const child of children) {
parent.appendChild(
child instanceof Node ? child : document.createTextNode(child)
);
}
}
/**
- Creates a DocumentFragment and appends the provided children to it.
*/
function createDocumentFragment(...children) {
const fragment = document.createDocumentFragment();
appendChildren(fragment, children);
return fragment;
}
/**
- Creates a new DOM element with the specified tagName, properties, and children.
*/
function createElement(tagName, props, ...children) {
if (tagName === '') {
return createDocumentFragment(...children);
}
if (typeof tagName !== 'string') {
throw new Error('Type must be a string');
}
if (!/^[a-zA-Z0-9-]*$/.test(tagName)) {
throw new Error('Tag name must contain only letters, numbers, hyphens, or be an empty string.');
}
let element = document.createElement(tagName);
appendChildren(element, children);
if (props === undefined || props === null) {
return element;
}
if (props.constructor !== Object) {
throw new Error('Props must be a plain object or null.');
}
for (const key in props) {
if (props[key]?.constructor === Object) {
Object.assign(element[key], props[key]);
continue;
}
element[key] = props[key];
}
return element;
}
/**
- Creates a Proxy to watch state changes.
*/
function createStateProxy(element, state) {
if (typeof state !== 'object' || state === null) {
throw new Error('State must be a plain object');
}
return new Proxy(state, {
set(target, key, value) {
const oldValue = target[key];
target[key] = value;
// Notify listeners if state changes
if (listenersMap.has(element)) {
listenersMap.get(element).forEach(callback => {
callback({ key, oldValue, newValue: value });
});
}
return true;
}
});
}
/**
- Sets the state for a given element with Proxy-based change tracking.
*/
function setState(element, state) {
if (!(element instanceof Node)) {
throw new Error('Element must be a Node');
}
const proxyState = createStateProxy(element, state);
stateMap.set(element, proxyState);
}
/**
- Gets the state for a given element.
*/
function getState(element) {
if (!(element instanceof Node)) {
throw new Error('Element must be a Node');
}
return stateMap.get(element);
}
/**
- Registers a listener for state changes of an element.
*/
function onStateChange(element, callback) {
if (!(element instanceof Node)) {
throw new Error('Element must be a Node');
}
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
if (!listenersMap.has(element)) {
listenersMap.set(element, []);
}
listenersMap.get(element).push(callback);
}
/**
- Removes all state change listeners for an element.
*/
function removeStateChangeListeners(element) {
if (!(element instanceof Node)) {
throw new Error('Element must be a Node');
}
listenersMap.delete(element);
}
return {
createDocumentFragment,
createElement,
getState,
setState,
onStateChange,
removeStateChangeListeners
};
})(window);
Metadata
Metadata
Assignees
Labels
No labels