Skip to content

state changes in HTML Elements Manager snippet #21

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);

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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