json-joy
Version:
Collection of libraries for building collaborative editing apps.
97 lines • 3.38 kB
JavaScript
import * as React from 'react';
import { BehaviorSubject, throttleTime } from 'rxjs';
import { resize$, rerender$ } from '../util/rect$';
import { EditorPortal } from '../web/react/util/EditorPortal';
class EntangledPortalState {
opts;
baseEl = null;
destEl = null;
baseSub = void 0;
destSub = void 0;
baseRect$ = new BehaviorSubject(void 0);
constructor(opts) {
this.opts = opts;
}
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';
};
base = (el) => {
this.baseEl = el;
this.baseSub?.unsubscribe();
this.baseSub = void 0;
if (el) {
const baseRect$ = this.baseRect$;
baseRect$.next(el.getBoundingClientRect());
this.baseSub = rerender$(el)
.pipe(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);
};
dest = (el) => {
this.destEl = el;
this.destSub?.unsubscribe();
this.destSub = void 0;
if (el) {
el.style.position = 'fixed';
this.destSub = resize$(el)
.pipe(throttleTime(20, void 0, { trailing: true }))
.subscribe(this.entangle);
this.entangle();
}
this.opts.onDest?.(el);
};
/** -------------------------------------------------- {@link UiLifeCycles} */
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.
*/
export 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, null,
React.createElement("div", { ref: state.dest }, children))));
};
//# sourceMappingURL=EntangledPortal.js.map