@mariusandra/simmerjs
Version:
A pure Javascript reverse CSS selector engine which calculates a DOM element's unique CSS selector on the current page.
97 lines (82 loc) • 3.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isUniqueElementID = isUniqueElementID;
exports.isUniqueDataAttr = isUniqueDataAttr;
exports.wrap = wrap;
exports.default = _default;
var _querySelectorShadowDom = require("@mariusandra/query-selector-shadow-dom");
/**
* Verify a specific ID's uniqueness one the page
* @param {object} element. The element we are trying to build a selector for
* @param {object} state. The current selector state (has the stack and specificity sum)
*/
function isUniqueElementID(query, elementID) {
// use selector to query an element and see if it is a one-to-one selection
var results = query(`[id="${elementID}"]`) || [];
return results.length === 1;
}
function isUniqueDataAttr(query, dataAttr) {
// use selector to query an element and see if it is a one-to-one selection
var results = query(`[data-attr="${dataAttr}"]`) || [];
return results.length === 1;
}
function traverseAttribute(el, dir) {
const matched = [];
let cur = el[dir];
while (cur && cur.nodeType !== 9) {
if (cur.nodeType === 1) {
matched.push(wrap(cur));
}
cur = cur[dir];
}
return matched;
}
function wrap(el) {
/// When the DOM wrapper return the selected element it wrapps
/// it with helper methods which aid in analyzing the result
return {
el,
getClass: function () {
return this.el.getAttribute('class') || '';
},
getClasses: function () {
return this.getClass().split(' ').map(className => className.replace(/^\s\s*/, '').replace(/\s\s*$/, '')).filter(className => className.length > 0);
},
prevAll: function () {
return traverseAttribute(this.el, 'previousSibling');
},
nextAll: function () {
return traverseAttribute(this.el, 'nextSibling');
},
parent: function () {
return this.el.parentNode ? this.el.parentNode.nodeType === 11 ? wrap(this.el.parentNode.host) : wrap(this.el.parentNode) : null;
}
};
}
const INVALID_DOCUMENT = {
querySelectorAll() {
throw new Error('An invalid context has been provided to Simmer, it doesnt know how to query it');
}
};
const documentQuerySelector = scope => {
const document = typeof scope.querySelectorAll === 'function' ? scope : scope.document ? scope.document : INVALID_DOCUMENT;
return (selector, onError) => {
try {
return (0, _querySelectorShadowDom.querySelectorAllDeep)(selector, document);
} catch (ex) {
// handle error
onError(ex);
}
};
};
/**
* A DOM manipulation object. Provides both CSS selector querying for verifications and a wrapper for the elements them selves to provide
* key behavioural methods, such as sibling querying etc.
* @param {object} elementOrSelector. An element we wish to wrapper or a CSS query string
*/
function _default(scope, configuredQueryEngine) {
const queryEngine = typeof configuredQueryEngine === 'function' ? configuredQueryEngine : documentQuerySelector(scope); // If no selector we return an empty array - no CSS selector will ever be generated in this situation!
return (selector, onError) => typeof selector !== 'string' ? [] : queryEngine(selector, onError, scope);
}