77 lines
2 KiB
TypeScript
77 lines
2 KiB
TypeScript
import React from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
|
|
type InteractiveProps = {
|
|
id: string;
|
|
label: string;
|
|
caption?: string;
|
|
description?: string;
|
|
fallback?: string;
|
|
attrs?: Record<string, string>;
|
|
};
|
|
|
|
type InteractiveModule = {
|
|
default: React.ComponentType<InteractiveProps>;
|
|
};
|
|
|
|
const modules = import.meta.glob<InteractiveModule>("./*/index.tsx");
|
|
|
|
function readProps(element: HTMLElement): InteractiveProps {
|
|
const encoded = element.dataset.interactiveProps;
|
|
if (!encoded) {
|
|
return {
|
|
id: element.dataset.interactiveId ?? "",
|
|
label: "",
|
|
};
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(decodeURIComponent(encoded)) as InteractiveProps;
|
|
} catch {
|
|
return {
|
|
id: element.dataset.interactiveId ?? "",
|
|
label: "",
|
|
fallback: element.textContent?.trim() ?? "",
|
|
};
|
|
}
|
|
}
|
|
|
|
async function mountInteractive(element: HTMLElement) {
|
|
const id = element.dataset.interactiveId;
|
|
if (!id || element.dataset.interactiveMounted) return;
|
|
|
|
const modulePath = `./${id}/index.tsx`;
|
|
const loader = modules[modulePath];
|
|
if (!loader) {
|
|
element.dataset.interactiveState = "missing-component";
|
|
return;
|
|
}
|
|
|
|
element.dataset.interactiveMounted = "true";
|
|
element.dataset.interactiveState = "loading";
|
|
|
|
try {
|
|
const props = readProps(element);
|
|
const { default: Component } = await loader();
|
|
element.replaceChildren();
|
|
createRoot(element).render(<Component {...props} />);
|
|
element.dataset.interactiveState = "ready";
|
|
} catch (error) {
|
|
element.dataset.interactiveState = "failed";
|
|
console.error(`Failed to mount interactive "${id}"`, error);
|
|
}
|
|
}
|
|
|
|
function mountAllInteractives() {
|
|
document
|
|
.querySelectorAll<HTMLElement>(".interactive-mount[data-interactive-id]")
|
|
.forEach((element) => {
|
|
void mountInteractive(element);
|
|
});
|
|
}
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", mountAllInteractives, { once: true });
|
|
} else {
|
|
mountAllInteractives();
|
|
}
|