mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-08-07 23:03:00 +03:00
Lexical: Imported core lexical libs
Imported at 0.17.1, Modified to work in-app. Added & configured test dependancies. Tests need to be altered to avoid using non-included deps including react dependancies.
This commit is contained in:
141
resources/js/wysiwyg/lexical/utils/positionNodeOnRange.ts
Normal file
141
resources/js/wysiwyg/lexical/utils/positionNodeOnRange.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
import type {LexicalEditor} from 'lexical';
|
||||
|
||||
import {createRectsFromDOMRange} from '@lexical/selection';
|
||||
import invariant from 'lexical/shared/invariant';
|
||||
|
||||
import px from './px';
|
||||
|
||||
const mutationObserverConfig = {
|
||||
attributes: true,
|
||||
characterData: true,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
};
|
||||
|
||||
export default function positionNodeOnRange(
|
||||
editor: LexicalEditor,
|
||||
range: Range,
|
||||
onReposition: (node: Array<HTMLElement>) => void,
|
||||
): () => void {
|
||||
let rootDOMNode: null | HTMLElement = null;
|
||||
let parentDOMNode: null | HTMLElement = null;
|
||||
let observer: null | MutationObserver = null;
|
||||
let lastNodes: Array<HTMLElement> = [];
|
||||
const wrapperNode = document.createElement('div');
|
||||
|
||||
function position(): void {
|
||||
invariant(rootDOMNode !== null, 'Unexpected null rootDOMNode');
|
||||
invariant(parentDOMNode !== null, 'Unexpected null parentDOMNode');
|
||||
const {left: rootLeft, top: rootTop} = rootDOMNode.getBoundingClientRect();
|
||||
const parentDOMNode_ = parentDOMNode;
|
||||
const rects = createRectsFromDOMRange(editor, range);
|
||||
if (!wrapperNode.isConnected) {
|
||||
parentDOMNode_.append(wrapperNode);
|
||||
}
|
||||
let hasRepositioned = false;
|
||||
for (let i = 0; i < rects.length; i++) {
|
||||
const rect = rects[i];
|
||||
// Try to reuse the previously created Node when possible, no need to
|
||||
// remove/create on the most common case reposition case
|
||||
const rectNode = lastNodes[i] || document.createElement('div');
|
||||
const rectNodeStyle = rectNode.style;
|
||||
if (rectNodeStyle.position !== 'absolute') {
|
||||
rectNodeStyle.position = 'absolute';
|
||||
hasRepositioned = true;
|
||||
}
|
||||
const left = px(rect.left - rootLeft);
|
||||
if (rectNodeStyle.left !== left) {
|
||||
rectNodeStyle.left = left;
|
||||
hasRepositioned = true;
|
||||
}
|
||||
const top = px(rect.top - rootTop);
|
||||
if (rectNodeStyle.top !== top) {
|
||||
rectNode.style.top = top;
|
||||
hasRepositioned = true;
|
||||
}
|
||||
const width = px(rect.width);
|
||||
if (rectNodeStyle.width !== width) {
|
||||
rectNode.style.width = width;
|
||||
hasRepositioned = true;
|
||||
}
|
||||
const height = px(rect.height);
|
||||
if (rectNodeStyle.height !== height) {
|
||||
rectNode.style.height = height;
|
||||
hasRepositioned = true;
|
||||
}
|
||||
if (rectNode.parentNode !== wrapperNode) {
|
||||
wrapperNode.append(rectNode);
|
||||
hasRepositioned = true;
|
||||
}
|
||||
lastNodes[i] = rectNode;
|
||||
}
|
||||
while (lastNodes.length > rects.length) {
|
||||
lastNodes.pop();
|
||||
}
|
||||
if (hasRepositioned) {
|
||||
onReposition(lastNodes);
|
||||
}
|
||||
}
|
||||
|
||||
function stop(): void {
|
||||
parentDOMNode = null;
|
||||
rootDOMNode = null;
|
||||
if (observer !== null) {
|
||||
observer.disconnect();
|
||||
}
|
||||
observer = null;
|
||||
wrapperNode.remove();
|
||||
for (const node of lastNodes) {
|
||||
node.remove();
|
||||
}
|
||||
lastNodes = [];
|
||||
}
|
||||
|
||||
function restart(): void {
|
||||
const currentRootDOMNode = editor.getRootElement();
|
||||
if (currentRootDOMNode === null) {
|
||||
return stop();
|
||||
}
|
||||
const currentParentDOMNode = currentRootDOMNode.parentElement;
|
||||
if (!(currentParentDOMNode instanceof HTMLElement)) {
|
||||
return stop();
|
||||
}
|
||||
stop();
|
||||
rootDOMNode = currentRootDOMNode;
|
||||
parentDOMNode = currentParentDOMNode;
|
||||
observer = new MutationObserver((mutations) => {
|
||||
const nextRootDOMNode = editor.getRootElement();
|
||||
const nextParentDOMNode =
|
||||
nextRootDOMNode && nextRootDOMNode.parentElement;
|
||||
if (
|
||||
nextRootDOMNode !== rootDOMNode ||
|
||||
nextParentDOMNode !== parentDOMNode
|
||||
) {
|
||||
return restart();
|
||||
}
|
||||
for (const mutation of mutations) {
|
||||
if (!wrapperNode.contains(mutation.target)) {
|
||||
// TODO throttle
|
||||
return position();
|
||||
}
|
||||
}
|
||||
});
|
||||
observer.observe(currentParentDOMNode, mutationObserverConfig);
|
||||
position();
|
||||
}
|
||||
|
||||
const removeRootListener = editor.registerRootListener(restart);
|
||||
|
||||
return () => {
|
||||
removeRootListener();
|
||||
stop();
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user