json-joy
Version:
Collection of libraries for building collaborative editing apps.
101 lines (100 loc) • 3.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EntangledPortal = void 0;
const tslib_1 = require("tslib");
const React = tslib_1.__importStar(require("react"));
const rxjs_1 = require("rxjs");
const rect_1 = require("../util/rect$");
const EditorPortal_1 = require("../web/react/util/EditorPortal");
class EntangledPortalState {
constructor(opts) {
this.opts = opts;
this.baseEl = null;
this.destEl = null;
this.baseSub = void 0;
this.destSub = void 0;
this.baseRect$ = new rxjs_1.BehaviorSubject(void 0);
this.entangle = () => {
const { destEl, opts } = this;
if (!destEl)
return;
const baseRect = this.baseRect$.getValue();
if (!baseRect)
return;
let { x, y } = baseRect;
const { position } = opts;
if (position)
[x, y] = position(baseRect, destEl.getBoundingClientRect());
const style = destEl.style;
style.left = x + 'px';
style.top = y + 'px';
};
this.base = (el) => {
this.baseEl = el;
this.baseSub?.unsubscribe();
this.baseSub = void 0;
if (el) {
const baseRect$ = this.baseRect$;
baseRect$.next(el.getBoundingClientRect());
this.baseSub = (0, rect_1.rerender$)(el)
.pipe((0, rxjs_1.throttleTime)(20, void 0, { trailing: true }))
.subscribe(() => {
const rect = el.getBoundingClientRect();
const oldRect = baseRect$.getValue();
if (oldRect &&
rect.x === oldRect.x &&
rect.y === oldRect.y &&
rect.width === oldRect.width &&
rect.height === oldRect.height)
return;
baseRect$.next(rect);
});
}
else
this.baseRect$.next(void 0);
this.opts.onBase?.(el);
};
this.dest = (el) => {
this.destEl = el;
this.destSub?.unsubscribe();
this.destSub = void 0;
if (el) {
el.style.position = 'fixed';
this.destSub = (0, rect_1.resize$)(el)
.pipe((0, rxjs_1.throttleTime)(20, void 0, { trailing: true }))
.subscribe(this.entangle);
this.entangle();
}
this.opts.onDest?.(el);
};
/** -------------------------------------------------- {@link UiLifeCycles} */
this.start = () => {
const subscription = this.baseRect$.subscribe(() => {
this.entangle();
});
return () => {
subscription.unsubscribe();
this.baseRect$.next(void 0);
this.baseSub?.unsubscribe();
this.baseSub = void 0;
};
};
}
}
/**
* Renders a <span> with a <div> child rendered in a portal, such that the
* <div> position is entangled with the <span> position, the <div> moves
* with the <span> on resize and scroll.
*/
const EntangledPortal = (props) => {
const { span, children } = props;
// biome-ignore lint: props are set on every re-render in the render body
const state = React.useMemo(() => new EntangledPortalState(props), []);
state.opts = props;
// biome-ignore lint: too many dependencies
React.useEffect(state.start, [state]);
return (React.createElement("span", { ...span, ref: state.base },
React.createElement(EditorPortal_1.EditorPortal, null,
React.createElement("div", { ref: state.dest }, children))));
};
exports.EntangledPortal = EntangledPortal;