// Bridge to the numbl/MATLAB `uihtml` host. The host calls a global
// `setup(htmlComponent)` once the page loads; we expose small subscribe/send
// helpers so React components don't race that callback (latest Data is buffered,
// and host-event listeners attach whenever the component becomes available).
interface HtmlComponent {
Data: unknown;
addEventListener(name: string, fn: (e: { Data?: unknown }) => void): void;
sendEventToMATLAB(name: string, data: unknown): void;
}
type DataListener = (data: unknown) => void;
type EventListener = (data: unknown) => void;
let htmlComponent: HtmlComponent | null = null;
let latestData: unknown = undefined;
const dataListeners = new Set();
const eventListeners = new Map>();
const attachedNames = new Set();
function deliverData(d: unknown): void {
latestData = d;
dataListeners.forEach(fn => fn(d));
}
/** Attach a single host listener for `name` that fans out to our listener set. */
function attach(name: string): void {
if (!htmlComponent || attachedNames.has(name)) return;
attachedNames.add(name);
htmlComponent.addEventListener(name, e => {
const fns = eventListeners.get(name);
if (fns) fns.forEach(fn => fn(e?.Data));
});
}
// Register the global the host calls after the page loads.
(window as unknown as { setup: (hc: HtmlComponent) => void }).setup = hc => {
htmlComponent = hc;
hc.addEventListener("DataChanged", () => deliverData(hc.Data));
for (const name of eventListeners.keys()) attach(name);
if (hc.Data != null) deliverData(hc.Data);
};
/** Subscribe to the `Data` channel (script → page). Fires immediately with the
* latest data if it has already arrived. */
export function onData(fn: DataListener): () => void {
dataListeners.add(fn);
if (latestData !== undefined) fn(latestData);
return () => {
dataListeners.delete(fn);
};
}
/** Subscribe to a named host event (from MATLAB `sendEventToHTMLSource`). */
export function onHostEvent(name: string, fn: EventListener): () => void {
let set = eventListeners.get(name);
if (!set) {
set = new Set();
eventListeners.set(name, set);
}
set.add(fn);
attach(name);
return () => {
set!.delete(fn);
};
}
/** Send an event back to the interpreter (page → script). */
export function sendToMATLAB(name: string, data: unknown): void {
htmlComponent?.sendEventToMATLAB(name, data);
}