35 lines
1.5 KiB
JavaScript
35 lines
1.5 KiB
JavaScript
(() => {
|
|
const registrations = new Map();
|
|
let mounted = [];
|
|
|
|
function register(name, definition) {
|
|
if (!name || !definition) throw new TypeError("LumiPage.register requires a name and mount definition.");
|
|
registrations.set(name, typeof definition === "function" ? { mount: definition } : definition);
|
|
}
|
|
|
|
function unmount() {
|
|
for (const item of mounted.splice(0).reverse()) {
|
|
try { item.controller.abort(); } catch {}
|
|
try { item.teardown?.(); } catch (error) { console.error(`Lumi page teardown failed for ${item.name}.`, error); }
|
|
}
|
|
window.dispatchEvent(new CustomEvent("lumi:page-unmounted"));
|
|
}
|
|
|
|
function mount(root = document) {
|
|
unmount();
|
|
for (const [name, definition] of registrations) {
|
|
const selector = definition.selector || `[data-lumi-page~="${CSS.escape(name)}"]`;
|
|
const target = root.matches?.(selector) ? root : root.querySelector?.(selector);
|
|
if (!target || typeof definition.mount !== "function") continue;
|
|
const controller = new AbortController();
|
|
const context = { root: target, signal: controller.signal, pageRoot: root };
|
|
const result = definition.mount(target, context);
|
|
mounted.push({ name, controller, teardown: typeof result === "function" ? result : result?.destroy?.bind(result) });
|
|
}
|
|
window.dispatchEvent(new CustomEvent("lumi:page-mounted", { detail: { root } }));
|
|
}
|
|
|
|
window.LumiPage = Object.freeze({ register, mount, unmount });
|
|
document.addEventListener("DOMContentLoaded", () => mount(document), { once: true });
|
|
})();
|