forked from jon49/html-traits
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
139 lines (121 loc) · 4.15 KB
/
index.js
File metadata and controls
139 lines (121 loc) · 4.15 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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Registry for trait classes
var traitRegistry = new Map()
// Register a trait class
function defineTrait(name, Class) {
name = name.trim()
if (name === '') throw new Error('Trait name cannot be empty')
if (traitRegistry.has(name)) throw new Error(`Trait "${name}" already defined`)
traitRegistry.set(name, Class)
}
// Map to track element -> trait instance(s)
var elementTraitInstances = new WeakMap()
// Apply trait(s) to an element
function applyTrait(element) {
var traitNames = element.getAttribute('traits')
if (!traitNames) return
var instances = elementTraitInstances.get(element) || []
for (let traitName of traitNames.split(/\s+/)) {
if (traitRegistry.has(traitName)) {
// Only instantiate if not already present for this element
var already = instances.some(function(inst) { return inst.constructor === traitRegistry.get(traitName) })
if (!already) {
var instance = new (traitRegistry.get(traitName))(element)
instances.push(instance)
}
}
}
if (instances.length) {
elementTraitInstances.set(element, instances)
}
}
// Handle trait removal when traits attribute changes
function handleTraitChanges(element, oldTraits, newTraits) {
var oldTraitNames = oldTraits ? oldTraits.split(/\s+/) : []
var newTraitNames = newTraits ? newTraits.split(/\s+/) : []
var removedTraits = oldTraitNames.filter(name => !newTraitNames.includes(name))
if (removedTraits.length > 0) {
var instances = elementTraitInstances.get(element) || []
var remainingInstances = []
for (let instance of instances) {
var traitName = getTraitNameFromInstance(instance)
if (removedTraits.includes(traitName)) {
// Call disconnectedCallback if it exists
if (typeof instance.disconnectedCallback === 'function') {
instance.disconnectedCallback()
}
} else {
remainingInstances.push(instance)
}
}
if (remainingInstances.length > 0) {
elementTraitInstances.set(element, remainingInstances)
} else {
elementTraitInstances.delete(element)
}
}
// Apply any new traits
if (newTraits) {
applyTrait(element)
}
}
// Helper function to get trait name from instance
function getTraitNameFromInstance(instance) {
for (let [name, Class] of traitRegistry) {
if (instance.constructor === Class) {
return name
}
}
return null
}
// Handle element removals for disconnectedCallback
function handleDisconnections(removedNode) {
if (removedNode.hasAttribute?.('traits')) {
handleDisconnect(removedNode)
}
// Also check descendants
for (let descendant of removedNode.querySelectorAll?.('[traits]') ?? []) {
handleDisconnect(descendant)
}
}
function handleDisconnect(removedNode) {
var instances = elementTraitInstances.get(removedNode)
for (let instance of instances ?? []) {
if (typeof instance.disconnectedCallback === 'function') {
instance.disconnectedCallback()
}
}
elementTraitInstances.delete(removedNode)
}
// Observe the DOM for elements with the "traits" attribute
var observer = new MutationObserver(function (records) {
for (let record of records) {
// Handle attribute changes
if (record.type === 'attributes' && record.attributeName === 'traits') {
handleTraitChanges(record.target, record.oldValue, record.target.getAttribute('traits'))
}
// Handle node additions
for (let node of Array.from(record.addedNodes)) {
if (node.nodeType === 1 && node.hasAttribute?.('traits')) {
applyTrait(node)
}
// Also check descendants
node.querySelectorAll?.('[traits]').forEach(applyTrait)
}
// Handle node removals
Array.from(record.removedNodes).forEach(handleDisconnections)
}
})
document.addEventListener('DOMContentLoaded', function() {
// Apply traits to existing elements on initial load
document.querySelectorAll('[traits]').forEach(applyTrait)
})
// Start observing the document
observer.observe(document, {
childList: true,
subtree: true,
attributes: true,
attributeOldValue: true,
attributeFilter: ['traits']
})
// Expose the trait registration function globally
window.defineTrait = defineTrait