UNPKG

luna-dom-highlighter

Version:

Highlighter for html elements

412 lines (411 loc) 16.8 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 __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var Component_1 = __importDefault(require("../share/Component")); var tool_highlight_1 = require("./overlay/tool_highlight"); var util_1 = require("../share/util"); var ResizeSensor_1 = __importDefault(require("licia/ResizeSensor")); var throttle_1 = __importDefault(require("licia/throttle")); var lowerCase_1 = __importDefault(require("licia/lowerCase")); var each_1 = __importDefault(require("licia/each")); var Color_1 = __importDefault(require("licia/Color")); var hex_1 = __importDefault(require("licia/hex")); var upperCase_1 = __importDefault(require("licia/upperCase")); var extend_1 = __importDefault(require("licia/extend")); var camelCase_1 = __importDefault(require("licia/camelCase")); var contain_1 = __importDefault(require("licia/contain")); var toNum_1 = __importDefault(require("licia/toNum")); var elementRoles_1 = __importDefault(require("./elementRoles")); var isStr_1 = __importDefault(require("licia/isStr")); require("path2d-polyfill"); var DomHighlighter = (function (_super) { __extends(DomHighlighter, _super); function DomHighlighter(container, options) { if (options === void 0) { options = {}; } var _this = _super.call(this, container, { compName: 'dom-highlighter' }, options) || this; _this.overlay = new tool_highlight_1.HighlightOverlay(window); _this.reset = function () { var viewportWidth = document.documentElement.clientWidth; var viewportHeight = document.documentElement.clientHeight; _this.overlay.reset({ viewportSize: { width: viewportWidth, height: viewportHeight, }, deviceScaleFactor: 1, pageScaleFactor: 1, pageZoomFactor: 1, emulationScaleFactor: 1, scrollX: window.scrollX, scrollY: window.scrollY, }); }; _this.initOptions(options, { showRulers: false, showExtensionLines: false, showInfo: true, showStyles: true, showAccessibilityInfo: true, colorFormat: 'hex', contentColor: 'rgba(111, 168, 220, .66)', paddingColor: 'rgba(147, 196, 125, .55)', borderColor: 'rgba(255, 229, 153, .66)', marginColor: 'rgba(246, 178, 107, .66)', monitorResize: true, }); _this.overlay.setContainer(container); _this.overlay.setPlatform('mac'); _this.redraw = (0, throttle_1.default)(function () { _this.reset(); _this.draw(); }, 16); _this.redraw(); _this.bindEvent(); return _this; } DomHighlighter.prototype.highlight = function (target, options) { if (options) { (0, extend_1.default)(this.options, options); } this.target = target; if (target instanceof HTMLElement && this.options.monitorResize) { if (this.resizeSensor) { this.resizeSensor.destroy(); } this.resizeSensor = new ResizeSensor_1.default(target); this.resizeSensor.addListener(this.redraw); } this.redraw(); }; DomHighlighter.prototype.hide = function () { this.target = null; this.redraw(); }; DomHighlighter.prototype.intercept = function (interceptor) { this.interceptor = interceptor; }; DomHighlighter.prototype.destroy = function () { window.removeEventListener('resize', this.redraw); window.removeEventListener('scroll', this.redraw); if (this.resizeSensor) { this.resizeSensor.destroy(); } _super.prototype.destroy.call(this); }; DomHighlighter.prototype.draw = function () { var target = this.target; if (!target) { return; } if (target instanceof Text) { this.drawText(target); } else { this.drawElement(target); } }; DomHighlighter.prototype.drawText = function (target) { var options = this.options; var range = document.createRange(); range.selectNode(target); var _a = range.getBoundingClientRect(), left = _a.left, top = _a.top, width = _a.width, height = _a.height; range.detach(); var highlight = { paths: [ { path: this.rectToPath({ left: left, top: top, width: width, height: height, }), fillColor: normalizeColor(options.contentColor), name: 'content', }, ], showExtensionLines: options.showExtensionLines, showRulers: options.showRulers, }; if (options.showInfo) { highlight.elementInfo = { tagName: '#text', nodeWidth: width, nodeHeight: height, }; } this.overlay.drawHighlight(highlight); }; DomHighlighter.prototype.drawElement = function (target) { var highlight = { paths: this.getPaths(target), showExtensionLines: this.options.showExtensionLines, showRulers: this.options.showRulers, colorFormat: this.options.colorFormat, }; if (this.options.showInfo) { highlight.elementInfo = this.getElementInfo(target); } if (this.interceptor) { var result = this.interceptor(highlight); if (result) { highlight = result; } } this.overlay.drawHighlight(highlight); }; DomHighlighter.prototype.getPaths = function (target) { var options = this.options; var computedStyle = window.getComputedStyle(target); var _a = target.getBoundingClientRect(), left = _a.left, top = _a.top, width = _a.width, height = _a.height; var getNumStyle = function (name) { return (0, util_1.pxToNum)(computedStyle.getPropertyValue(name)); }; var ml = getNumStyle('margin-left'); var mr = getNumStyle('margin-right'); var mt = getNumStyle('margin-top'); var mb = getNumStyle('margin-bottom'); var bl = getNumStyle('border-left-width'); var br = getNumStyle('border-right-width'); var bt = getNumStyle('border-top-width'); var bb = getNumStyle('border-bottom-width'); var pl = getNumStyle('padding-left'); var pr = getNumStyle('padding-right'); var pt = getNumStyle('padding-top'); var pb = getNumStyle('padding-bottom'); var contentPath = { path: this.rectToPath({ left: left + bl + pl, top: top + bt + pt, width: width - bl - pl - br - pr, height: height - bt - pt - bb - pb, }), fillColor: normalizeColor(options.contentColor), name: 'content', }; var paddingPath = { path: this.rectToPath({ left: left + bl, top: top + bt, width: width - bl - br, height: height - bt - bb, }), fillColor: normalizeColor(options.paddingColor), name: 'padding', }; var borderPath = { path: this.rectToPath({ left: left, top: top, width: width, height: height, }), fillColor: normalizeColor(options.borderColor), name: 'border', }; var marginPath = { path: this.rectToPath({ left: left - ml, top: top - mt, width: width + ml + mr, height: height + mt + mb, }), fillColor: normalizeColor(options.marginColor), name: 'margin', }; return [contentPath, paddingPath, borderPath, marginPath]; }; DomHighlighter.prototype.getElementInfo = function (target) { var _a = target.getBoundingClientRect(), width = _a.width, height = _a.height; var className = target.getAttribute('class') || ''; className = className .split(/\s+/) .map(function (c) { return '.' + c; }) .join(''); var elementInfo = { tagName: (0, lowerCase_1.default)(target.tagName), className: className, idValue: target.id, nodeWidth: width, nodeHeight: height, }; if (this.options.showStyles) { elementInfo.style = this.getStyles(target); } if (this.options.showAccessibilityInfo) { (0, extend_1.default)(elementInfo, this.getAccessibilityInfo(target)); } return elementInfo; }; DomHighlighter.prototype.getStyles = function (target) { var computedStyle = window.getComputedStyle(target); var hasTextChildren = false; var childNodes = target.childNodes; for (var i = 0, len = childNodes.length; i < len; i++) { if (childNodes[i].nodeType === 3) { hasTextChildren = true; } } var properties = []; if (hasTextChildren) { properties.push('color', 'font-family', 'font-size', 'line-height'); } properties.push('padding', 'margin', 'background-color'); return propertiesToValues(computedStyle, properties); }; DomHighlighter.prototype.getAccessibilityInfo = function (target) { var computedStyle = window.getComputedStyle(target); return __assign({ showAccessibilityInfo: true, contrast: __assign({ contrastAlgorithm: 'aa', textOpacity: 0.1 }, propertiesToValues(computedStyle, ['font-size', 'font-weight', 'background-color', 'text-opacity'], true)), isKeyboardFocusable: this.isFocusable(target) }, this.getAccessibleNameAndRole(target)); }; DomHighlighter.prototype.isFocusable = function (target) { var tagName = (0, lowerCase_1.default)(target.tagName); if ((0, contain_1.default)(['a', 'button', 'input', 'textarea', 'select', 'details'], tagName)) { return true; } var tabIdx = target.getAttribute('tabindex'); if (tabIdx && (0, toNum_1.default)(tabIdx) > -1) { return true; } return false; }; DomHighlighter.prototype.getAccessibleNameAndRole = function (target) { var name = target.getAttribute('labelledby') || target.getAttribute('aria-label'); var role = target.getAttribute('role'); var tagName = (0, lowerCase_1.default)(target.tagName); elementRoles_1.default.forEach(function (value) { var e_1, _a; if (role) { return; } var name = value[0]; var attributes = value[2]; if (name !== tagName) { return; } if (attributes) { try { for (var attributes_1 = __values(attributes), attributes_1_1 = attributes_1.next(); !attributes_1_1.done; attributes_1_1 = attributes_1.next()) { var attribute = attributes_1_1.value; if (target.getAttribute(attribute[0]) !== attribute[1]) { return; } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (attributes_1_1 && !attributes_1_1.done && (_a = attributes_1.return)) _a.call(attributes_1); } finally { if (e_1) throw e_1.error; } } } role = value[1]; }); return { accessibleName: name || target.getAttribute('title') || '', accessibleRole: role || 'generic', }; }; DomHighlighter.prototype.bindEvent = function () { var _this = this; window.addEventListener('resize', this.redraw); window.addEventListener('scroll', this.redraw); this.on('optionChange', function () { return _this.redraw(); }); }; DomHighlighter.prototype.rectToPath = function (_a) { var left = _a.left, top = _a.top, width = _a.width, height = _a.height; var path = []; path.push('M', left, top); path.push('L', left + width, top); path.push('L', left + width, top + height); path.push('L', left, top + height); path.push('Z'); return path; }; return DomHighlighter; }(Component_1.default)); exports.default = DomHighlighter; module.exports = DomHighlighter; module.exports.default = DomHighlighter; var regRgb = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/; var regRgba = /^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d*(?:\.\d+)?)\)$/; function isColor(color) { return regRgb.test(color) || regRgba.test(color); } function rgbToHex(rgb) { var color = Color_1.default.parse(rgb); var opacity = color.val[3] || 1; color.val = color.val.slice(0, 3); color.val.push(Math.round(255 * opacity)); return '#' + (0, upperCase_1.default)(hex_1.default.encode(color.val)); } function normalizeColor(color) { if ((0, isStr_1.default)(color)) { return color; } color = color; if (color.a) { return "rgba(".concat(color.r, ", ").concat(color.g, ", ").concat(color.b, ", ").concat(color.a, ")"); } return "rgb(".concat(color.r, ", ").concat(color.g, ", ").concat(color.b, ")"); } function propertiesToValues(computedStyle, properties, useCamelCase) { if (useCamelCase === void 0) { useCamelCase = false; } var ret = {}; (0, each_1.default)(properties, function (property) { var value = computedStyle[property === 'text-opacity' ? 'color' : property]; if (!value) { return; } if (isColor(value)) { value = rgbToHex(value); if (property === 'text-opacity') { value = value.slice(7); value = hex_1.default.decode(value)[0] / 255; } } if (useCamelCase) { property = (0, camelCase_1.default)(property); } ret[property] = value; }); return ret; }