|
/* UTIL: generate all possible combos */ |
|
/* ex: 'div > div.maybe-useful > p:nth-child(1)' -> tokenize -> permute -> test */ |
|
var powerSet = ( list ) => { |
|
var set = [], |
|
listSize = list.length, |
|
combinationsCount = (1 << listSize); |
|
|
|
for (var i = 1; i < combinationsCount ; i++ , set.push(combination) ) |
|
for (var j=0, combination = [];j<listSize;j++) |
|
if ((i & (1 << j))) |
|
combination.push(list[j]); |
|
return set; |
|
} |
|
|
|
/* generate a universally unique selector for the desired element */ |
|
var cssPath = (el) => { |
|
if (typeof el === 'string') el = document.querySelector(el) |
|
if (!(el instanceof Element)) |
|
return -1; |
|
var path = []; |
|
while (el.nodeType === Node.ELEMENT_NODE) { |
|
var selector = el.nodeName.toLowerCase(); |
|
if (el.id) { |
|
selector = '#' + el.id; |
|
path.unshift(selector); |
|
break; |
|
} |
|
if(true) { |
|
var sib = el; |
|
var nth = 1; |
|
while (sib = sib.previousElementSibling) { |
|
if (sib.nodeName.toLowerCase() == selector){ |
|
nth++; |
|
} |
|
} |
|
if (nth != 1 || el.nextElementSibling) selector += ":nth-of-type(" + nth + ")"; |
|
} |
|
// Pretty significant impact on performance... |
|
// if(el.className){ |
|
// selector += '.' + el.className.split(' ').filter(e => e.trim())[1].join('.'); |
|
// } |
|
path.unshift(selector); |
|
if(document.querySelectorAll(path.join(" > ")).length===1) { |
|
return path.join(" > ") |
|
} |
|
el = el.parentNode; |
|
} |
|
return path.join(" > "); |
|
} |
|
|
|
var splitPermute = (s) => s.split(/ > /).map( r => powerSet(r.split(/(?=[#\.:])/)).map(sel => sel.join(''))) |
|
var shortestPath = (agg, e=[], i) => ((!agg || e.length<agg.length) ? e : agg) |
|
var checkVs = (target) => { |
|
return el => { |
|
return $$(el).length === 1 && $$(el)[0] === target |
|
} |
|
} |
|
|
|
var reducePath = (pre="", opts=[], target, max=1000, join=' > ') => { |
|
res = [] |
|
if(pre.length >= max) return []; |
|
if(opts.length===0) return [pre.trim()]; |
|
if (opts.length) { |
|
y = opts[0] |
|
res = y.flatMap((yi) => { |
|
if(pre){ |
|
prior = [pre, yi.trim()].filter(t => t.trim()).join(join) |
|
} else { |
|
prior = yi.trim() |
|
} |
|
|
|
res = reducePath(prior, opts.slice(1), target, max, ' > ') || [] |
|
|
|
if(res && res.length) return res |
|
return [] |
|
}) |
|
return res.filter(e => e && e.trim()) |
|
|
|
} else { |
|
return [] |
|
} |
|
return res.flat() |
|
} |
|
|
|
var findSelector = (elem) => { |
|
node = typeof elem === 'string' ? $$(elem)[0] : elem; |
|
selector = cssPath(node); |
|
if(selector===-1) return -1; |
|
perms = splitPermute(selector).filter(s => s.length <= selector.length).sort(); |
|
paths = reducePath('', perms, node, selector.length).filter(checkVs(node)) |
|
return paths.length ? paths.reduce(shortestPath) : selector |
|
} |
|
|
|
var SmartSelect = { |
|
cssPath, |
|
findSelector, |
|
} |