luna-dom-highlighter
Version:
Highlighter for html elements
412 lines (411 loc) • 16.8 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 __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;
}