459 lines
19 KiB
JavaScript
459 lines
19 KiB
JavaScript
// Generated, do not edit. sha256:db0c8db151ef8689662b07ab525d02d912551c1706e34e778f8bcf4190dc7dc0
|
|||
|
|
const VOID = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr']);
|
||
|
|
const RAW_TEXT = new Set(['textarea', 'script', 'style', 'title']);
|
||
|
|
const SELF_CLOSING_OK = new Set(['svg', 'path', 'line', 'circle', 'rect', 'polyline', 'polygon', 'g', 'use']);
|
||
|
|
|
||
|
|
const ENTITIES = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'" };
|
||
|
|
export function decodeEntities(s) {
|
||
|
|
return s.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (all, name) => {
|
||
|
|
if (name[0] === '#') {
|
||
|
|
const code = name[1] === 'x' || name[1] === 'X' ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10);
|
||
|
|
return Number.isFinite(code) ? String.fromCodePoint(code) : all;
|
||
|
|
}
|
||
|
|
return Object.prototype.hasOwnProperty.call(ENTITIES, name.toLowerCase()) ? ENTITIES[name.toLowerCase()] : all;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
const escText = s => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||
|
|
const escAttr = s => String(s).replace(/&/g, '&').replace(/"/g, '"');
|
||
|
|
|
||
|
|
const hidden = (obj, key, value) => Object.defineProperty(obj, key, { value, writable: true, enumerable: false, configurable: true });
|
||
|
|
|
||
|
|
class Node {
|
||
|
|
constructor(doc) { hidden(this, 'ownerDocument', doc); hidden(this, 'parentNode', null); this.childNodes = []; }
|
||
|
|
get parentElement() { return this.parentNode instanceof Element ? this.parentNode : null; }
|
||
|
|
get isConnected() {
|
||
|
|
let n = this;
|
||
|
|
while (n) { if (n === this.ownerDocument) return true; n = n.parentNode; }
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
appendChild(child) {
|
||
|
|
if (child.parentNode) child.parentNode._removeChild(child);
|
||
|
|
child.parentNode = this;
|
||
|
|
this.childNodes.push(child);
|
||
|
|
return child;
|
||
|
|
}
|
||
|
|
append(...children) { for (const c of children) this.appendChild(c); }
|
||
|
|
_removeChild(child) {
|
||
|
|
const i = this.childNodes.indexOf(child);
|
||
|
|
if (i >= 0) this.childNodes.splice(i, 1);
|
||
|
|
child.parentNode = null;
|
||
|
|
}
|
||
|
|
remove() { if (this.parentNode) this.parentNode._removeChild(this); }
|
||
|
|
contains(other) {
|
||
|
|
for (let n = other; n; n = n.parentNode) if (n === this) return true;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class Text extends Node {
|
||
|
|
constructor(doc, data) { super(doc); this.nodeType = 3; this.data = data; }
|
||
|
|
get textContent() { return this.data; }
|
||
|
|
set textContent(v) { this.data = String(v); }
|
||
|
|
}
|
||
|
|
|
||
|
|
class ClassList {
|
||
|
|
constructor(el) { this.el = el; }
|
||
|
|
_get() { return (this.el.getAttribute('class') || '').split(/\s+/).filter(Boolean); }
|
||
|
|
_set(list) { this.el.setAttribute('class', list.join(' ')); }
|
||
|
|
contains(c) { return this._get().includes(c); }
|
||
|
|
add(...cs) { const l = this._get(); for (const c of cs) if (c && !l.includes(c)) l.push(c); this._set(l); }
|
||
|
|
remove(...cs) { this._set(this._get().filter(c => !cs.includes(c))); }
|
||
|
|
toggle(c, force) {
|
||
|
|
const on = force === undefined ? !this.contains(c) : !!force;
|
||
|
|
if (on) this.add(c); else this.remove(c);
|
||
|
|
return on;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const kebab = s => s.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
||
|
|
const camel = s => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
||
|
|
|
||
|
|
function makeStyle() {
|
||
|
|
const props = new Map();
|
||
|
|
return new Proxy({
|
||
|
|
setProperty(k, v) { props.set(k, String(v)); },
|
||
|
|
getPropertyValue(k) { return props.get(k) ?? ''; },
|
||
|
|
removeProperty(k) { props.delete(k); },
|
||
|
|
}, {
|
||
|
|
get(target, key) { return key in target ? target[key] : (props.get(kebab(String(key))) ?? ''); },
|
||
|
|
set(_t, key, v) { props.set(kebab(String(key)), String(v)); return true; },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
export class Element extends Node {
|
||
|
|
constructor(doc, tag) {
|
||
|
|
super(doc);
|
||
|
|
this.nodeType = 1;
|
||
|
|
this.localName = tag.toLowerCase();
|
||
|
|
this.attrs = new Map();
|
||
|
|
hidden(this, 'style', makeStyle());
|
||
|
|
hidden(this, 'classList', new ClassList(this));
|
||
|
|
hidden(this, 'listeners', []);
|
||
|
|
this.scrollTop = 0;
|
||
|
|
this.scrollLeft = 0;
|
||
|
|
}
|
||
|
|
get tagName() { return this.localName.toUpperCase(); }
|
||
|
|
[Symbol.for('nodejs.util.inspect.custom')]() {
|
||
|
|
const attrs = [...this.attrs].map(([k, v]) => (v === '' ? ` ${k}` : ` ${k}="${v}"`)).join('');
|
||
|
|
return `<${this.localName}${attrs}>`;
|
||
|
|
}
|
||
|
|
getAttribute(n) { return this.attrs.has(n.toLowerCase()) ? this.attrs.get(n.toLowerCase()) : null; }
|
||
|
|
setAttribute(n, v) { this.attrs.set(n.toLowerCase(), String(v)); }
|
||
|
|
removeAttribute(n) { this.attrs.delete(n.toLowerCase()); }
|
||
|
|
hasAttribute(n) { return this.attrs.has(n.toLowerCase()); }
|
||
|
|
get id() { return this.getAttribute('id') || ''; }
|
||
|
|
set id(v) { this.setAttribute('id', v); }
|
||
|
|
get className() { return this.getAttribute('class') || ''; }
|
||
|
|
set className(v) { this.setAttribute('class', v); }
|
||
|
|
get title() { return this.getAttribute('title') || ''; }
|
||
|
|
set title(v) { this.setAttribute('title', v); }
|
||
|
|
get src() { return this.getAttribute('src') || ''; }
|
||
|
|
set src(v) { this.setAttribute('src', v); }
|
||
|
|
get type() { return this.localName === 'input' ? (this.getAttribute('type') || 'text').toLowerCase() : (this.getAttribute('type') || ''); }
|
||
|
|
set type(v) { this.setAttribute('type', v); }
|
||
|
|
get disabled() { return this.hasAttribute('disabled'); }
|
||
|
|
set disabled(v) { if (v) this.setAttribute('disabled', ''); else this.removeAttribute('disabled'); }
|
||
|
|
get dataset() {
|
||
|
|
const el = this;
|
||
|
|
return new Proxy({}, {
|
||
|
|
get(_t, key) { return typeof key === 'string' ? (el.getAttribute('data-' + kebab(key)) ?? undefined) : undefined; },
|
||
|
|
set(_t, key, v) { el.setAttribute('data-' + kebab(String(key)), v); return true; },
|
||
|
|
has(_t, key) { return el.hasAttribute('data-' + kebab(String(key))); },
|
||
|
|
ownKeys() { return [...el.attrs.keys()].filter(k => k.startsWith('data-')).map(k => camel(k.slice(5))); },
|
||
|
|
getOwnPropertyDescriptor(_t, key) {
|
||
|
|
const v = el.getAttribute('data-' + kebab(String(key)));
|
||
|
|
return v === null ? undefined : { value: v, enumerable: true, configurable: true };
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|
||
|
|
get children() { return this.childNodes.filter(n => n instanceof Element); }
|
||
|
|
|
||
|
|
get value() {
|
||
|
|
if (this.localName === 'select') {
|
||
|
|
if (this._value !== undefined) return this._value;
|
||
|
|
const opts = this.querySelectorAll('option');
|
||
|
|
const sel = opts.find(o => o.hasAttribute('selected')) || opts[0];
|
||
|
|
return sel ? sel.value : '';
|
||
|
|
}
|
||
|
|
if (this.localName === 'option') return this.getAttribute('value') ?? this.textContent;
|
||
|
|
if (this.localName === 'textarea') return this._value !== undefined ? this._value : this.textContent;
|
||
|
|
return this._value !== undefined ? this._value : (this.getAttribute('value') ?? '');
|
||
|
|
}
|
||
|
|
set value(v) { this._value = String(v); }
|
||
|
|
get checked() { return this._checked !== undefined ? this._checked : this.hasAttribute('checked'); }
|
||
|
|
set checked(v) { this._checked = !!v; }
|
||
|
|
get selectionStart() { return this._selStart ?? this.value.length; }
|
||
|
|
get selectionEnd() { return this._selEnd ?? this.value.length; }
|
||
|
|
setSelectionRange(a, b) { this._selStart = a; this._selEnd = b; }
|
||
|
|
|
||
|
|
get offsetHeight() { return 0; }
|
||
|
|
get offsetWidth() { return 0; }
|
||
|
|
get scrollHeight() { return 0; }
|
||
|
|
get clientHeight() { return 0; }
|
||
|
|
get clientWidth() { return 0; }
|
||
|
|
getBoundingClientRect() { return { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 }; }
|
||
|
|
scrollIntoView(opts) { this.ownerDocument.scrolledIntoView.push({ el: this, opts }); }
|
||
|
|
focus() { this.ownerDocument.activeElement = this; }
|
||
|
|
blur() { if (this.ownerDocument.activeElement === this) this.ownerDocument.activeElement = this.ownerDocument.body; }
|
||
|
|
|
||
|
|
get innerHTML() { return this.childNodes.map(serialize).join(''); }
|
||
|
|
set innerHTML(html) {
|
||
|
|
for (const c of [...this.childNodes]) this._removeChild(c);
|
||
|
|
for (const c of parseHTML(this.ownerDocument, String(html), this.localName)) this.appendChild(c);
|
||
|
|
this._value = undefined;
|
||
|
|
}
|
||
|
|
get outerHTML() { return serialize(this); }
|
||
|
|
get textContent() { return this.childNodes.map(n => n.textContent).join(''); }
|
||
|
|
set textContent(v) {
|
||
|
|
for (const c of [...this.childNodes]) this._removeChild(c);
|
||
|
|
if (v !== '' && v != null) this.appendChild(new Text(this.ownerDocument, String(v)));
|
||
|
|
}
|
||
|
|
|
||
|
|
querySelectorAll(sel) {
|
||
|
|
const list = parseSelector(sel);
|
||
|
|
const out = [];
|
||
|
|
walk(this, el => { if (list.some(s => matchComplex(el, s, this))) out.push(el); });
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
querySelector(sel) { return this.querySelectorAll(sel)[0] || null; }
|
||
|
|
matches(sel) { return parseSelector(sel).some(s => matchComplex(this, s, null)); }
|
||
|
|
closest(sel) {
|
||
|
|
const list = parseSelector(sel);
|
||
|
|
for (let n = this; n instanceof Element; n = n.parentNode) if (list.some(s => matchComplex(n, s, null))) return n;
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
addEventListener(type, fn, opts) { this.listeners.push({ type, fn, capture: opts === true || !!(opts && opts.capture) }); }
|
||
|
|
removeEventListener(type, fn, opts) {
|
||
|
|
const capture = opts === true || !!(opts && opts.capture);
|
||
|
|
this.listeners = this.listeners.filter(l => !(l.type === type && l.fn === fn && l.capture === capture));
|
||
|
|
}
|
||
|
|
dispatchEvent(ev) { return dispatch(this, ev.type, { bubbles: ev.bubbles, ...pickInit(ev) }); }
|
||
|
|
}
|
||
|
|
|
||
|
|
function pickInit(ev) {
|
||
|
|
const init = {};
|
||
|
|
for (const k of ['key', 'ctrlKey', 'metaKey', 'shiftKey', 'altKey', 'relatedTarget', 'detail']) if (ev[k] !== undefined) init[k] = ev[k];
|
||
|
|
return init;
|
||
|
|
}
|
||
|
|
|
||
|
|
function walk(root, fn) {
|
||
|
|
for (const c of root.childNodes) {
|
||
|
|
if (c instanceof Element) { fn(c); walk(c, fn); }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function serialize(n) {
|
||
|
|
if (n instanceof Text) return escText(n.data);
|
||
|
|
if (!(n instanceof Element)) return '';
|
||
|
|
const attrs = [...n.attrs].map(([k, v]) => (v === '' ? ` ${k}` : ` ${k}="${escAttr(v)}"`)).join('');
|
||
|
|
if (VOID.has(n.localName)) return `<${n.localName}${attrs}>`;
|
||
|
|
return `<${n.localName}${attrs}>${n.childNodes.map(serialize).join('')}</${n.localName}>`;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function parseHTML(doc, html) {
|
||
|
|
const root = new Element(doc, '#fragment');
|
||
|
|
const stack = [root];
|
||
|
|
const top = () => stack[stack.length - 1];
|
||
|
|
let i = 0;
|
||
|
|
while (i < html.length) {
|
||
|
|
if (html.startsWith('<!--', i)) {
|
||
|
|
const end = html.indexOf('-->', i + 4);
|
||
|
|
i = end < 0 ? html.length : end + 3;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (html[i] === '<' && html[i + 1] === '/') {
|
||
|
|
const end = html.indexOf('>', i);
|
||
|
|
const name = html.slice(i + 2, end < 0 ? html.length : end).trim().toLowerCase();
|
||
|
|
i = end < 0 ? html.length : end + 1;
|
||
|
|
const at = stack.map(n => n.localName).lastIndexOf(name);
|
||
|
|
if (at > 0) stack.length = at;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (html[i] === '<' && /[a-zA-Z]/.test(html[i + 1] || '')) {
|
||
|
|
let j = i + 1, quote = null;
|
||
|
|
for (; j < html.length; j++) {
|
||
|
|
const ch = html[j];
|
||
|
|
if (quote) { if (ch === quote) quote = null; continue; }
|
||
|
|
if (ch === '"' || ch === "'") { quote = ch; continue; }
|
||
|
|
if (ch === '>') break;
|
||
|
|
}
|
||
|
|
let body = html.slice(i + 1, j);
|
||
|
|
i = j + 1;
|
||
|
|
const selfClose = body.endsWith('/');
|
||
|
|
if (selfClose) body = body.slice(0, -1);
|
||
|
|
const name = body.match(/^[^\s/>]+/)[0].toLowerCase();
|
||
|
|
const el = new Element(doc, name);
|
||
|
|
const attrRe = /([^\s"'>\/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
||
|
|
const rest = body.slice(name.length);
|
||
|
|
let m;
|
||
|
|
while ((m = attrRe.exec(rest))) {
|
||
|
|
const v = m[2] ?? m[3] ?? m[4] ?? '';
|
||
|
|
el.setAttribute(m[1], decodeEntities(v));
|
||
|
|
}
|
||
|
|
top().appendChild(el);
|
||
|
|
if (VOID.has(name) || (selfClose && SELF_CLOSING_OK.has(name))) continue;
|
||
|
|
if (RAW_TEXT.has(name)) {
|
||
|
|
const close = html.toLowerCase().indexOf(`</${name}`, i);
|
||
|
|
const raw = html.slice(i, close < 0 ? html.length : close);
|
||
|
|
if (raw) el.appendChild(new Text(doc, decodeEntities(raw)));
|
||
|
|
const end = close < 0 ? html.length : html.indexOf('>', close);
|
||
|
|
i = end < 0 ? html.length : end + 1;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
stack.push(el);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const next = html.indexOf('<', i + 1);
|
||
|
|
const text = html.slice(i, next < 0 ? html.length : next);
|
||
|
|
i = next < 0 ? html.length : next;
|
||
|
|
if (text) top().appendChild(new Text(doc, decodeEntities(text)));
|
||
|
|
}
|
||
|
|
const nodes = [...root.childNodes];
|
||
|
|
for (const n of nodes) root._removeChild(n);
|
||
|
|
return nodes;
|
||
|
|
}
|
||
|
|
|
||
|
|
const cache = new Map();
|
||
|
|
export function parseSelector(sel) {
|
||
|
|
if (cache.has(sel)) return cache.get(sel);
|
||
|
|
const list = splitTop(sel, ',').map(s => s.trim()).filter(Boolean).map(parseComplex);
|
||
|
|
if (!list.length) throw new Error(`mini-dom: empty selector "${sel}"`);
|
||
|
|
cache.set(sel, list);
|
||
|
|
return list;
|
||
|
|
}
|
||
|
|
|
||
|
|
function splitTop(s, sep) {
|
||
|
|
const out = [];
|
||
|
|
let depth = 0, quote = null, start = 0;
|
||
|
|
for (let i = 0; i < s.length; i++) {
|
||
|
|
const ch = s[i];
|
||
|
|
if (quote) { if (ch === quote) quote = null; continue; }
|
||
|
|
if (ch === '"' || ch === "'") quote = ch;
|
||
|
|
else if (ch === '(' || ch === '[') depth++;
|
||
|
|
else if (ch === ')' || ch === ']') depth--;
|
||
|
|
else if (ch === sep && depth === 0) { out.push(s.slice(start, i)); start = i + 1; }
|
||
|
|
}
|
||
|
|
out.push(s.slice(start));
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseComplex(s) {
|
||
|
|
const parts = [];
|
||
|
|
let i = 0;
|
||
|
|
let combinator = null;
|
||
|
|
while (i < s.length) {
|
||
|
|
if (/\s/.test(s[i]) || s[i] === '>') {
|
||
|
|
let comb = ' ';
|
||
|
|
while (i < s.length && (/\s/.test(s[i]) || s[i] === '>')) { if (s[i] === '>') comb = '>'; i++; }
|
||
|
|
if (parts.length) combinator = comb;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const [compound, next] = parseCompound(s, i);
|
||
|
|
if (parts.length) parts[parts.length - 1].combinator = combinator || ' ';
|
||
|
|
parts.push({ compound, combinator: null });
|
||
|
|
combinator = null;
|
||
|
|
i = next;
|
||
|
|
}
|
||
|
|
return parts;
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseCompound(s, i) {
|
||
|
|
const c = { tag: null, id: null, classes: [], attrs: [], nots: [] };
|
||
|
|
const ident = (k) => { const m = s.slice(k).match(/^[A-Za-z0-9_-]+/); if (!m) throw new Error(`mini-dom: unsupported selector "${s}"`); return m[0]; };
|
||
|
|
if (s[i] === '*') i++;
|
||
|
|
else if (/[A-Za-z]/.test(s[i])) { c.tag = ident(i).toLowerCase(); i += c.tag.length; }
|
||
|
|
while (i < s.length && !/[\s>]/.test(s[i])) {
|
||
|
|
const ch = s[i];
|
||
|
|
if (ch === '#') { const n = ident(i + 1); c.id = n; i += 1 + n.length; }
|
||
|
|
else if (ch === '.') { const n = ident(i + 1); c.classes.push(n); i += 1 + n.length; }
|
||
|
|
else if (ch === '[') {
|
||
|
|
const end = findClose(s, i, '[', ']');
|
||
|
|
const inner = s.slice(i + 1, end).trim();
|
||
|
|
const m = inner.match(/^([A-Za-z0-9_:-]+)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^\s\]]+)))?$/);
|
||
|
|
if (!m) throw new Error(`mini-dom: unsupported attribute selector "[${inner}]"`);
|
||
|
|
c.attrs.push({ name: m[1].toLowerCase(), value: m[2] ?? m[3] ?? m[4] ?? null });
|
||
|
|
i = end + 1;
|
||
|
|
} else if (s.startsWith(':not(', i)) {
|
||
|
|
const end = findClose(s, i + 4, '(', ')');
|
||
|
|
c.nots.push(parseSelector(s.slice(i + 5, end)));
|
||
|
|
i = end + 1;
|
||
|
|
} else {
|
||
|
|
throw new Error(`mini-dom: unsupported selector "${s}" at "${s.slice(i)}"`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return [c, i];
|
||
|
|
}
|
||
|
|
|
||
|
|
function findClose(s, open, a, b) {
|
||
|
|
let depth = 0, quote = null;
|
||
|
|
for (let k = open; k < s.length; k++) {
|
||
|
|
const ch = s[k];
|
||
|
|
if (quote) { if (ch === quote) quote = null; continue; }
|
||
|
|
if (ch === '"' || ch === "'") quote = ch;
|
||
|
|
else if (ch === a) depth++;
|
||
|
|
else if (ch === b && --depth === 0) return k;
|
||
|
|
}
|
||
|
|
throw new Error(`mini-dom: unclosed "${a}" in selector "${s}"`);
|
||
|
|
}
|
||
|
|
|
||
|
|
function matchCompound(el, c) {
|
||
|
|
if (!(el instanceof Element)) return false;
|
||
|
|
if (c.tag && el.localName !== c.tag) return false;
|
||
|
|
if (c.id && el.getAttribute('id') !== c.id) return false;
|
||
|
|
for (const k of c.classes) if (!el.classList.contains(k)) return false;
|
||
|
|
for (const a of c.attrs) {
|
||
|
|
if (!el.hasAttribute(a.name)) return false;
|
||
|
|
if (a.value !== null && el.getAttribute(a.name) !== a.value) return false;
|
||
|
|
}
|
||
|
|
for (const list of c.nots) if (list.some(s => matchComplex(el, s, null))) return false;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
function matchComplex(el, parts, _scope) {
|
||
|
|
const at = (k, node) => {
|
||
|
|
if (!matchCompound(node, parts[k].compound)) return false;
|
||
|
|
if (k === 0) return true;
|
||
|
|
const comb = parts[k - 1].combinator;
|
||
|
|
if (comb === '>') return node.parentNode instanceof Element && at(k - 1, node.parentNode);
|
||
|
|
for (let p = node.parentNode; p instanceof Element; p = p.parentNode) if (at(k - 1, p)) return true;
|
||
|
|
return false;
|
||
|
|
};
|
||
|
|
return at(parts.length - 1, el);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function dispatch(target, type, init = {}) {
|
||
|
|
const doc = target.ownerDocument || target;
|
||
|
|
let stopped = false;
|
||
|
|
const ev = {
|
||
|
|
type, target, bubbles: init.bubbles !== false, defaultPrevented: false, currentTarget: null,
|
||
|
|
key: init.key, ctrlKey: !!init.ctrlKey, metaKey: !!init.metaKey, shiftKey: !!init.shiftKey, altKey: !!init.altKey,
|
||
|
|
relatedTarget: init.relatedTarget ?? null, detail: init.detail ?? null,
|
||
|
|
stopPropagation() { stopped = true; },
|
||
|
|
stopImmediatePropagation() { stopped = true; },
|
||
|
|
preventDefault() { this.defaultPrevented = true; },
|
||
|
|
};
|
||
|
|
const path = [];
|
||
|
|
for (let n = target; n; n = n.parentNode) path.push(n);
|
||
|
|
if (path[path.length - 1] !== doc) path.push(doc);
|
||
|
|
const run = (node, capture) => {
|
||
|
|
ev.currentTarget = node;
|
||
|
|
if (!capture && typeof node['on' + type] === 'function') node['on' + type](ev);
|
||
|
|
for (const l of [...(node.listeners || [])]) if (l.type === type && l.capture === capture) l.fn(ev);
|
||
|
|
};
|
||
|
|
for (let k = path.length - 1; k > 0 && !stopped; k--) run(path[k], true);
|
||
|
|
if (!stopped) { run(target, true); if (!stopped) run(target, false); }
|
||
|
|
if (ev.bubbles) for (let k = 1; k < path.length && !stopped; k++) run(path[k], false);
|
||
|
|
return !ev.defaultPrevented;
|
||
|
|
}
|
||
|
|
|
||
|
|
class Document extends Node {
|
||
|
|
constructor() {
|
||
|
|
super(null);
|
||
|
|
this.ownerDocument = this;
|
||
|
|
this.listeners = [];
|
||
|
|
this.documentElement = new Element(this, 'html');
|
||
|
|
this.head = new Element(this, 'head');
|
||
|
|
this.body = new Element(this, 'body');
|
||
|
|
this.appendChild(this.documentElement);
|
||
|
|
this.documentElement.appendChild(this.head);
|
||
|
|
this.documentElement.appendChild(this.body);
|
||
|
|
this.activeElement = this.body;
|
||
|
|
this.scrolledIntoView = [];
|
||
|
|
}
|
||
|
|
[Symbol.for('nodejs.util.inspect.custom')]() { return '#document'; }
|
||
|
|
createElement(tag) { return new Element(this, tag); }
|
||
|
|
createTextNode(t) { return new Text(this, t); }
|
||
|
|
querySelectorAll(sel) { return this.documentElement.querySelectorAll(sel); }
|
||
|
|
querySelector(sel) { return this.documentElement.querySelector(sel); }
|
||
|
|
getElementById(id) { return this.querySelector(`#${id}`); }
|
||
|
|
addEventListener(type, fn, opts) { Element.prototype.addEventListener.call(this, type, fn, opts); }
|
||
|
|
removeEventListener(type, fn, opts) { Element.prototype.removeEventListener.call(this, type, fn, opts); }
|
||
|
|
dispatchEvent(ev) { return dispatch(this, ev.type, { bubbles: ev.bubbles, ...pickInit(ev) }); }
|
||
|
|
}
|
||
|
|
|
||
|
|
export function installDom() {
|
||
|
|
const document = new Document();
|
||
|
|
const frames = [];
|
||
|
|
const win = {
|
||
|
|
innerWidth: 1280, innerHeight: 800, listeners: [],
|
||
|
|
addEventListener(type, fn, opts) { Element.prototype.addEventListener.call(this, type, fn, opts); },
|
||
|
|
removeEventListener(type, fn, opts) { Element.prototype.removeEventListener.call(this, type, fn, opts); },
|
||
|
|
};
|
||
|
|
globalThis.document = document;
|
||
|
|
globalThis.window = win;
|
||
|
|
globalThis.requestAnimationFrame = fn => { frames.push(fn); return frames.length; };
|
||
|
|
globalThis.getComputedStyle = el => ({ getPropertyValue: k => el.style.getPropertyValue(k) });
|
||
|
|
const fire = (el, type, init) => dispatch(el, type, init);
|
||
|
|
return {
|
||
|
|
document,
|
||
|
|
window: win,
|
||
|
|
fire,
|
||
|
|
click: el => fire(el, 'click'),
|
||
|
|
type(el, value) { el.value = value; fire(el, 'input'); fire(el, 'change'); },
|
||
|
|
choose(el, value) { el.value = value; fire(el, 'change'); },
|
||
|
|
key: (el, key, mods = {}) => fire(el, 'keydown', { key, ...mods }),
|
||
|
|
flushFrames() { while (frames.length) frames.shift()(); },
|
||
|
|
host() { const el = document.createElement('div'); document.body.appendChild(el); return el; },
|
||
|
|
};
|
||
|
|
}
|