UNPKG

window-page

Version:

Route, setup, and build web pages

165 lines (152 loc) 3.31 kB
let domReady = false; function readyLsn() { if (!domReady) { domReady = true; return true; } } function once(emitter, events, filter) { if (!Array.isArray(events)) events = [events]; const d = Promise.withResolvers(); const listener = (e) => { if (!filter || filter(e)) { for (const event of events) emitter.removeEventListener(event, listener); d.resolve(emitter); } }; for (const event of events) emitter.addEventListener(event, listener); return d.promise; } export function domDeferred() { if (domReady) return; const d = Promise.withResolvers(); if (document.readyState == "complete") { domReady = true; setTimeout(d.resolve); return d.promise; } return Promise.race([ once(document, 'DOMContentLoaded', readyLsn), once(window, 'load', readyLsn) ]); } export class Queue { #list = []; #on = false; #resolve; done; started = false; stopped = false; constructor() { this.count = 0; const { promise, resolve } = Promise.withResolvers(); this.done = promise; this.#resolve = resolve; } get length() { return this.#list.length + (this.#on ? 1 : 0); } queue(job) { this.stopped = false; const d = Promise.withResolvers(); const p = d.promise.then(() => job()).finally(() => { this.#on = false; this.dequeue(); }); this.#list.push(d); this.dequeue(); return p; } dequeue() { this.started = true; if (this.#on) { return; } const d = this.#list.shift(); if (d) { this.count++; this.#on = true; d.resolve(); } else { this.stopped = true; this.#resolve(); } } } function isDocVisible() { return !document.hidden || document.visibilityState == "visible"; } export class UiQueue { // run last job when doc is visible, then run all jobs #d = Promise.withResolvers(); #job; constructor() { this.#d.promise.then(() => { this.#d = null; const job = this.#job; if (job) { this.#job = null; return job(); } }); if (!isDocVisible()) { once(document, 'visibilitychange', isDocVisible).then(this.#d.resolve); } else { setTimeout(this.#d.resolve, 0); } } run(job) { if (this.#d) { this.#job = job; } else { job(); this.#job = null; } } } export function waitStyles() { return Promise.all( Array.from(document.head.querySelectorAll('link[rel="stylesheet"]')) .map(node => waitSheet(node)) ); } function waitSheet(link) { let ok = false; try { ok = link.sheet && link.sheet.cssRules; } catch(ex) { // bail out ok = true; } if (ok) return; return once(link, ['load', 'error']); } export function loadNode(node) { const tag = node.nodeName; const isScript = tag == "SCRIPT"; const copy = node.ownerDocument.createElement(tag); for (const attr of node.attributes) { let { name } = attr; if (isScript) { if (name == "type") { continue; } else if (name == "priv-type") { name = "type"; } } else if (name == "rel") { continue; } else if (name == "priv-rel") { name = "rel"; } copy.setAttribute(name, attr.value); } if (isScript && node.textContent && !node.src) { copy.textContent = node.textContent; node.parentNode.replaceChild(copy, node); return Promise.resolve(copy); } else { const p = once(copy, ['load', 'error']); node.parentNode.replaceChild(copy, node); return p; } }