Files
fieldwork/dist/js/dom.js
T
jzzliu 73fcc0548c Rework storefront into modules and tokenised styles
Split the 100-line script into catalog, cart, format, dom and main modules.
Money is integer cents, the bag is capped and sanitised and syncs across
tabs, and the view (category, sort) lives in the URL. Templates escape every
interpolation.

Rewrite the minified stylesheet as cascade layers on design tokens. Fixes
heading tracking that collapsed word gaps, a 2.6:1 focus ring on the hero,
1.3:1 control borders, misaligned cart quantities, uneven product cards and
undersized touch targets. Adds a favicon and a resolvable dialog label.
2026-11-07 21:18:38 -07:00

51 lines
1.7 KiB
JavaScript

/**
* Tiny HTML templating with automatic escaping.
*
* Every interpolated value is escaped unless it is itself the result of `html` (or `raw`),
* so product names, ids and any future user-supplied text can never inject markup.
*
* html`<h3>${name}</h3>` // name is escaped
* html`<ul>${items.map(i => html`<li>${i}</li>`)}</ul>` // nested templates are not double-escaped
*/
class SafeHtml {
constructor(value) {
this.value = value;
}
toString() {
return this.value;
}
}
const ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
export function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, (ch) => ESCAPES[ch]);
}
function toHtml(value) {
if (value instanceof SafeHtml) return value.value;
if (Array.isArray(value)) return value.map(toHtml).join("");
// null/undefined render as nothing. `false` deliberately renders as "false" so boolean
// attributes such as aria-pressed="${isActive}" work; use `cond ? x : ""` for optional chunks.
if (value === null || value === undefined) return "";
return escapeHtml(value);
}
/** Mark a trusted string (e.g. a static attribute) as already-safe markup. */
export const raw = (value) => new SafeHtml(String(value));
export function html(strings, ...values) {
let out = strings[0];
for (let i = 0; i < values.length; i++) out += toHtml(values[i]) + strings[i + 1];
return new SafeHtml(out);
}
/** Replace an element's contents. Only accepts `html` results, never plain strings. */
export function mount(element, template) {
if (!(template instanceof SafeHtml)) {
throw new TypeError("mount() needs an html`...` template; plain strings are refused");
}
element.innerHTML = template.value;
}