mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-02-23 17:49:49 +08:00
feat(editor): auto detect no ref component dom element
This commit is contained in:
parent
0b1e2f0931
commit
b9bc122bce
@ -59,6 +59,7 @@
|
||||
"react": "^17.0.2",
|
||||
"react-codemirror2": "^7.2.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-fiber-traverse": "^0.0.8",
|
||||
"react-json-tree": "^0.16.1",
|
||||
"scroll-into-view": "^1.16.2"
|
||||
},
|
||||
@ -86,9 +87,9 @@
|
||||
"vite": "^3.0.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.8.1",
|
||||
"react": "16.x || 17.x",
|
||||
"react-dom": "16.x || 17.x",
|
||||
"@emotion/react": "^11.8.1"
|
||||
"react-dom": "16.x || 17.x"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
@ -59,7 +59,9 @@ export const EditorMask: React.FC<Props> = observer((props: Props) => {
|
||||
const { hoverMaskPosition, selectedMaskPosition } = manager;
|
||||
|
||||
useEffect(() => {
|
||||
manager.init();
|
||||
setTimeout(() => {
|
||||
manager.init();
|
||||
}, 0);
|
||||
return () => {
|
||||
manager.destroy();
|
||||
};
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { debounce } from 'lodash';
|
||||
import { action, autorun, makeObservable, observable } from 'mobx';
|
||||
import React from 'react';
|
||||
import { traverse as traverseFiber } from 'react-fiber-traverse';
|
||||
import { EditorServices } from '../../types';
|
||||
|
||||
type MaskPosition = {
|
||||
@ -15,6 +17,8 @@ export class EditorMaskManager {
|
||||
hoverMaskPosition: MaskPosition | null = null;
|
||||
selectedMaskPosition: MaskPosition | null = null;
|
||||
mousePosition: [number, number] = [0, 0];
|
||||
// the elements get by fiber
|
||||
private fiberEleMap = new Map<string, Element>();
|
||||
private elementIdMap = new Map<Element, string>();
|
||||
// rect of mask container
|
||||
private maskContainerRect: DOMRect | null = null;
|
||||
@ -59,10 +63,11 @@ export class EditorMaskManager {
|
||||
}
|
||||
|
||||
init() {
|
||||
this.unsafeAutoFindNoRefElement();
|
||||
this.updateElementIdMap();
|
||||
this.observeContainerResize();
|
||||
this.observeIntersection();
|
||||
this.observeResize();
|
||||
this.refreshElementIdMap();
|
||||
|
||||
// when hoverComponentId & selectedComponentId change, refresh mask position
|
||||
autorun(() => {
|
||||
@ -110,6 +115,9 @@ export class EditorMaskManager {
|
||||
this.eleMap.forEach(ele => {
|
||||
this.resizeObserver.observe(ele);
|
||||
});
|
||||
this.fiberEleMap.forEach(ele => {
|
||||
this.intersectionObserver.observe(ele);
|
||||
});
|
||||
}
|
||||
|
||||
private observeContainerResize() {
|
||||
@ -126,23 +134,32 @@ export class EditorMaskManager {
|
||||
this.eleMap.forEach(ele => {
|
||||
this.intersectionObserver.observe(ele);
|
||||
});
|
||||
this.fiberEleMap.forEach(ele => {
|
||||
this.intersectionObserver.observe(ele);
|
||||
});
|
||||
}
|
||||
|
||||
private onHTMLElementsUpdated = () => {
|
||||
private onHTMLElementsUpdated = debounce(() => {
|
||||
this.unsafeAutoFindNoRefElement();
|
||||
this.updateElementIdMap();
|
||||
this.observeIntersection();
|
||||
this.observeResize();
|
||||
this.refreshElementIdMap();
|
||||
this.refreshHoverElement();
|
||||
this.refreshMaskPosition();
|
||||
};
|
||||
}, 16);
|
||||
|
||||
private onScroll = () => {
|
||||
this.refreshHoverElement();
|
||||
this.refreshMaskPosition();
|
||||
};
|
||||
|
||||
private getHTMLElement(id: string) {
|
||||
return this.eleMap.get(id) || this.fiberEleMap.get(id);
|
||||
}
|
||||
|
||||
private getMaskPosition(id: string) {
|
||||
const rect = this.eleMap.get(id)?.getBoundingClientRect();
|
||||
if (!id) return null;
|
||||
const rect = this.getHTMLElement(id)?.getBoundingClientRect();
|
||||
if (!this.maskContainerRect || !rect) return null;
|
||||
return {
|
||||
id,
|
||||
@ -163,14 +180,63 @@ export class EditorMaskManager {
|
||||
};
|
||||
}
|
||||
|
||||
private refreshElementIdMap() {
|
||||
private updateElementIdMap() {
|
||||
// generate elementIdMap, this only aim to improving the performance of refreshHoverElement method
|
||||
const elementIdMap = new Map<Element, string>();
|
||||
this.eleMap.forEach((ele, id) => {
|
||||
elementIdMap.set(ele, id);
|
||||
this.elementIdMap.set(ele, id);
|
||||
});
|
||||
this.fiberEleMap.forEach((ele, id) => {
|
||||
this.elementIdMap.set(ele, id);
|
||||
});
|
||||
}
|
||||
|
||||
this.elementIdMap = elementIdMap;
|
||||
private getElementsByFiber(ids: string[]): Record<string, HTMLElement | undefined> {
|
||||
const map: Record<string, HTMLElement | undefined> = {};
|
||||
const rootFiberNode = (document.getElementById('root') as any)._reactRootContainer
|
||||
._internalRoot.current;
|
||||
traverseFiber(rootFiberNode, (fiber: any) => {
|
||||
if (!fiber.memoizedProps) {
|
||||
return;
|
||||
}
|
||||
const componentId = fiber.memoizedProps['data-sunmao-id'];
|
||||
const i = ids.indexOf(componentId);
|
||||
if (i === -1) {
|
||||
return;
|
||||
}
|
||||
ids.splice(i, 1);
|
||||
// find the nearest child HTMLElement
|
||||
let curr = fiber;
|
||||
while (!curr.stateNode && curr.child) {
|
||||
curr = curr.child;
|
||||
}
|
||||
if (curr.stateNode instanceof HTMLElement) {
|
||||
map[componentId] = curr.stateNode;
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
// Auto find the components that does not have ref by React Fiber
|
||||
// This function is a fallback.
|
||||
// And because it uses React Fiber, it is not stable.
|
||||
private unsafeAutoFindNoRefElement() {
|
||||
try {
|
||||
const noEleComponentIds: string[] = [];
|
||||
this.services.appModelManager.appModel.allComponents.forEach(c => {
|
||||
if (!this.eleMap.has(c.id)) {
|
||||
const eleFromFiber = this.fiberEleMap.get(c.id);
|
||||
if (!eleFromFiber || !eleFromFiber.isConnected) {
|
||||
noEleComponentIds.push(c.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (noEleComponentIds.length === 0) return;
|
||||
const fiberElements = this.getElementsByFiber(noEleComponentIds);
|
||||
for (const key in fiberElements) {
|
||||
const ele = fiberElements[key];
|
||||
if (ele) this.fiberEleMap.set(key, ele);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
private refreshHoverElement() {
|
||||
@ -199,7 +265,7 @@ export class EditorMaskManager {
|
||||
private refreshMaskPosition() {
|
||||
this.setHoverMaskPosition(this.getMaskPosition(this.hoverComponentId));
|
||||
const selectedComponentId = this.services.editorStore.selectedComponentId;
|
||||
const selectedComponentEle = this.eleMap.get(selectedComponentId);
|
||||
const selectedComponentEle = this.getHTMLElement(selectedComponentId);
|
||||
if (selectedComponentEle && this.visibleMap.get(selectedComponentEle)) {
|
||||
this.setSelectedMaskPosition(this.getMaskPosition(selectedComponentId));
|
||||
} else {
|
||||
|
@ -163,6 +163,7 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
|
||||
|
||||
const C = unmount ? null : (
|
||||
<Impl
|
||||
data-sunmao-id={c.id}
|
||||
ref={ref}
|
||||
key={c.id}
|
||||
{...omit(props, ['slotContext'])}
|
||||
|
98
yarn.lock
98
yarn.lock
@ -1242,6 +1242,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.5.4":
|
||||
version "7.20.1"
|
||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9"
|
||||
integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.10"
|
||||
|
||||
"@babel/template@^7.16.0", "@babel/template@^7.3.3":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz"
|
||||
@ -2661,6 +2668,24 @@
|
||||
source-map "^0.5.7"
|
||||
stylis "4.0.13"
|
||||
|
||||
"@emotion/babel-plugin@^11.10.5":
|
||||
version "11.10.5"
|
||||
resolved "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz#65fa6e1790ddc9e23cc22658a4c5dea423c55c3c"
|
||||
integrity sha512-xE7/hyLHJac7D2Ve9dKroBBZqBT7WuPQmWcq7HSGb84sUuP4mlOWoB8dvVfD9yk5DHkU1m6RW7xSoDtnQHNQeA==
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^7.16.7"
|
||||
"@babel/plugin-syntax-jsx" "^7.17.12"
|
||||
"@babel/runtime" "^7.18.3"
|
||||
"@emotion/hash" "^0.9.0"
|
||||
"@emotion/memoize" "^0.8.0"
|
||||
"@emotion/serialize" "^1.1.1"
|
||||
babel-plugin-macros "^3.1.0"
|
||||
convert-source-map "^1.5.0"
|
||||
escape-string-regexp "^4.0.0"
|
||||
find-root "^1.1.0"
|
||||
source-map "^0.5.7"
|
||||
stylis "4.1.3"
|
||||
|
||||
"@emotion/cache@^11.10.0":
|
||||
version "11.10.3"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.3.tgz#c4f67904fad10c945fea5165c3a5a0583c164b87"
|
||||
@ -2672,6 +2697,17 @@
|
||||
"@emotion/weak-memoize" "^0.3.0"
|
||||
stylis "4.0.13"
|
||||
|
||||
"@emotion/cache@^11.10.5":
|
||||
version "11.10.5"
|
||||
resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz#c142da9351f94e47527ed458f7bbbbe40bb13c12"
|
||||
integrity sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==
|
||||
dependencies:
|
||||
"@emotion/memoize" "^0.8.0"
|
||||
"@emotion/sheet" "^1.2.1"
|
||||
"@emotion/utils" "^1.2.0"
|
||||
"@emotion/weak-memoize" "^0.3.0"
|
||||
stylis "4.1.3"
|
||||
|
||||
"@emotion/cache@^11.4.0":
|
||||
version "11.5.0"
|
||||
resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.5.0.tgz"
|
||||
@ -2734,14 +2770,14 @@
|
||||
integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==
|
||||
|
||||
"@emotion/react@^11.0.0", "@emotion/react@^11.1.1", "@emotion/react@^11.8.1":
|
||||
version "11.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.4.tgz#9dc6bccbda5d70ff68fdb204746c0e8b13a79199"
|
||||
integrity sha512-j0AkMpr6BL8gldJZ6XQsQ8DnS9TxEQu1R+OGmDZiWjBAJtCcbt0tS3I/YffoqHXxH6MjgI7KdMbYKw3MEiU9eA==
|
||||
version "11.10.5"
|
||||
resolved "https://registry.npmjs.org/@emotion/react/-/react-11.10.5.tgz#95fff612a5de1efa9c0d535384d3cfa115fe175d"
|
||||
integrity sha512-TZs6235tCJ/7iF6/rvTaOH4oxQg2gMAcdHemjwLKIjKz4rRuYe1HJ2TQJKnAcRAfOUDdU8XoDadCe1rl72iv8A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.18.3"
|
||||
"@emotion/babel-plugin" "^11.10.0"
|
||||
"@emotion/cache" "^11.10.0"
|
||||
"@emotion/serialize" "^1.1.0"
|
||||
"@emotion/babel-plugin" "^11.10.5"
|
||||
"@emotion/cache" "^11.10.5"
|
||||
"@emotion/serialize" "^1.1.1"
|
||||
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.0"
|
||||
"@emotion/utils" "^1.2.0"
|
||||
"@emotion/weak-memoize" "^0.3.0"
|
||||
@ -2769,6 +2805,17 @@
|
||||
"@emotion/utils" "^1.2.0"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@emotion/serialize@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz#0595701b1902feded8a96d293b26be3f5c1a5cf0"
|
||||
integrity sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==
|
||||
dependencies:
|
||||
"@emotion/hash" "^0.9.0"
|
||||
"@emotion/memoize" "^0.8.0"
|
||||
"@emotion/unitless" "^0.8.0"
|
||||
"@emotion/utils" "^1.2.0"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@emotion/sheet@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.0.3.tgz"
|
||||
@ -2779,15 +2826,20 @@
|
||||
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.0.tgz#771b1987855839e214fc1741bde43089397f7be5"
|
||||
integrity sha512-OiTkRgpxescko+M51tZsMq7Puu/KP55wMT8BgpcXVG2hqXc0Vo0mfymJ/Uj24Hp0i083ji/o0aLddh08UEjq8w==
|
||||
|
||||
"@emotion/sheet@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz#0767e0305230e894897cadb6c8df2c51e61a6c2c"
|
||||
integrity sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==
|
||||
|
||||
"@emotion/styled@^11.0.0", "@emotion/styled@^11.8.1":
|
||||
version "11.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.4.tgz#e93f84a4d54003c2acbde178c3f97b421fce1cd4"
|
||||
integrity sha512-pRl4R8Ez3UXvOPfc2bzIoV8u9P97UedgHS4FPX594ntwEuAMA114wlaHvOK24HB48uqfXiGlYIZYCxVJ1R1ttQ==
|
||||
version "11.10.5"
|
||||
resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.5.tgz#1fe7bf941b0909802cb826457e362444e7e96a79"
|
||||
integrity sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.18.3"
|
||||
"@emotion/babel-plugin" "^11.10.0"
|
||||
"@emotion/babel-plugin" "^11.10.5"
|
||||
"@emotion/is-prop-valid" "^1.2.0"
|
||||
"@emotion/serialize" "^1.1.0"
|
||||
"@emotion/serialize" "^1.1.1"
|
||||
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.0"
|
||||
"@emotion/utils" "^1.2.0"
|
||||
|
||||
@ -5925,6 +5977,11 @@ css-box-model@1.2.1:
|
||||
dependencies:
|
||||
tiny-invariant "^1.0.6"
|
||||
|
||||
css-what@^3.2.0:
|
||||
version "3.4.2"
|
||||
resolved "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4"
|
||||
integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==
|
||||
|
||||
css.escape@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
|
||||
@ -10717,6 +10774,15 @@ react-fast-compare@^2.0.1:
|
||||
resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz"
|
||||
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
||||
|
||||
react-fiber-traverse@^0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.npmjs.org/react-fiber-traverse/-/react-fiber-traverse-0.0.8.tgz#08987681bdeaba2c964593468fe22470e01ab486"
|
||||
integrity sha512-UdCfB6inAFmB1/wT1NdMwqbZIuC2hh+i2vo8L9xy9ULKd2UjqTgxJiZBrF4RXmVmbP0XAFJSFWEz9axzXwKgPw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.4"
|
||||
css-what "^3.2.0"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-focus-lock@2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.5.0.tgz#12e3a3940e897c26e2c2a0408cd25ea3c99b3709"
|
||||
@ -11057,6 +11123,11 @@ regenerator-runtime@^0.11.0:
|
||||
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz"
|
||||
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
|
||||
|
||||
regenerator-runtime@^0.13.10:
|
||||
version "0.13.10"
|
||||
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee"
|
||||
integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==
|
||||
|
||||
regenerator-runtime@^0.13.4:
|
||||
version "0.13.9"
|
||||
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz"
|
||||
@ -11841,6 +11912,11 @@ stylis@4.0.13:
|
||||
resolved "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz"
|
||||
integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
|
||||
|
||||
stylis@4.1.3:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7"
|
||||
integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==
|
||||
|
||||
stylis@^4.0.10:
|
||||
version "4.0.10"
|
||||
resolved "https://registry.npmjs.org/stylis/-/stylis-4.0.10.tgz"
|
||||
|
Loading…
Reference in New Issue
Block a user