UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

101 lines (100 loc) 3.9 kB
"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;