275 lines
7.7 KiB
TypeScript
275 lines
7.7 KiB
TypeScript
/*
|
|
* 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);
|
|
}
|