Skip to content

Commit

Permalink
Fix #90 - Drop attributes on null or void values
Browse files Browse the repository at this point in the history
  • Loading branch information
WebReflection committed Nov 17, 2023
1 parent 16a36f7 commit f1cc16f
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 162 deletions.
162 changes: 99 additions & 63 deletions esm/handler.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
import udomdiff from 'udomdiff';
import { empty, isArray, set } from './utils.js';
import { diffFragment } from './persistent-fragment.js';
import { comment } from './literals.js';
import drop from './range.js';

const setAttribute = (element, name, value) => {
element.setAttribute(name, value);
}

export const removeAttribute = (element, name) => {
element.removeAttribute(name);
}

/**
* @template T
* @param {Element} element
* @param {T} value
* @returns {T}
*/
const aria = (element, value) => {
export const aria = (element, value) => {
for (const key in value) {
const $ = value[key];
const name = key === 'role' ? key : `aria-${key}`;
if ($ == null) element.removeAttribute(name);
else element.setAttribute(name, $);
if ($ == null) removeAttribute(element, name);
else setAttribute(element, name, $);
}
return value;
};

export const arrayComment = () => array;

let listeners;

/**
Expand All @@ -28,7 +39,7 @@ let listeners;
* @param {string} name
* @returns {T}
*/
const at = (element, value, name) => {
export const at = (element, value, name) => {
name = name.slice(1);
if (!listeners) listeners = new WeakMap;
const known = listeners.get(element) || set(listeners, element, {});
Expand All @@ -40,21 +51,53 @@ const at = (element, value, name) => {
return value;
};

/**
* @template T
* @this {import("./literals.js").HoleDetails}
* @param {Node} node
* @param {T} value
* @returns {T}
*/
function hole(node, value) {
const n = this.n || (this.n = node);
switch (typeof value) {
case 'string':
case 'number':
case 'boolean': {
if (n !== node) n.replaceWith((this.n = node));
this.n.data = value;
break;
}
case 'object':
case 'undefined': {
if (value == null) (this.n = node).data = '';
else this.n = value.valueOf();
n.replaceWith(this.n);
break;
}
}
return value;
};

export const boundComment = () => hole.bind(comment());

/**
* @template T
* @param {Element} element
* @param {T} value
* @returns {T}
*/
const className = (element, value) => direct(element, value, 'className');
export const className = (element, value) => maybeDirect(
element, value, value == null ? 'class' : 'className'
);

/**
* @template T
* @param {Element} element
* @param {T} value
* @returns {T}
*/
const data = (element, value) => {
export const data = (element, value) => {
const { dataset } = element;
for (const key in value) {
if (value[key] == null) delete dataset[key];
Expand All @@ -70,7 +113,7 @@ const data = (element, value) => {
* @param {string} name
* @returns {T}
*/
const direct = (ref, value, name) => (ref[name] = value);
export const direct = (ref, value, name) => (ref[name] = value);

/**
* @template T
Expand All @@ -79,19 +122,32 @@ const direct = (ref, value, name) => (ref[name] = value);
* @param {string} name
* @returns {T}
*/
const dot = (element, value, name) => direct(element, value, name.slice(1));
export const dot = (element, value, name) => direct(element, value, name.slice(1));

/**
* @template T
* @param {Element} element
* @param {T} value
* @param {string} name
* @returns {T}
*/
const ref = (element, value) => {
if (typeof value === 'function') value(element);
else value.current = element;
return value;
};
export const maybeDirect = (element, value, name) => (
value == null ?
(removeAttribute(element, name), value) :
direct(element, value, name)
);

/**
* @template T
* @param {Element} element
* @param {T} value
* @returns {T}
*/
export const ref = (element, value) => (
(typeof value === 'function' ?
value(element) : (value.current = element)),
value
);

/**
* @template T
Expand All @@ -100,19 +156,24 @@ const ref = (element, value) => {
* @param {string} name
* @returns {T}
*/
const regular = (element, value, name) => {
if (value == null) element.removeAttribute(name);
else element.setAttribute(name, value);
return value;
};
const regular = (element, value, name) => (
(value == null ?
removeAttribute(element, name) :
setAttribute(element, name, value)),
value
);

/**
* @template T
* @param {Element} element
* @param {T} value
* @returns {T}
*/
const style = (element, value) => direct(element.style, value, 'cssText');
export const style = (element, value) => (
value == null ?
maybeDirect(element, value, 'style') :
direct(element.style, value, 'cssText')
);

/**
* @template T
Expand All @@ -121,10 +182,10 @@ const style = (element, value) => direct(element.style, value, 'cssText');
* @param {string} name
* @returns {T}
*/
const toggle = (element, value, name) => {
element.toggleAttribute(name.slice(1), value);
return value;
};
export const toggle = (element, value, name) => (
element.toggleAttribute(name.slice(1), value),
value
);

/**
* @param {Node} node
Expand All @@ -133,13 +194,11 @@ const toggle = (element, value, name) => {
* @param {Node[]} prev
* @returns {Node[]}
*/
export const array = (node, value, _, prev) => {
if (value.length)
return udomdiff(node.parentNode, prev, value, diffFragment, node);
if (prev.length)
drop(prev[0], prev.at(-1), false);
return empty;
};
export const array = (node, value, _, prev) => (
value.length ?
udomdiff(node.parentNode, prev, value, diffFragment, node) :
(drop(prev[0], prev.at(-1), false), empty)
);

export const attr = new Map([
['aria', aria],
Expand All @@ -163,7 +222,12 @@ export const attribute = (element, name, svg) => {
default: return (
svg || ('ownerSVGElement' in element) ?
(name === 'ref' ? ref : regular) :
(attr.get(name) || (name in element ? direct : regular))
(attr.get(name) || (
name in element ?
(name.startsWith('on') ? direct : maybeDirect) :
regular
)
)
);
}
};
Expand All @@ -174,35 +238,7 @@ export const attribute = (element, name, svg) => {
* @param {T} value
* @returns {T}
*/
export const text = (element, value) => {
element.textContent = value == null ? '' : value;
return value;
};

/**
* @template T
* @this {import("./literals.js").HoleDetails}
* @param {Node} node
* @param {T} value
* @returns {T}
*/
export function hole(node, value) {
const n = this.n || (this.n = node);
switch (typeof value) {
case 'string':
case 'number':
case 'boolean': {
if (n !== node) n.replaceWith((this.n = node));
this.n.data = value;
break;
}
case 'object':
case 'undefined': {
if (value == null) (this.n = node).data = '';
else this.n = value.valueOf();
n.replaceWith(this.n);
break;
}
}
return value;
};
export const text = (element, value) => (
(element.textContent = value == null ? '' : value),
value
);

0 comments on commit f1cc16f

Please sign in to comment.