let elementProperties = {
'::after': ['terminal', 'jumpable'],
'::backdrop': ['terminal', 'jumpable'],
'::before': ['terminal', 'jumpable'],
'::cue': ['terminal'],
'::cue-region': ['terminal'],
'::first-letter': ['terminal', 'jumpable'],
'::first-line': ['terminal', 'jumpable'],
'::grammar-error': ['terminal'],
'::marker': ['terminal', 'jumpable'],
'::part': ['terminal', 'actionable'],
'::placeholder': ['terminal', 'jumpable'],
'::selection': ['terminal', 'jumpable'],
'::slotted': ['terminal'],
'::spelling-error': ['terminal'],
'::target-text': ['terminal'],
'::file-selector-button': ['terminal', 'actionable'],
'::deep': ['actionable'],
'::v-deep': ['actionable'],
'::ng-deep': ['actionable'],
':after': ['terminal', 'jumpable'],
':before': ['terminal', 'jumpable'],
':first-letter': ['terminal', 'jumpable'],
':first-line': ['terminal', 'jumpable'],
__default__: ['terminal', 'actionable'],
}
export function movePseudos(sel) {
let [pseudos] = movablePseudos(sel)
pseudos.forEach(([sel, pseudo]) => sel.removeChild(pseudo))
sel.nodes.push(...pseudos.map(([, pseudo]) => pseudo))
return sel
}
function movablePseudos(sel) {
let buffer = []
let lastSeenElement = null
for (let node of sel.nodes) {
if (node.type === 'combinator') {
buffer = buffer.filter(([, node]) => propertiesForPseudo(node).includes('jumpable'))
lastSeenElement = null
} else if (node.type === 'pseudo') {
if (isMovablePseudoElement(node)) {
lastSeenElement = node
buffer.push([sel, node, null])
} else if (lastSeenElement && isAttachablePseudoClass(node, lastSeenElement)) {
buffer.push([sel, node, lastSeenElement])
} else {
lastSeenElement = null
}
for (let sub of node.nodes ?? []) {
let [movable, lastSeenElementInSub] = movablePseudos(sub)
lastSeenElement = lastSeenElementInSub || lastSeenElement
buffer.push(...movable)
}
}
}
return [buffer, lastSeenElement]
}
function isPseudoElement(node) {
return node.value.startsWith('::') || elementProperties[node.value] !== undefined
}
function isMovablePseudoElement(node) {
return isPseudoElement(node) && propertiesForPseudo(node).includes('terminal')
}
function isAttachablePseudoClass(node, pseudo) {
if (node.type !== 'pseudo') return false
if (isPseudoElement(node)) return false
return propertiesForPseudo(pseudo).includes('actionable')
}
function propertiesForPseudo(pseudo) {
return elementProperties[pseudo.value] ?? elementProperties.__default__
}