concept-collection / surfacefun-interactive
71 lines · 2.4 KBBlameHistoryRaw
1// Bridge to the numbl/MATLAB `uihtml` host. The host calls a global
2// `setup(htmlComponent)` once the page loads; we expose small subscribe/send
3// helpers so React components don't race that callback (latest Data is buffered,
4// and host-event listeners attach whenever the component becomes available).
6interface HtmlComponent {
7 Data: unknown;
8 addEventListener(name: string, fn: (e: { Data?: unknown }) => void): void;
9 sendEventToMATLAB(name: string, data: unknown): void;
12type DataListener = (data: unknown) => void;
13type EventListener = (data: unknown) => void;
15let htmlComponent: HtmlComponent | null = null;
16let latestData: unknown = undefined;
17const dataListeners = new Set<DataListener>();
18const eventListeners = new Map<string, Set<EventListener>>();
19const attachedNames = new Set<string>();
21function deliverData(d: unknown): void {
22 latestData = d;
23 dataListeners.forEach(fn => fn(d));
26/** Attach a single host listener for `name` that fans out to our listener set. */
27function attach(name: string): void {
28 if (!htmlComponent || attachedNames.has(name)) return;
29 attachedNames.add(name);
30 htmlComponent.addEventListener(name, e => {
31 const fns = eventListeners.get(name);
32 if (fns) fns.forEach(fn => fn(e?.Data));
33 });
36// Register the global the host calls after the page loads.
37(window as unknown as { setup: (hc: HtmlComponent) => void }).setup = hc => {
38 htmlComponent = hc;
39 hc.addEventListener("DataChanged", () => deliverData(hc.Data));
40 for (const name of eventListeners.keys()) attach(name);
41 if (hc.Data != null) deliverData(hc.Data);
42};
44/** Subscribe to the `Data` channel (script → page). Fires immediately with the
45 * latest data if it has already arrived. */
46export function onData(fn: DataListener): () => void {
47 dataListeners.add(fn);
48 if (latestData !== undefined) fn(latestData);
49 return () => {
50 dataListeners.delete(fn);
51 };
54/** Subscribe to a named host event (from MATLAB `sendEventToHTMLSource`). */
55export function onHostEvent(name: string, fn: EventListener): () => void {
56 let set = eventListeners.get(name);
57 if (!set) {
58 set = new Set();
59 eventListeners.set(name, set);
60 }
61 set.add(fn);
62 attach(name);
63 return () => {
64 set!.delete(fn);
65 };
68/** Send an event back to the interpreter (page → script). */
69export function sendToMATLAB(name: string, data: unknown): void {
70 htmlComponent?.sendEventToMATLAB(name, data);