folk-canvas/src/common/iframe-script.ts

120 lines
3.3 KiB
TypeScript

import { FolkShape } from '../folk-shape.ts';
import { ClientRectObserverManager, ClientRectObserverEntry } from './client-rect-observer.ts';
import { TransformEvent } from './TransformEvent.ts';
const clientRectObserver = new ClientRectObserverManager();
interface ObservedElementEntry {
selector: string;
element: Element;
count: number;
}
class ObservedElements {
#elements: ObservedElementEntry[] = [];
observe(selector: string) {
let entry = this.#elements.find((e) => e.selector === selector);
if (entry === undefined) {
entry = { selector, element: document.querySelector(selector)!, count: 0 };
this.#elements.push(entry);
}
entry.count += 1;
return entry.element;
}
unobserve(selector: string) {
const entryIndex = this.#elements.findIndex((e) => e.selector === selector);
const entry = this.#elements[entryIndex];
if (entry === undefined) return;
entry.count -= 1;
if (entry.count === 0) {
this.#elements.splice(entryIndex, 1);
}
}
getElement(selector: string) {
return this.#elements.find((e) => e.selector === selector)?.element;
}
getSelector(element: Element) {
return this.#elements.find((e) => e.element === element)?.selector;
}
}
// If this page is framed in then mock inject the following post message script
if (window.parent !== window) {
// keep track of count of elements being observed
const observedElements = new Map();
const observedSelectors = new Map();
function boundingBoxCallback(entry: ClientRectObserverEntry) {
window.parent.postMessage({
type: 'folk-element-change',
selector: observedSelectors.get(entry.target),
boundingBox: entry.contentRect,
});
}
function onGeometryChange(event: TransformEvent) {
window.parent.postMessage({
type: 'folk-element-change',
selector: observedSelectors.get(event.target),
boundingBox: (event.target as FolkShape)?.getTransformDOMRect(),
});
}
window.addEventListener('message', (event) => {
switch (event.data.type) {
case 'folk-observe-element': {
const selector = event.data.selector;
const element = document.querySelector(selector);
if (element === null) return;
observedElements.set(selector, element);
observedSelectors.set(element, selector);
if (element instanceof FolkShape) {
element.addEventListener('transform', onGeometryChange);
window.parent.postMessage({
type: 'folk-element-change',
selector: selector,
boundingBox: element.getTransformDOMRect(),
});
} else {
clientRectObserver.observe(element, boundingBoxCallback);
}
return;
}
case 'folk-unobserve-element': {
const selector = event.data.selector;
const element = observedElements.get(selector);
if (element === undefined) return;
if (element instanceof FolkShape) {
element.removeEventListener('transform', onGeometryChange);
observedElements.delete(selector);
observedSelectors.delete(element);
} else {
clientRectObserver.unobserve(element, boundingBoxCallback);
}
return;
}
}
});
window.parent.postMessage({
type: 'folk-iframe-ready',
});
}