luna-dom-highlighter
Version:
Highlighter for html elements
468 lines (467 loc) • 22.1 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HighlightOverlay = void 0;
var endWith_1 = __importDefault(require("licia/endWith"));
var ColorUtils_1 = require("./ColorUtils");
var common_1 = require("./common");
var highlight_common_1 = require("./highlight_common");
var HighlightOverlay = (function (_super) {
__extends(HighlightOverlay, _super);
function HighlightOverlay() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.gridLabelState = { gridLayerCounter: 0 };
return _this;
}
HighlightOverlay.prototype.setContainer = function (container) {
this._container = container;
};
HighlightOverlay.prototype.setPlatform = function (platform) {
if (this.container) {
this.container.classList.add('luna-dom-highlighter-platform-' + platform);
}
_super.prototype.setPlatform.call(this, platform);
};
Object.defineProperty(HighlightOverlay.prototype, "container", {
get: function () {
return this._container;
},
enumerable: false,
configurable: true
});
HighlightOverlay.prototype.reset = function (resetData) {
_super.prototype.reset.call(this, resetData);
this.tooltip.innerHTML = '';
this.gridLabelState.gridLayerCounter = 0;
};
HighlightOverlay.prototype.install = function () {
var canvas = this.document.createElement('canvas');
canvas.classList.add('luna-dom-highlighter-fill');
this.container.appendChild(canvas);
var tooltip = this.document.createElement('div');
this.container.appendChild(tooltip);
this.tooltip = tooltip;
this.setCanvas(canvas);
_super.prototype.install.call(this);
};
HighlightOverlay.prototype.uninstall = function () {
this.document.body.classList.remove('fill');
this.document.body.innerHTML = '';
_super.prototype.uninstall.call(this);
};
HighlightOverlay.prototype.drawHighlight = function (highlight) {
this.context.save();
var bounds = (0, highlight_common_1.emptyBounds)();
for (var paths = highlight.paths.slice(); paths.length;) {
var path = paths.pop();
if (!path) {
continue;
}
this.context.save();
(0, highlight_common_1.drawPath)(this.context, path.path, path.fillColor, path.outlineColor, undefined, bounds, this.emulationScaleFactor);
if (paths.length) {
this.context.globalCompositeOperation = 'destination-out';
(0, highlight_common_1.drawPath)(this.context, paths[paths.length - 1].path, 'red', undefined, undefined, bounds, this.emulationScaleFactor);
}
this.context.restore();
}
this.context.restore();
this.context.save();
var rulerAtRight = Boolean(highlight.paths.length && highlight.showRulers && bounds.minX < 20 && bounds.maxX + 20 < this.canvasWidth);
var rulerAtBottom = Boolean(highlight.paths.length && highlight.showRulers && bounds.minY < 20 && bounds.maxY + 20 < this.canvasHeight);
if (highlight.showRulers) {
this.drawAxis(this.context, rulerAtRight, rulerAtBottom);
}
if (highlight.paths.length) {
if (highlight.showExtensionLines) {
drawRulers(this.context, bounds, rulerAtRight, rulerAtBottom, undefined, false, this.canvasWidth, this.canvasHeight);
}
if (highlight.elementInfo) {
drawElementTitle(this.tooltip, highlight.elementInfo, highlight.colorFormat, bounds, this.canvasWidth, this.canvasHeight);
}
}
this.context.restore();
return { bounds: bounds };
};
HighlightOverlay.prototype.drawAxis = function (context, rulerAtRight, rulerAtBottom) {
context.save();
var pageFactor = this.pageZoomFactor * this.pageScaleFactor * this.emulationScaleFactor;
var scrollX = this.scrollX * this.pageScaleFactor;
var scrollY = this.scrollY * this.pageScaleFactor;
function zoom(x) {
return Math.round(x * pageFactor);
}
function unzoom(x) {
return Math.round(x / pageFactor);
}
var width = this.canvasWidth / pageFactor;
var height = this.canvasHeight / pageFactor;
var gridSubStep = 5;
var gridStep = 50;
{
context.save();
context.fillStyle = gridBackgroundColor;
if (rulerAtBottom) {
context.fillRect(0, zoom(height) - 15, zoom(width), zoom(height));
}
else {
context.fillRect(0, 0, zoom(width), 15);
}
context.globalCompositeOperation = 'destination-out';
context.fillStyle = 'red';
if (rulerAtRight) {
context.fillRect(zoom(width) - 15, 0, zoom(width), zoom(height));
}
else {
context.fillRect(0, 0, 15, zoom(height));
}
context.restore();
context.fillStyle = gridBackgroundColor;
if (rulerAtRight) {
context.fillRect(zoom(width) - 15, 0, zoom(width), zoom(height));
}
else {
context.fillRect(0, 0, 15, zoom(height));
}
}
context.lineWidth = 1;
context.strokeStyle = darkGridColor;
context.fillStyle = darkGridColor;
{
context.save();
context.translate(-scrollX, 0.5 - scrollY);
var maxY = height + unzoom(scrollY);
for (var y = 2 * gridStep; y < maxY; y += 2 * gridStep) {
context.save();
context.translate(scrollX, zoom(y));
context.rotate(-Math.PI / 2);
context.fillText(String(y), 2, rulerAtRight ? zoom(width) - 7 : 13);
context.restore();
}
context.translate(0.5, -0.5);
var maxX = width + unzoom(scrollX);
for (var x = 2 * gridStep; x < maxX; x += 2 * gridStep) {
context.save();
context.fillText(String(x), zoom(x) + 2, rulerAtBottom ? scrollY + zoom(height) - 7 : scrollY + 13);
context.restore();
}
context.restore();
}
{
context.save();
if (rulerAtRight) {
context.translate(zoom(width), 0);
context.scale(-1, 1);
}
context.translate(-scrollX, 0.5 - scrollY);
var maxY = height + unzoom(scrollY);
for (var y = gridStep; y < maxY; y += gridStep) {
context.beginPath();
context.moveTo(scrollX, zoom(y));
var markLength = (y % (gridStep * 2)) ? 5 : 8;
context.lineTo(scrollX + markLength, zoom(y));
context.stroke();
}
context.strokeStyle = lightGridColor;
for (var y = gridSubStep; y < maxY; y += gridSubStep) {
if (!(y % gridStep)) {
continue;
}
context.beginPath();
context.moveTo(scrollX, zoom(y));
context.lineTo(scrollX + gridSubStep, zoom(y));
context.stroke();
}
context.restore();
}
{
context.save();
if (rulerAtBottom) {
context.translate(0, zoom(height));
context.scale(1, -1);
}
context.translate(0.5 - scrollX, -scrollY);
var maxX = width + unzoom(scrollX);
for (var x = gridStep; x < maxX; x += gridStep) {
context.beginPath();
context.moveTo(zoom(x), scrollY);
var markLength = (x % (gridStep * 2)) ? 5 : 8;
context.lineTo(zoom(x), scrollY + markLength);
context.stroke();
}
context.strokeStyle = lightGridColor;
for (var x = gridSubStep; x < maxX; x += gridSubStep) {
if (!(x % gridStep)) {
continue;
}
context.beginPath();
context.moveTo(zoom(x), scrollY);
context.lineTo(zoom(x), scrollY + gridSubStep);
context.stroke();
}
context.restore();
}
context.restore();
};
return HighlightOverlay;
}(common_1.Overlay));
exports.HighlightOverlay = HighlightOverlay;
var lightGridColor = 'rgba(0,0,0,0.2)';
var darkGridColor = 'rgba(0,0,0,0.7)';
var gridBackgroundColor = 'rgba(255, 255, 255, 0.8)';
function getElementLayoutType(elementInfo) {
if (elementInfo.layoutObjectName && (0, endWith_1.default)(elementInfo.layoutObjectName, 'Grid')) {
return 'grid';
}
if (elementInfo.layoutObjectName && elementInfo.layoutObjectName === 'LayoutNGFlexibleBox') {
return 'flex';
}
return null;
}
function createElementDescription(elementInfo, colorFormat) {
var elementInfoElement = (0, common_1.createElement)('div', 'element-info');
var elementInfoHeaderElement = (0, common_1.createChild)(elementInfoElement, 'div', 'element-info-header');
var layoutType = getElementLayoutType(elementInfo);
if (layoutType) {
(0, common_1.createChild)(elementInfoHeaderElement, 'div', "element-layout-type ".concat(layoutType));
}
var descriptionElement = (0, common_1.createChild)(elementInfoHeaderElement, 'div', 'element-description');
var tagNameElement = (0, common_1.createChild)(descriptionElement, 'span', 'material-tag-name');
tagNameElement.textContent = elementInfo.tagName;
var nodeIdElement = (0, common_1.createChild)(descriptionElement, 'span', 'material-node-id');
var maxLength = 80;
nodeIdElement.textContent = elementInfo.idValue ? '#' + (0, common_1.ellipsify)(elementInfo.idValue, maxLength) : '';
nodeIdElement.classList.toggle('hidden', !elementInfo.idValue);
var classNameElement = (0, common_1.createChild)(descriptionElement, 'span', 'material-class-name');
if (nodeIdElement.textContent.length < maxLength) {
classNameElement.textContent = (0, common_1.ellipsify)(elementInfo.className || '', maxLength - nodeIdElement.textContent.length);
}
classNameElement.classList.toggle('hidden', !elementInfo.className);
var dimensionsElement = (0, common_1.createChild)(elementInfoHeaderElement, 'div', 'dimensions');
(0, common_1.createChild)(dimensionsElement, 'span', 'material-node-width').textContent =
String(Math.round(elementInfo.nodeWidth * 100) / 100);
(0, common_1.createTextChild)(dimensionsElement, '\u00d7');
(0, common_1.createChild)(dimensionsElement, 'span', 'material-node-height').textContent =
String(Math.round(elementInfo.nodeHeight * 100) / 100);
var style = elementInfo.style || {};
var elementInfoBodyElement;
if (elementInfo.isLockedAncestor) {
addTextRow('Showing content-visibility ancestor', '');
}
if (elementInfo.isLocked) {
addTextRow('Descendants are skipped due to content-visibility', '');
}
var color = style['color'];
if (color && color !== '#00000000') {
addColorRow('Color', color, colorFormat);
}
var fontFamily = style['font-family'];
var fontSize = style['font-size'];
if (fontFamily && fontSize !== '0px') {
addTextRow('Font', "".concat(fontSize, " ").concat(fontFamily));
}
var bgcolor = style['background-color'];
if (bgcolor && bgcolor !== '#00000000') {
addColorRow('Background', bgcolor, colorFormat);
}
var margin = style['margin'];
if (margin && margin !== '0px') {
addTextRow('Margin', margin);
}
var padding = style['padding'];
if (padding && padding !== '0px') {
addTextRow('Padding', padding);
}
var cbgColor = elementInfo.contrast ? elementInfo.contrast.backgroundColor : null;
var hasContrastInfo = color && color !== '#00000000' && cbgColor && cbgColor !== '#00000000';
if (elementInfo.showAccessibilityInfo) {
addSection('Accessibility');
if (hasContrastInfo && style['color'] && elementInfo.contrast) {
addContrastRow(style['color'], elementInfo.contrast);
}
addTextRow('Name', elementInfo.accessibleName);
addTextRow('Role', elementInfo.accessibleRole);
addIconRow('Keyboard-focusable', elementInfo.isKeyboardFocusable ? 'a11y-icon a11y-icon-ok' : 'a11y-icon a11y-icon-not-ok');
}
function ensureElementInfoBody() {
if (!elementInfoBodyElement) {
elementInfoBodyElement = (0, common_1.createChild)(elementInfoElement, 'div', 'element-info-body');
}
}
function addSection(name) {
ensureElementInfoBody();
var rowElement = (0, common_1.createChild)(elementInfoBodyElement, 'div', 'element-info-row element-info-section');
var nameElement = (0, common_1.createChild)(rowElement, 'div', 'section-name');
nameElement.textContent = name;
(0, common_1.createChild)((0, common_1.createChild)(rowElement, 'div', 'separator-container'), 'div', 'separator');
}
function addRow(name, rowClassName, valueClassName) {
ensureElementInfoBody();
var rowElement = (0, common_1.createChild)(elementInfoBodyElement, 'div', 'element-info-row');
if (rowClassName) {
rowElement.classList.add(rowClassName);
}
var nameElement = (0, common_1.createChild)(rowElement, 'div', 'element-info-name');
nameElement.textContent = name;
(0, common_1.createChild)(rowElement, 'div', 'element-info-gap');
return (0, common_1.createChild)(rowElement, 'div', valueClassName || '');
}
function addIconRow(name, value) {
(0, common_1.createChild)(addRow(name, '', 'element-info-value-icon'), 'div', value);
}
function addTextRow(name, value) {
(0, common_1.createTextChild)(addRow(name, '', 'element-info-value-text'), value);
}
function addColorRow(name, color, colorFormat) {
var valueElement = addRow(name, '', 'element-info-value-color');
var swatch = (0, common_1.createChild)(valueElement, 'div', 'color-swatch');
var inner = (0, common_1.createChild)(swatch, 'div', 'color-swatch-inner');
inner.style.backgroundColor = color;
(0, common_1.createTextChild)(valueElement, (0, highlight_common_1.formatColor)(color, colorFormat));
}
function addContrastRow(fgColor, contrast) {
var parsedFgColor = (0, highlight_common_1.parseHexa)(fgColor);
var parsedBgColor = (0, highlight_common_1.parseHexa)(contrast.backgroundColor);
parsedFgColor[3] *= contrast.textOpacity;
var valueElement = addRow('Contrast', '', 'element-info-value-contrast');
var sampleText = (0, common_1.createChild)(valueElement, 'div', 'contrast-text');
sampleText.style.color = (0, highlight_common_1.formatRgba)(parsedFgColor, 'rgb');
sampleText.style.backgroundColor = contrast.backgroundColor;
sampleText.textContent = 'Aa';
var valueSpan = (0, common_1.createChild)(valueElement, 'span');
if (contrast.contrastAlgorithm === 'apca') {
var percentage = (0, ColorUtils_1.contrastRatioAPCA)(parsedFgColor, parsedBgColor);
var threshold = (0, ColorUtils_1.getAPCAThreshold)(contrast.fontSize, contrast.fontWeight);
valueSpan.textContent = String(Math.floor(percentage * 100) / 100) + '%';
(0, common_1.createChild)(valueElement, 'div', threshold === null || Math.abs(percentage) < threshold ? 'a11y-icon a11y-icon-warning' :
'a11y-icon a11y-icon-ok');
}
else if (contrast.contrastAlgorithm === 'aa' || contrast.contrastAlgorithm === 'aaa') {
var ratio = (0, ColorUtils_1.contrastRatio)(parsedFgColor, parsedBgColor);
var threshold = (0, ColorUtils_1.getContrastThreshold)(contrast.fontSize, contrast.fontWeight)[contrast.contrastAlgorithm];
valueSpan.textContent = String(Math.floor(ratio * 100) / 100);
(0, common_1.createChild)(valueElement, 'div', ratio < threshold ? 'a11y-icon a11y-icon-warning' : 'a11y-icon a11y-icon-ok');
}
}
return elementInfoElement;
}
function drawElementTitle(tooltipContainer, elementInfo, colorFormat, bounds, canvasWidth, canvasHeight) {
tooltipContainer.innerHTML = '';
var wrapper = (0, common_1.createChild)(tooltipContainer, 'div');
var tooltipContent = (0, common_1.createChild)(wrapper, 'div', 'tooltip-content');
var tooltip = createElementDescription(elementInfo, colorFormat);
tooltipContent.appendChild(tooltip);
var titleWidth = tooltipContent.offsetWidth;
var titleHeight = tooltipContent.offsetHeight;
var arrowHalfWidth = 8;
var pageMargin = 2;
var arrowWidth = arrowHalfWidth * 2;
var arrowInset = arrowHalfWidth + 2;
var containerMinX = pageMargin + arrowInset;
var containerMaxX = canvasWidth - pageMargin - arrowInset - arrowWidth;
var boundsAreTooNarrow = bounds.maxX - bounds.minX < arrowWidth + 2 * arrowInset;
var arrowX;
if (boundsAreTooNarrow) {
arrowX = (bounds.minX + bounds.maxX) * 0.5 - arrowHalfWidth;
}
else {
var xFromLeftBound = bounds.minX + arrowInset;
var xFromRightBound = bounds.maxX - arrowInset - arrowWidth;
if (xFromLeftBound > containerMinX && xFromLeftBound < containerMaxX) {
arrowX = xFromLeftBound;
}
else {
arrowX = (0, common_1.constrainNumber)(containerMinX, xFromLeftBound, xFromRightBound);
}
}
var arrowHidden = arrowX < containerMinX || arrowX > containerMaxX;
var boxX = arrowX - arrowInset;
boxX = (0, common_1.constrainNumber)(boxX, pageMargin, canvasWidth - titleWidth - pageMargin);
var boxY = bounds.minY - arrowHalfWidth - titleHeight;
var onTop = true;
if (boxY < 0) {
boxY = Math.min(canvasHeight - titleHeight, bounds.maxY + arrowHalfWidth);
onTop = false;
}
else if (bounds.minY > canvasHeight) {
boxY = canvasHeight - arrowHalfWidth - titleHeight;
}
var includes = boxX >= bounds.minX && boxX + titleWidth <= bounds.maxX && boxY >= bounds.minY &&
boxY + titleHeight <= bounds.maxY;
var overlaps = boxX < bounds.maxX && boxX + titleWidth > bounds.minX && boxY < bounds.maxY && boxY + titleHeight > bounds.minY;
if (overlaps && !includes) {
tooltipContent.style.display = 'none';
return;
}
tooltipContent.style.top = boxY + 'px';
tooltipContent.style.left = boxX + 'px';
if (arrowHidden) {
return;
}
var tooltipArrow = (0, common_1.createChild)(tooltipContent, 'div', 'tooltip-arrow');
tooltipArrow.style.clipPath = onTop ? 'polygon(0 0, 100% 0, 50% 100%)' : 'polygon(50% 0, 0 100%, 100% 100%)';
tooltipArrow.style.top = (onTop ? titleHeight - 1 : -arrowHalfWidth) + 'px';
tooltipArrow.style.left = (arrowX - boxX) + 'px';
}
var DEFAULT_RULER_COLOR = 'rgba(128, 128, 128, 0.3)';
function drawRulers(context, bounds, rulerAtRight, rulerAtBottom, color, dash, canvasWidth, canvasHeight) {
context.save();
var width = canvasWidth;
var height = canvasHeight;
context.strokeStyle = color || DEFAULT_RULER_COLOR;
context.lineWidth = 1;
context.translate(0.5, 0.5);
if (dash) {
context.setLineDash([3, 3]);
}
if (rulerAtRight) {
for (var y in bounds.rightmostXForY) {
context.beginPath();
context.moveTo(width, Number(y));
context.lineTo(bounds.rightmostXForY[y], Number(y));
context.stroke();
}
}
else {
for (var y in bounds.leftmostXForY) {
context.beginPath();
context.moveTo(0, Number(y));
context.lineTo(bounds.leftmostXForY[y], Number(y));
context.stroke();
}
}
if (rulerAtBottom) {
for (var x in bounds.bottommostYForX) {
context.beginPath();
context.moveTo(Number(x), height);
context.lineTo(Number(x), bounds.topmostYForX[x]);
context.stroke();
}
}
else {
for (var x in bounds.topmostYForX) {
context.beginPath();
context.moveTo(Number(x), 0);
context.lineTo(Number(x), bounds.topmostYForX[x]);
context.stroke();
}
}
context.restore();
}