UNPKG

luna-dom-highlighter

Version:

Highlighter for html elements

468 lines (467 loc) 22.1 kB
"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(); }