Skip to content

state changes in HTML Elements Manager snippet #21

@virtualitems

Description

@virtualitems

/**

  • @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

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions