[inula-dev-tools]<feat> 组件树选中高亮合入
This commit is contained in:
parent
3e01b7124a
commit
8f33376722
|
@ -0,0 +1,274 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||
*
|
||||
* openInula is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
*
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
import assign from 'object-assign';
|
||||
import { VNode } from '../../../inula/src/renderer/vnode/VNode';
|
||||
|
||||
const overlayStyles = {
|
||||
background: 'rgba(120, 170, 210, 0.7)',
|
||||
padding: 'rgba(77, 200, 0, 0.3)',
|
||||
margin: 'rgba(255, 155, 0, 0.3)',
|
||||
border: 'rgba(255, 200, 50, 0.3)'
|
||||
};
|
||||
|
||||
type Rect = {
|
||||
bottom: number;
|
||||
height: number;
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
width: number;
|
||||
};
|
||||
|
||||
function setBoxStyle(eleStyle, boxArea, node) {
|
||||
assign(node.style, {
|
||||
borderTopWidth: eleStyle[boxArea + 'Top'] + 'px',
|
||||
borderLeftWidth: eleStyle[boxArea + 'Left'] + 'px',
|
||||
borderRightWidth: eleStyle[boxArea + 'Right'] + 'px',
|
||||
borderBottomWidth: eleStyle[boxArea + 'Bottom'] + 'px',
|
||||
});
|
||||
}
|
||||
|
||||
function getOwnerWindow(node: Element): typeof window | null {
|
||||
if (!node.ownerDocument) {
|
||||
return null;
|
||||
}
|
||||
return node.ownerDocument.defaultView;
|
||||
}
|
||||
|
||||
function getOwnerIframe(node: Element): Element | null {
|
||||
const nodeWindow = getOwnerWindow(node);
|
||||
if (nodeWindow) {
|
||||
return nodeWindow.frameElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getElementStyle(domElement: Element) {
|
||||
const style = window.getComputedStyle(domElement);
|
||||
return{
|
||||
marginLeft: parseInt(style.marginLeft, 10),
|
||||
marginRight: parseInt(style.marginRight, 10),
|
||||
marginTop: parseInt(style.marginTop, 10),
|
||||
marginBottom: parseInt(style.marginBottom, 10),
|
||||
borderLeft: parseInt(style.borderLeftWidth, 10),
|
||||
borderRight: parseInt(style.borderRightWidth, 10),
|
||||
borderTop: parseInt(style.borderTopWidth, 10),
|
||||
borderBottom: parseInt(style.borderBottomWidth, 10),
|
||||
paddingLeft: parseInt(style.paddingLeft, 10),
|
||||
paddingRight: parseInt(style.paddingRight, 10),
|
||||
paddingTop: parseInt(style.paddingTop, 10),
|
||||
paddingBottom: parseInt(style.paddingBottom, 10)
|
||||
};
|
||||
}
|
||||
|
||||
function mergeRectOffsets(rects: Array<Rect>): Rect {
|
||||
return rects.reduce((previousRect, rect) => {
|
||||
if (previousRect == null) {
|
||||
return rect;
|
||||
}
|
||||
|
||||
return {
|
||||
top: previousRect.top + rect.top,
|
||||
left: previousRect.left + rect.left,
|
||||
width: previousRect.width + rect.width,
|
||||
height: previousRect.height + rect.height,
|
||||
bottom: previousRect.bottom + rect.bottom,
|
||||
right: previousRect.right + rect.right
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getBoundingClientRectWithBorderOffset(node: Element) {
|
||||
const dimensions = getElementStyle(node);
|
||||
return mergeRectOffsets([
|
||||
node.getBoundingClientRect(),
|
||||
{
|
||||
top: dimensions.borderTop,
|
||||
left: dimensions.borderLeft,
|
||||
bottom: dimensions.borderBottom,
|
||||
right:dimensions.borderRight,
|
||||
// 高度和宽度不会被使用
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
function getNestedBoundingClientRect(
|
||||
node: HTMLElement,
|
||||
boundaryWindow
|
||||
): Rect {
|
||||
const ownerIframe = getOwnerIframe(node);
|
||||
if (ownerIframe && ownerIframe !== boundaryWindow) {
|
||||
const rects = [node.getBoundingClientRect()] as Rect[];
|
||||
let currentIframe = ownerIframe;
|
||||
let onlyOneMore = false;
|
||||
while (currentIframe) {
|
||||
const rect = getBoundingClientRectWithBorderOffset(currentIframe);
|
||||
rects.push(rect);
|
||||
currentIframe = getOwnerIframe(currentIframe);
|
||||
|
||||
if (onlyOneMore) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (currentIframe &&getOwnerWindow(currentIframe) === boundaryWindow) {
|
||||
onlyOneMore = true;
|
||||
}
|
||||
}
|
||||
|
||||
return mergeRectOffsets(rects);
|
||||
} else {
|
||||
return node.getBoundingClientRect();
|
||||
}
|
||||
}
|
||||
|
||||
// 用来遮罩
|
||||
class OverlayRect {
|
||||
node: HTMLElement;
|
||||
border: HTMLElement;
|
||||
padding: HTMLElement;
|
||||
content: HTMLElement;
|
||||
|
||||
constructor(doc: Document, container: HTMLElement) {
|
||||
this.node = doc.createElement('div');
|
||||
this.border = doc.createElement('div');
|
||||
this.padding = doc.createElement('div');
|
||||
this.content = doc.createElement('div');
|
||||
|
||||
this.border.style.borderColor = overlayStyles.border;
|
||||
this.padding.style.borderColor = overlayStyles.padding;
|
||||
this.content.style.backgroundColor = overlayStyles.background;
|
||||
|
||||
assign(this.node.style, {
|
||||
borderColor: overlayStyles.margin,
|
||||
pointerEvents: 'none',
|
||||
position: 'fixed'
|
||||
});
|
||||
|
||||
this.node.style.zIndex = '10000000';
|
||||
|
||||
this.node.appendChild(this.border);
|
||||
this.border.appendChild(this.padding);
|
||||
this.padding.appendChild(this.content);
|
||||
container.appendChild(this.node);
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (this.node.parentNode) {
|
||||
this.node.parentNode.removeChild(this.node);
|
||||
}
|
||||
}
|
||||
|
||||
update(boxRect: Rect, eleStyle: any) {
|
||||
setBoxStyle(eleStyle, 'margin', this.node);
|
||||
setBoxStyle(eleStyle, 'border', this.border);
|
||||
setBoxStyle(eleStyle, 'padding', this.padding);
|
||||
|
||||
assign(this.content.style, {
|
||||
height: boxRect.height - eleStyle.borderTop - eleStyle.borderBottom - eleStyle.paddingTop - eleStyle.paddingBottom + 'px',
|
||||
width: boxRect.width - eleStyle.borderLeft - eleStyle.borderRight - eleStyle.paddingLeft - eleStyle.paddingRight + 'px'
|
||||
});
|
||||
|
||||
assign(this.node.style, {
|
||||
top: boxRect.top - eleStyle.marginTop + 'px',
|
||||
left: boxRect.left - eleStyle.marginLeft + 'px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ElementOverlay {
|
||||
window: typeof window;
|
||||
container: HTMLElement;
|
||||
rects: Array<OverlayRect>;
|
||||
|
||||
constructor() {
|
||||
this.window = window;
|
||||
const doc = window.document;
|
||||
this.container = doc.createElement('div');
|
||||
this.container.style.zIndex = '10000000';
|
||||
this.rects = [];
|
||||
|
||||
doc.body.appendChild(this.container);
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.rects.forEach(rect => {
|
||||
rect.remove();
|
||||
});
|
||||
this.rects.length = 0;
|
||||
if (this.container.parentNode) {
|
||||
this.container.parentNode.removeChild(this.container);
|
||||
}
|
||||
}
|
||||
|
||||
execute(nodes: Array<VNode>) {
|
||||
const elements = nodes.filter(node => node.tag === 'DomComponent');
|
||||
|
||||
// 有几个 element 就添加几个 OverlayRect
|
||||
while (this.rects.length > elements.length) {
|
||||
const rect = this.rects.pop();
|
||||
rect.remove();
|
||||
}
|
||||
if (elements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (this.rects.length < elements.length) {
|
||||
this.rects.push(new OverlayRect(this.window.document, this.container));
|
||||
}
|
||||
|
||||
const outerBox = {
|
||||
top: Number.POSITIVE_INFINITY,
|
||||
right: Number.NEGATIVE_INFINITY,
|
||||
bottom: Number.NEGATIVE_INFINITY,
|
||||
left: Number.POSITIVE_INFINITY
|
||||
};
|
||||
|
||||
elements.forEach((element, index) => {
|
||||
const eleStyle = getElementStyle(element.realNode);
|
||||
const boxRect = getNestedBoundingClientRect(element.realNode, this.window);
|
||||
|
||||
outerBox.top = Math.min(outerBox.top, boxRect.top - eleStyle.marginTop);
|
||||
outerBox.right = Math.max(outerBox.right, boxRect.left + boxRect.width + eleStyle.marginRight);
|
||||
outerBox.bottom = Math.max(outerBox.bottom, boxRect.top + boxRect.height + eleStyle.marginBottom);
|
||||
outerBox.left = Math.min(outerBox.left, boxRect.left - eleStyle.marginLeft);
|
||||
|
||||
const rect = this.rects[index];
|
||||
rect.update(boxRect, eleStyle);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let elementOverlay: ElementOverlay | null = null;
|
||||
export function hideHighlight() {
|
||||
if (elementOverlay !== null) {
|
||||
elementOverlay.remove();
|
||||
elementOverlay = null;
|
||||
}
|
||||
}
|
||||
|
||||
export function showHighlight(elements: Array<VNode> | null) {
|
||||
if (window.document == null || elements == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (elementOverlay === null) {
|
||||
elementOverlay = new ElementOverlay();
|
||||
}
|
||||
|
||||
elementOverlay.execute(elements);
|
||||
}
|
Loading…
Reference in New Issue