improved tag(h), to work with htm, xhtm

This commit is contained in:
Geoff Doty 2025-05-31 01:10:07 -04:00
parent 25a4d003e4
commit d6ae676d43
1 changed files with 56 additions and 16 deletions

View File

@ -4,27 +4,67 @@
* Generates new DOM element(s) from a tag, attributes * Generates new DOM element(s) from a tag, attributes
* *
* @param {String} tag - tag name * @param {String} tag - tag name
* @param {Object|String|Array} args - attributes, text or array of child elements * @param {Object} props - tag attributes
* @param {Object|String|Array} args - text or array of child elements
* *
* @returns {HTMLElement} The created DOM element(s) * @returns {HTMLElement} The created DOM element(s)
*/ */
export default function h(tag, ...args) { export function h(tagName, props, ...children) {
const el = document.createElement(tag); const el = tagName === DocumentFragment ? document.createDocumentFragment() : document.createElement(tagName);
const isScalar = (value) => typeof value === 'string' || typeof value === 'number';
const booleanAttrs = ['disabled', 'checked', 'selected', 'hidden', 'readonly', 'required', 'open', 'autoplay', 'loop', 'muted'];
// support all scalar values as TextNodes // Handle props (object or null)
const isScalar = (value) => ["boolean", "string", "number"].includes(typeof value); if (props != null && typeof props === 'object' && !Array.isArray(props)) {
for (const [key, value] of Object.entries(props)) {
for(let i = 0; i < args.length; i++) { if (value == null) continue;
if (isScalar(args[i])) { if (booleanAttrs.includes(key)) {
el.appendChild(document.createTextNode(args[i])); if (value === true) {
} else if (Array.isArray(args[i])) { el.setAttribute(key, '');
el.append(...args[i]); el[key] = true;
} else { } else if (value === false) {
for(const [k,v] of Object.entries(args[i])) { el.removeAttribute(key);
// if not both ways, some attributes do not render el[key] = false;
el.setAttribute(k, v); }
el[k] = v; continue;
} }
if (key.startsWith('on') && typeof value === 'function') {
el.addEventListener(key.slice(2).toLowerCase(), value);
continue;
}
if (key === 'class') {
el.className = value;
continue;
}
if (key === 'style' && typeof value === 'object') {
Object.assign(el.style, value);
continue;
}
el.setAttribute(key, value);
if (key in el) {
el[key] = value;
}
}
} else if (props != null) {
// If props is not an object, treat it as a child
children.unshift(props);
}
// Handle children
for (const child of children) {
if (child == null) continue;
if (isScalar(child)) {
el.appendChild(document.createTextNode(child));
} else if (Array.isArray(child)) {
const fragment = document.createDocumentFragment();
fragment.append(...child.filter(c => c != null));
el.appendChild(fragment);
} else if (child instanceof Node) {
el.appendChild(child);
} else if (typeof child === 'boolean') {
console.warn(`Boolean child ${child} passed to h() for tag "${tagName}". Booleans are not rendered.`);
} else {
console.error(`Unsupported child type: ${typeof child} for tag "${tagName}" in h() function`);
} }
} }