-
Notifications
You must be signed in to change notification settings - Fork 55
Expand file tree
/
Copy pathtag-observer.ts
More file actions
67 lines (61 loc) · 2.38 KB
/
tag-observer.ts
File metadata and controls
67 lines (61 loc) · 2.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
type Parse = (str: string) => string[]
type Found = (el: Element, controller: Element | ShadowRoot, tag: string, ...parsed: string[]) => void
function closestShadowPiercing(el: Element, tagName: string): Element | null {
const closest: Element | null = el.closest(tagName)
if (!closest) {
const shadow = el.getRootNode()
if (!(shadow instanceof ShadowRoot)) return null
return shadow.host.closest(tagName)
}
return closest
}
export const parseElementTags = (el: Element, tag: string, parse: Parse) =>
(el.getAttribute(tag) || '')
.trim()
.split(/\s+/g)
.map((tagPart: string) => parse(tagPart))
const registry = new Map<string, [Parse, Found]>()
const observer = new MutationObserver((mutations: MutationRecord[]) => {
for (const mutation of mutations) {
if (mutation.type === 'attributes') {
const tag = mutation.attributeName!
const el = mutation.target
if (el instanceof Element && registry.has(tag)) {
const [parse, found] = registry.get(tag)!
for (const [tagName, ...meta] of parseElementTags(el, tag, parse)) {
const controller = closestShadowPiercing(el, tagName)
if (controller) found(el, controller, tag, ...meta)
}
}
} else if (mutation.addedNodes.length) {
for (const node of mutation.addedNodes) {
if (node instanceof Element) observeElementForTags(node)
}
}
}
})
export const registerTag = (tag: string, parse: Parse, found: Found) => {
if (registry.has(tag)) throw new Error('duplicate tag')
registry.set(tag, [parse, found])
}
export const observeElementForTags = (root: Element | ShadowRoot) => {
for (const [tag, [parse, found]] of registry) {
for (const el of root.querySelectorAll(`[${tag}]`)) {
for (const [tagName, ...meta] of parseElementTags(el, tag, parse)) {
const controller = closestShadowPiercing(el, tagName)
if (controller) found(el, controller, tag, ...meta)
}
}
if (root instanceof Element && root.hasAttribute(tag)) {
for (const [tagName, ...meta] of parseElementTags(root, tag, parse)) {
const controller = closestShadowPiercing(root, tagName)
if (controller) found(root, controller, tag, ...meta)
}
}
}
observer.observe(root instanceof Element ? root.ownerDocument : root, {
childList: true,
subtree: true,
attributeFilter: Array.from(registry.keys())
})
}