react-dev-inspector
Version:
dev-tool for inspect react components and jump to local IDE for component code.
364 lines (363 loc) • 13.3 kB
JavaScript
"use strict";
/**
* mirror from https://github.com/facebook/react/blob/v16.13.1/packages/react-devtools-shared/src/backend/views/utils.js
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Overlay = void 0;
const utils_1 = require("./utils");
// Note that the Overlay components are not affected by the active Theme,
// because they highlight elements in the main Chrome window (outside of devtools).
// The colors below were chosen to roughly match those used by Chrome devtools.
class OverlayRect {
constructor(doc, container) {
Object.defineProperty(this, "node", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "border", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "padding", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "content", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
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;
Object.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);
// ensure OverlayRect dom always before OverlayTip dom rather than cover OverlayTip
container.prepend(this.node);
}
remove() {
if (this.node.parentNode) {
this.node.parentNode.removeChild(this.node);
}
}
update(box, dims) {
boxWrap(dims, 'margin', this.node);
boxWrap(dims, 'border', this.border);
boxWrap(dims, 'padding', this.padding);
Object.assign(this.content.style, {
height: `${box.height
- dims.borderTop
- dims.borderBottom
- dims.paddingTop
- dims.paddingBottom}px`,
width: `${box.width
- dims.borderLeft
- dims.borderRight
- dims.paddingLeft
- dims.paddingRight}px`,
});
Object.assign(this.node.style, {
top: `${box.top - dims.marginTop}px`,
left: `${box.left - dims.marginLeft}px`,
});
}
}
class OverlayTip {
constructor(doc, container) {
Object.defineProperty(this, "tip", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "nameSpan", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "titleDiv", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "infoDiv", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "dimSpan", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.tip = doc.createElement('div');
Object.assign(this.tip.style, {
display: 'flex',
flexFlow: 'row nowrap',
alignItems: 'center',
backgroundColor: '#333740',
borderRadius: '2px',
fontFamily: '"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace',
fontWeight: 'bold',
padding: '6px 8px',
pointerEvents: 'none',
position: 'fixed',
fontSize: '12px',
whiteSpace: 'nowrap',
});
this.nameSpan = doc.createElement('span');
this.tip.appendChild(this.nameSpan);
Object.assign(this.nameSpan.style, {
display: 'flex',
flexDirection: 'column',
borderRight: '1px solid #aaaaaa',
paddingRight: '0.8rem',
marginRight: '0.8rem',
});
this.titleDiv = doc.createElement('div');
this.nameSpan.appendChild(this.titleDiv);
Object.assign(this.titleDiv.style, {
color: '#ee78e6',
fontSize: '16px',
});
this.infoDiv = doc.createElement('div');
this.nameSpan.appendChild(this.infoDiv);
Object.assign(this.infoDiv.style, {
color: '#ee78e6',
fontSize: '14px',
});
this.dimSpan = doc.createElement('span');
this.tip.appendChild(this.dimSpan);
Object.assign(this.dimSpan.style, {
color: '#d7d7d7',
});
this.tip.style.zIndex = '10000000';
container.appendChild(this.tip);
}
remove() {
if (this.tip.parentNode) {
this.tip.parentNode.removeChild(this.tip);
}
}
updateText(name, info, width, height) {
this.titleDiv.textContent = name;
this.infoDiv.textContent = info !== null && info !== void 0 ? info : '';
this.dimSpan.textContent
= `${Math.round(width)}px × ${Math.round(height)}px`;
}
updatePosition(dims, bounds) {
const tipRect = this.tip.getBoundingClientRect();
const tipPos = findTipPos(dims, bounds, {
width: tipRect.width,
height: tipRect.height,
});
Object.assign(this.tip.style, tipPos.style);
}
}
class Overlay {
constructor() {
Object.defineProperty(this, "window", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "tipBoundsWindow", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "container", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "tip", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "rects", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "removeCallback", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
// Find the root window, because overlays are positioned relative to it.
const currentWindow = window.__REACT_DEVTOOLS_TARGET_WINDOW__ || window;
this.window = currentWindow;
// When opened in shells/dev,
// the tooltip should be bound by the app iframe, not by the topmost window.
const tipBoundsWindow = window.__REACT_DEVTOOLS_TARGET_WINDOW__ || window;
this.tipBoundsWindow = tipBoundsWindow;
const doc = currentWindow.document;
this.container = doc.createElement('div');
this.container.style.zIndex = '10000000';
this.tip = new OverlayTip(doc, this.container);
this.rects = [];
this.removeCallback = () => { };
doc.body.appendChild(this.container);
}
remove() {
this.tip.remove();
this.rects.forEach(rect => {
rect.remove();
});
this.rects.length = 0;
if (this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
this.removeCallback();
}
setRemoveCallback(callback) {
this.removeCallback = callback.bind(this);
}
inspect(nodes, name, info) {
var _a;
// We can't get the size of text nodes or comment nodes. React as of v15
// heavily uses comment nodes to delimit text.
const elements = nodes.filter(node => node.nodeType === Node.ELEMENT_NODE);
while (this.rects.length > elements.length) {
const rect = this.rects.pop();
rect === null || rect === void 0 ? void 0 : 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 box = (0, utils_1.getNestedBoundingClientRect)(element, this.window);
const dims = (0, utils_1.getElementDimensions)(element);
outerBox.top = Math.min(outerBox.top, box.top - dims.marginTop);
outerBox.right = Math.max(outerBox.right, box.left + box.width + dims.marginRight);
outerBox.bottom = Math.max(outerBox.bottom, box.top + box.height + dims.marginBottom);
outerBox.left = Math.min(outerBox.left, box.left - dims.marginLeft);
const rect = this.rects[index];
rect.update(box, dims);
});
if (!name) {
name = elements[0].nodeName.toLowerCase();
const node = elements[0];
const hook = (_a = node.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.__REACT_DEVTOOLS_GLOBAL_HOOK__;
if (hook === null || hook === void 0 ? void 0 : hook.rendererInterfaces) {
let ownerName = null;
for (const rendererInterface of hook.rendererInterfaces.values()) {
const id = rendererInterface.getFiberIDForNative(node, true);
if (id !== null) {
ownerName = rendererInterface.getDisplayNameForFiberID(id, true);
break;
}
}
if (ownerName) {
name += ` (in ${ownerName})`;
}
}
}
this.tip.updateText(name, info, outerBox.right - outerBox.left, outerBox.bottom - outerBox.top);
const tipBounds = (0, utils_1.getNestedBoundingClientRect)(this.tipBoundsWindow.document.documentElement, this.window);
this.tip.updatePosition({
top: outerBox.top,
left: outerBox.left,
height: outerBox.bottom - outerBox.top,
width: outerBox.right - outerBox.left,
}, {
top: tipBounds.top + this.tipBoundsWindow.scrollY,
left: tipBounds.left + this.tipBoundsWindow.scrollX,
height: this.tipBoundsWindow.innerHeight,
width: this.tipBoundsWindow.innerWidth,
});
}
}
exports.Overlay = Overlay;
function findTipPos(dims, bounds, tipSize) {
const tipHeight = Math.max(tipSize.height, 20);
const tipWidth = Math.max(tipSize.width, 60);
const margin = 5;
let top;
if (dims.top + dims.height + tipHeight <= bounds.top + bounds.height) {
if (dims.top + dims.height < bounds.top + 0) {
top = bounds.top + margin;
}
else {
top = dims.top + dims.height + margin;
}
}
else if (dims.top - tipHeight <= bounds.top + bounds.height) {
if (dims.top - tipHeight - margin < bounds.top + margin) {
top = bounds.top + margin;
}
else {
top = dims.top - tipHeight - margin;
}
}
else {
top = bounds.top + bounds.height - tipHeight - margin;
}
let left = dims.left + margin;
if (dims.left < bounds.left) {
left = bounds.left + margin;
}
if (dims.left + tipWidth > bounds.left + bounds.width) {
left = bounds.left + bounds.width - tipWidth - margin;
}
return {
style: {
top: `${top}px`,
left: `${left}px`,
},
};
}
function boxWrap(dims, what, node) {
Object.assign(node.style, {
borderTopWidth: `${dims[`${what}Top`]}px`,
borderLeftWidth: `${dims[`${what}Left`]}px`,
borderRightWidth: `${dims[`${what}Right`]}px`,
borderBottomWidth: `${dims[`${what}Bottom`]}px`,
borderStyle: 'solid',
});
}
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)',
};