Files
TORCH/site/live-refresh.js
T

52 lines
2.4 KiB
JavaScript

(() => {
function createScheduler({ load, apply, onError = () => {} }) {
let generation = 0;
let running = null;
function request({ supersede = true } = {}) {
if (running && !supersede) return running;
generation += 1;
if (running) return running;
running = (async () => {
let observed;
do {
observed = generation;
try {
const snapshot = await load();
if (observed === generation) apply(snapshot);
} catch (error) { if (observed === generation) onError(error); }
} while (observed !== generation);
})().finally(() => { running = null; });
return running;
}
return Object.freeze({ request });
}
function createProtection(document) {
const baseline = new WeakMap();
const fields = (element) => [...element.querySelectorAll('input,select,textarea')];
const signature = (field) => JSON.stringify({ value: field.value, checked: field.checked,
selected: field.tagName === 'SELECT' ? [...field.selectedOptions].map((option) => option.value) : null });
const dirty = () => new Set(fields(document).filter((field) => baseline.has(field) && baseline.get(field) !== signature(field)));
const remember = ({ preserve = new Set() } = {}) => fields(document).forEach((field) => {
if (!preserve.has(field)) baseline.set(field, signature(field));
});
function protects(element) {
if (!element) return false;
const focused = document.activeElement;
if (element.contains(focused) && focused?.matches('input,select,textarea,button,a,summary')) return true;
if (element.matches('[data-preview-token]') || element.querySelector('[data-preview-token]')) return true;
return fields(element).some((field) => baseline.has(field) && baseline.get(field) !== signature(field));
}
function complete(element) {
if (!element) return;
fields(element).forEach((field) => baseline.set(field, signature(field)));
[element, ...element.querySelectorAll('[data-preview-token]')].forEach((node) => {
delete node.dataset.previewToken;
delete node.dataset.planHash;
});
if (element.contains(document.activeElement)) document.activeElement.blur();
}
return Object.freeze({ remember, dirty, protects, complete });
}
globalThis.TorchLiveRefresh = Object.freeze({ createScheduler, createProtection });
})();