tangled
alpha
login
or
join now
yippee.fun
/
morphlex
0
fork
atom
Precise DOM morphing
morphing
typescript
dom
0
fork
atom
overview
issues
pulls
pipelines
Move options to `this`
joel.drapper.me
2 years ago
691223b7
e83be73a
+103
-63
3 changed files
expand all
collapse all
unified
split
dist
morphlex.d.ts
morphlex.js
src
morphlex.ts
+2
-1
dist/morphlex.d.ts
···
1
-
export interface Options {
2
ignoreActiveValue?: boolean;
3
preserveModifiedValues?: boolean;
4
beforeNodeMorphed?: (node: Node, referenceNode: Node) => boolean;
···
13
afterPropertyUpdated?: (node: Node, propertyName: PropertyKey, previousValue: unknown) => void;
14
}
15
export declare function morph(node: ChildNode, reference: ChildNode | string, options?: Options): void;
0
···
1
+
interface Options {
2
ignoreActiveValue?: boolean;
3
preserveModifiedValues?: boolean;
4
beforeNodeMorphed?: (node: Node, referenceNode: Node) => boolean;
···
13
afterPropertyUpdated?: (node: Node, propertyName: PropertyKey, previousValue: unknown) => void;
14
}
15
export declare function morph(node: ChildNode, reference: ChildNode | string, options?: Options): void;
16
+
export {};
+49
-28
dist/morphlex.js
···
16
}
17
}
18
class Morph {
19
-
#options;
20
#idMap;
21
#sensivityMap;
0
0
0
0
0
0
0
0
0
0
0
0
22
constructor(options = {}) {
23
-
this.#options = options;
24
this.#idMap = new WeakMap();
25
this.#sensivityMap = new WeakMap();
26
-
Object.freeze(this.#options);
0
0
0
0
0
0
0
0
0
0
0
27
Object.freeze(this);
28
}
29
morph(node, reference) {
···
86
}
87
}
88
#morphMatchingElementNode(node, reference) {
89
-
if (!(this.#options.beforeNodeMorphed?.(node, reference) ?? true)) return;
90
if (node.hasAttributes() || reference.hasAttributes()) this.#morphAttributes(node, reference);
91
if (isHead(node)) {
92
this.#morphHead(node, reference);
93
} else if (node.hasChildNodes() || reference.hasChildNodes()) this.#morphChildNodes(node, reference);
94
-
this.#options.afterNodeMorphed?.(node, reference);
95
}
96
#morphOtherNode(node, reference) {
97
-
if (!(this.#options.beforeNodeMorphed?.(node, reference) ?? true)) return;
98
if (node.nodeType === reference.nodeType && node.nodeValue !== null && reference.nodeValue !== null) {
99
// Handle text nodes, comments, and CDATA sections.
100
this.#updateProperty(node, "nodeValue", reference.nodeValue);
101
} else this.#replaceNode(node, reference.cloneNode(true));
102
-
this.#options.afterNodeMorphed?.(node, reference);
103
}
104
#morphHead(node, reference) {
105
const refChildNodesMap = new Map();
···
118
#morphAttributes(element, reference) {
119
// Remove any excess attributes from the element that aren’t present in the reference.
120
for (const { name, value } of element.attributes) {
121
-
if (!reference.hasAttribute(name) && (this.#options.beforeAttributeUpdated?.(element, name, null) ?? true)) {
122
element.removeAttribute(name);
123
-
this.#options.afterAttributeUpdated?.(element, name, value);
124
}
125
}
126
// Copy attributes from the reference to the element, if they don’t already match.
127
for (const { name, value } of reference.attributes) {
128
const previousValue = element.getAttribute(name);
129
-
if (previousValue !== value && (this.#options.beforeAttributeUpdated?.(element, name, value) ?? true)) {
130
element.setAttribute(name, value);
131
-
this.#options.afterAttributeUpdated?.(element, name, previousValue);
132
}
133
}
134
// For certain types of elements, we need to do some extra work to ensure
···
139
this.#updateProperty(element, "indeterminate", reference.indeterminate);
140
if (
141
element.type !== "file" &&
142
-
!(this.#options.ignoreActiveValue && document.activeElement === element) &&
143
-
!(this.#options.preserveModifiedValues && element.name === reference.name && element.value !== element.defaultValue)
144
) {
145
this.#updateProperty(element, "value", reference.value);
146
}
···
149
} else if (
150
isTextArea(element) &&
151
isTextArea(reference) &&
152
-
!(this.#options.ignoreActiveValue && document.activeElement === element) &&
153
-
!(this.#options.preserveModifiedValues && element.name === reference.name && element.value !== element.defaultValue)
154
) {
155
this.#updateProperty(element, "value", reference.value);
156
const text = element.firstElementChild;
···
183
}
184
}
185
#morphChildElement(child, reference, parent) {
186
-
if (!(this.#options.beforeNodeMorphed?.(child, reference) ?? true)) return;
187
const refIdSet = this.#idMap.get(reference);
188
// Generate the array in advance of the loop
189
const refSetArray = refIdSet ? [...refIdSet] : [];
···
216
this.#morphNode(nextMatchByTagName, reference);
217
} else {
218
const newNode = reference.cloneNode(true);
219
-
if (this.#options.beforeNodeAdded?.(newNode) ?? true) {
220
this.#insertBefore(parent, newNode, child);
221
-
this.#options.afterNodeAdded?.(newNode);
222
}
223
}
224
-
this.#options.afterNodeMorphed?.(child, reference);
225
}
226
#updateProperty(node, propertyName, newValue) {
227
const previousValue = node[propertyName];
228
-
if (previousValue !== newValue && (this.#options.beforePropertyUpdated?.(node, propertyName, newValue) ?? true)) {
229
node[propertyName] = newValue;
230
-
this.#options.afterPropertyUpdated?.(node, propertyName, previousValue);
231
}
232
}
233
#replaceNode(node, newNode) {
234
-
if ((this.#options.beforeNodeRemoved?.(node) ?? true) && (this.#options.beforeNodeAdded?.(newNode) ?? true)) {
235
node.replaceWith(newNode);
236
-
this.#options.afterNodeAdded?.(newNode);
237
-
this.#options.afterNodeRemoved?.(node);
238
}
239
}
240
#insertBefore(parent, node, insertionPoint) {
···
256
parent.insertBefore(node, insertionPoint);
257
}
258
#appendChild(node, newNode) {
259
-
if (this.#options.beforeNodeAdded?.(newNode) ?? true) {
260
node.appendChild(newNode);
261
-
this.#options.afterNodeAdded?.(newNode);
262
}
263
}
264
#removeNode(node) {
265
-
if (this.#options.beforeNodeRemoved?.(node) ?? true) {
266
node.remove();
267
-
this.#options.afterNodeRemoved?.(node);
268
}
269
}
270
}
···
16
}
17
}
18
class Morph {
0
19
#idMap;
20
#sensivityMap;
21
+
#ignoreActiveValue;
22
+
#preserveModifiedValues;
23
+
#beforeNodeMorphed;
24
+
#afterNodeMorphed;
25
+
#beforeNodeAdded;
26
+
#afterNodeAdded;
27
+
#beforeNodeRemoved;
28
+
#afterNodeRemoved;
29
+
#beforeAttributeUpdated;
30
+
#afterAttributeUpdated;
31
+
#beforePropertyUpdated;
32
+
#afterPropertyUpdated;
33
constructor(options = {}) {
0
34
this.#idMap = new WeakMap();
35
this.#sensivityMap = new WeakMap();
36
+
this.#ignoreActiveValue = options.ignoreActiveValue || false;
37
+
this.#preserveModifiedValues = options.preserveModifiedValues || false;
38
+
this.#beforeNodeMorphed = options.beforeNodeMorphed;
39
+
this.#afterNodeMorphed = options.afterNodeMorphed;
40
+
this.#beforeNodeAdded = options.beforeNodeAdded;
41
+
this.#afterNodeAdded = options.afterNodeAdded;
42
+
this.#beforeNodeRemoved = options.beforeNodeRemoved;
43
+
this.#afterNodeRemoved = options.afterNodeRemoved;
44
+
this.#beforeAttributeUpdated = options.beforeAttributeUpdated;
45
+
this.#afterAttributeUpdated = options.afterAttributeUpdated;
46
+
this.#beforePropertyUpdated = options.beforePropertyUpdated;
47
+
this.#afterPropertyUpdated = options.afterPropertyUpdated;
48
Object.freeze(this);
49
}
50
morph(node, reference) {
···
107
}
108
}
109
#morphMatchingElementNode(node, reference) {
110
+
if (!(this.#beforeNodeMorphed?.(node, reference) ?? true)) return;
111
if (node.hasAttributes() || reference.hasAttributes()) this.#morphAttributes(node, reference);
112
if (isHead(node)) {
113
this.#morphHead(node, reference);
114
} else if (node.hasChildNodes() || reference.hasChildNodes()) this.#morphChildNodes(node, reference);
115
+
this.#afterNodeMorphed?.(node, reference);
116
}
117
#morphOtherNode(node, reference) {
118
+
if (!(this.#beforeNodeMorphed?.(node, reference) ?? true)) return;
119
if (node.nodeType === reference.nodeType && node.nodeValue !== null && reference.nodeValue !== null) {
120
// Handle text nodes, comments, and CDATA sections.
121
this.#updateProperty(node, "nodeValue", reference.nodeValue);
122
} else this.#replaceNode(node, reference.cloneNode(true));
123
+
this.#afterNodeMorphed?.(node, reference);
124
}
125
#morphHead(node, reference) {
126
const refChildNodesMap = new Map();
···
139
#morphAttributes(element, reference) {
140
// Remove any excess attributes from the element that aren’t present in the reference.
141
for (const { name, value } of element.attributes) {
142
+
if (!reference.hasAttribute(name) && (this.#beforeAttributeUpdated?.(element, name, null) ?? true)) {
143
element.removeAttribute(name);
144
+
this.#afterAttributeUpdated?.(element, name, value);
145
}
146
}
147
// Copy attributes from the reference to the element, if they don’t already match.
148
for (const { name, value } of reference.attributes) {
149
const previousValue = element.getAttribute(name);
150
+
if (previousValue !== value && (this.#beforeAttributeUpdated?.(element, name, value) ?? true)) {
151
element.setAttribute(name, value);
152
+
this.#afterAttributeUpdated?.(element, name, previousValue);
153
}
154
}
155
// For certain types of elements, we need to do some extra work to ensure
···
160
this.#updateProperty(element, "indeterminate", reference.indeterminate);
161
if (
162
element.type !== "file" &&
163
+
!(this.#ignoreActiveValue && document.activeElement === element) &&
164
+
!(this.#preserveModifiedValues && element.name === reference.name && element.value !== element.defaultValue)
165
) {
166
this.#updateProperty(element, "value", reference.value);
167
}
···
170
} else if (
171
isTextArea(element) &&
172
isTextArea(reference) &&
173
+
!(this.#ignoreActiveValue && document.activeElement === element) &&
174
+
!(this.#preserveModifiedValues && element.name === reference.name && element.value !== element.defaultValue)
175
) {
176
this.#updateProperty(element, "value", reference.value);
177
const text = element.firstElementChild;
···
204
}
205
}
206
#morphChildElement(child, reference, parent) {
207
+
if (!(this.#beforeNodeMorphed?.(child, reference) ?? true)) return;
208
const refIdSet = this.#idMap.get(reference);
209
// Generate the array in advance of the loop
210
const refSetArray = refIdSet ? [...refIdSet] : [];
···
237
this.#morphNode(nextMatchByTagName, reference);
238
} else {
239
const newNode = reference.cloneNode(true);
240
+
if (this.#beforeNodeAdded?.(newNode) ?? true) {
241
this.#insertBefore(parent, newNode, child);
242
+
this.#afterNodeAdded?.(newNode);
243
}
244
}
245
+
this.#afterNodeMorphed?.(child, reference);
246
}
247
#updateProperty(node, propertyName, newValue) {
248
const previousValue = node[propertyName];
249
+
if (previousValue !== newValue && (this.#beforePropertyUpdated?.(node, propertyName, newValue) ?? true)) {
250
node[propertyName] = newValue;
251
+
this.#afterPropertyUpdated?.(node, propertyName, previousValue);
252
}
253
}
254
#replaceNode(node, newNode) {
255
+
if ((this.#beforeNodeRemoved?.(node) ?? true) && (this.#beforeNodeAdded?.(newNode) ?? true)) {
256
node.replaceWith(newNode);
257
+
this.#afterNodeAdded?.(newNode);
258
+
this.#afterNodeRemoved?.(node);
259
}
260
}
261
#insertBefore(parent, node, insertionPoint) {
···
277
parent.insertBefore(node, insertionPoint);
278
}
279
#appendChild(node, newNode) {
280
+
if (this.#beforeNodeAdded?.(newNode) ?? true) {
281
node.appendChild(newNode);
282
+
this.#afterNodeAdded?.(newNode);
283
}
284
}
285
#removeNode(node) {
286
+
if (this.#beforeNodeRemoved?.(node) ?? true) {
287
node.remove();
288
+
this.#afterNodeRemoved?.(node);
289
}
290
}
291
}
+52
-34
src/morphlex.ts
···
28
readonly length: NodeListOf<T>["length"];
29
};
30
31
-
export interface Options {
32
ignoreActiveValue?: boolean;
33
preserveModifiedValues?: boolean;
34
-
35
beforeNodeMorphed?: (node: Node, referenceNode: Node) => boolean;
36
afterNodeMorphed?: (node: Node, referenceNode: Node) => void;
37
-
38
beforeNodeAdded?: (node: Node) => boolean;
39
afterNodeAdded?: (node: Node) => void;
40
-
41
beforeNodeRemoved?: (node: Node) => boolean;
42
afterNodeRemoved?: (node: Node) => void;
43
-
44
beforeAttributeUpdated?: (element: Element, attributeName: string, newValue: string | null) => boolean;
45
afterAttributeUpdated?: (element: Element, attributeName: string, previousValue: string | null) => void;
46
-
47
beforePropertyUpdated?: (node: Node, propertyName: PropertyKey, newValue: unknown) => boolean;
48
afterPropertyUpdated?: (node: Node, propertyName: PropertyKey, previousValue: unknown) => void;
49
}
···
68
}
69
70
class Morph {
71
-
readonly #options: Options;
72
readonly #idMap: IdMap;
73
readonly #sensivityMap: SensivityMap;
74
0
0
0
0
0
0
0
0
0
0
0
0
0
75
constructor(options: Options = {}) {
76
-
this.#options = options;
77
this.#idMap = new WeakMap();
78
this.#sensivityMap = new WeakMap();
79
80
-
Object.freeze(this.#options);
0
0
0
0
0
0
0
0
0
0
0
0
81
Object.freeze(this);
82
}
83
···
155
}
156
157
#morphMatchingElementNode(node: Element, reference: ReadonlyNode<Element>): void {
158
-
if (!(this.#options.beforeNodeMorphed?.(node, reference as ChildNode) ?? true)) return;
159
160
if (node.hasAttributes() || reference.hasAttributes()) this.#morphAttributes(node, reference);
161
···
163
this.#morphHead(node, reference as ReadonlyNode<HTMLHeadElement>);
164
} else if (node.hasChildNodes() || reference.hasChildNodes()) this.#morphChildNodes(node, reference);
165
166
-
this.#options.afterNodeMorphed?.(node, reference as ChildNode);
167
}
168
169
#morphOtherNode(node: ChildNode, reference: ReadonlyNode<ChildNode>): void {
170
-
if (!(this.#options.beforeNodeMorphed?.(node, reference as ChildNode) ?? true)) return;
171
172
if (node.nodeType === reference.nodeType && node.nodeValue !== null && reference.nodeValue !== null) {
173
// Handle text nodes, comments, and CDATA sections.
174
this.#updateProperty(node, "nodeValue", reference.nodeValue);
175
} else this.#replaceNode(node, reference.cloneNode(true));
176
177
-
this.#options.afterNodeMorphed?.(node, reference as ChildNode);
178
}
179
180
#morphHead(node: HTMLHeadElement, reference: ReadonlyNode<HTMLHeadElement>): void {
···
199
#morphAttributes(element: Element, reference: ReadonlyNode<Element>): void {
200
// Remove any excess attributes from the element that aren’t present in the reference.
201
for (const { name, value } of element.attributes) {
202
-
if (!reference.hasAttribute(name) && (this.#options.beforeAttributeUpdated?.(element, name, null) ?? true)) {
203
element.removeAttribute(name);
204
-
this.#options.afterAttributeUpdated?.(element, name, value);
205
}
206
}
207
208
// Copy attributes from the reference to the element, if they don’t already match.
209
for (const { name, value } of reference.attributes) {
210
const previousValue = element.getAttribute(name);
211
-
if (previousValue !== value && (this.#options.beforeAttributeUpdated?.(element, name, value) ?? true)) {
212
element.setAttribute(name, value);
213
-
this.#options.afterAttributeUpdated?.(element, name, previousValue);
214
}
215
}
216
···
222
this.#updateProperty(element, "indeterminate", reference.indeterminate);
223
if (
224
element.type !== "file" &&
225
-
!(this.#options.ignoreActiveValue && document.activeElement === element) &&
226
-
!(this.#options.preserveModifiedValues && element.name === reference.name && element.value !== element.defaultValue)
227
) {
228
this.#updateProperty(element, "value", reference.value);
229
}
···
232
} else if (
233
isTextArea(element) &&
234
isTextArea(reference) &&
235
-
!(this.#options.ignoreActiveValue && document.activeElement === element) &&
236
-
!(this.#options.preserveModifiedValues && element.name === reference.name && element.value !== element.defaultValue)
237
) {
238
this.#updateProperty(element, "value", reference.value);
239
···
272
}
273
274
#morphChildElement(child: Element, reference: ReadonlyNode<Element>, parent: Element): void {
275
-
if (!(this.#options.beforeNodeMorphed?.(child, reference as ChildNode) ?? true)) return;
276
277
const refIdSet = this.#idMap.get(reference);
278
···
314
this.#morphNode(nextMatchByTagName, reference);
315
} else {
316
const newNode = reference.cloneNode(true);
317
-
if (this.#options.beforeNodeAdded?.(newNode) ?? true) {
318
this.#insertBefore(parent, newNode, child);
319
-
this.#options.afterNodeAdded?.(newNode);
320
}
321
}
322
323
-
this.#options.afterNodeMorphed?.(child, reference as ChildNode);
324
}
325
326
#updateProperty<N extends Node, P extends keyof N>(node: N, propertyName: P, newValue: N[P]): void {
327
const previousValue = node[propertyName];
328
329
-
if (previousValue !== newValue && (this.#options.beforePropertyUpdated?.(node, propertyName, newValue) ?? true)) {
330
node[propertyName] = newValue;
331
-
this.#options.afterPropertyUpdated?.(node, propertyName, previousValue);
332
}
333
}
334
335
#replaceNode(node: ChildNode, newNode: Node): void {
336
-
if ((this.#options.beforeNodeRemoved?.(node) ?? true) && (this.#options.beforeNodeAdded?.(newNode) ?? true)) {
337
node.replaceWith(newNode);
338
-
this.#options.afterNodeAdded?.(newNode);
339
-
this.#options.afterNodeRemoved?.(node);
340
}
341
}
342
···
366
}
367
368
#appendChild(node: ParentNode, newNode: Node): void {
369
-
if (this.#options.beforeNodeAdded?.(newNode) ?? true) {
370
node.appendChild(newNode);
371
-
this.#options.afterNodeAdded?.(newNode);
372
}
373
}
374
375
#removeNode(node: ChildNode): void {
376
-
if (this.#options.beforeNodeRemoved?.(node) ?? true) {
377
node.remove();
378
-
this.#options.afterNodeRemoved?.(node);
379
}
380
}
381
}
···
28
readonly length: NodeListOf<T>["length"];
29
};
30
31
+
interface Options {
32
ignoreActiveValue?: boolean;
33
preserveModifiedValues?: boolean;
0
34
beforeNodeMorphed?: (node: Node, referenceNode: Node) => boolean;
35
afterNodeMorphed?: (node: Node, referenceNode: Node) => void;
0
36
beforeNodeAdded?: (node: Node) => boolean;
37
afterNodeAdded?: (node: Node) => void;
0
38
beforeNodeRemoved?: (node: Node) => boolean;
39
afterNodeRemoved?: (node: Node) => void;
0
40
beforeAttributeUpdated?: (element: Element, attributeName: string, newValue: string | null) => boolean;
41
afterAttributeUpdated?: (element: Element, attributeName: string, previousValue: string | null) => void;
0
42
beforePropertyUpdated?: (node: Node, propertyName: PropertyKey, newValue: unknown) => boolean;
43
afterPropertyUpdated?: (node: Node, propertyName: PropertyKey, previousValue: unknown) => void;
44
}
···
63
}
64
65
class Morph {
0
66
readonly #idMap: IdMap;
67
readonly #sensivityMap: SensivityMap;
68
69
+
readonly #ignoreActiveValue: boolean;
70
+
readonly #preserveModifiedValues: boolean;
71
+
readonly #beforeNodeMorphed?: (node: Node, referenceNode: Node) => boolean;
72
+
readonly #afterNodeMorphed?: (node: Node, referenceNode: Node) => void;
73
+
readonly #beforeNodeAdded?: (node: Node) => boolean;
74
+
readonly #afterNodeAdded?: (node: Node) => void;
75
+
readonly #beforeNodeRemoved?: (node: Node) => boolean;
76
+
readonly #afterNodeRemoved?: (node: Node) => void;
77
+
readonly #beforeAttributeUpdated?: (element: Element, attributeName: string, newValue: string | null) => boolean;
78
+
readonly #afterAttributeUpdated?: (element: Element, attributeName: string, previousValue: string | null) => void;
79
+
readonly #beforePropertyUpdated?: (node: Node, propertyName: PropertyKey, newValue: unknown) => boolean;
80
+
readonly #afterPropertyUpdated?: (node: Node, propertyName: PropertyKey, previousValue: unknown) => void;
81
+
82
constructor(options: Options = {}) {
0
83
this.#idMap = new WeakMap();
84
this.#sensivityMap = new WeakMap();
85
86
+
this.#ignoreActiveValue = options.ignoreActiveValue || false;
87
+
this.#preserveModifiedValues = options.preserveModifiedValues || false;
88
+
this.#beforeNodeMorphed = options.beforeNodeMorphed;
89
+
this.#afterNodeMorphed = options.afterNodeMorphed;
90
+
this.#beforeNodeAdded = options.beforeNodeAdded;
91
+
this.#afterNodeAdded = options.afterNodeAdded;
92
+
this.#beforeNodeRemoved = options.beforeNodeRemoved;
93
+
this.#afterNodeRemoved = options.afterNodeRemoved;
94
+
this.#beforeAttributeUpdated = options.beforeAttributeUpdated;
95
+
this.#afterAttributeUpdated = options.afterAttributeUpdated;
96
+
this.#beforePropertyUpdated = options.beforePropertyUpdated;
97
+
this.#afterPropertyUpdated = options.afterPropertyUpdated;
98
+
99
Object.freeze(this);
100
}
101
···
173
}
174
175
#morphMatchingElementNode(node: Element, reference: ReadonlyNode<Element>): void {
176
+
if (!(this.#beforeNodeMorphed?.(node, reference as ChildNode) ?? true)) return;
177
178
if (node.hasAttributes() || reference.hasAttributes()) this.#morphAttributes(node, reference);
179
···
181
this.#morphHead(node, reference as ReadonlyNode<HTMLHeadElement>);
182
} else if (node.hasChildNodes() || reference.hasChildNodes()) this.#morphChildNodes(node, reference);
183
184
+
this.#afterNodeMorphed?.(node, reference as ChildNode);
185
}
186
187
#morphOtherNode(node: ChildNode, reference: ReadonlyNode<ChildNode>): void {
188
+
if (!(this.#beforeNodeMorphed?.(node, reference as ChildNode) ?? true)) return;
189
190
if (node.nodeType === reference.nodeType && node.nodeValue !== null && reference.nodeValue !== null) {
191
// Handle text nodes, comments, and CDATA sections.
192
this.#updateProperty(node, "nodeValue", reference.nodeValue);
193
} else this.#replaceNode(node, reference.cloneNode(true));
194
195
+
this.#afterNodeMorphed?.(node, reference as ChildNode);
196
}
197
198
#morphHead(node: HTMLHeadElement, reference: ReadonlyNode<HTMLHeadElement>): void {
···
217
#morphAttributes(element: Element, reference: ReadonlyNode<Element>): void {
218
// Remove any excess attributes from the element that aren’t present in the reference.
219
for (const { name, value } of element.attributes) {
220
+
if (!reference.hasAttribute(name) && (this.#beforeAttributeUpdated?.(element, name, null) ?? true)) {
221
element.removeAttribute(name);
222
+
this.#afterAttributeUpdated?.(element, name, value);
223
}
224
}
225
226
// Copy attributes from the reference to the element, if they don’t already match.
227
for (const { name, value } of reference.attributes) {
228
const previousValue = element.getAttribute(name);
229
+
if (previousValue !== value && (this.#beforeAttributeUpdated?.(element, name, value) ?? true)) {
230
element.setAttribute(name, value);
231
+
this.#afterAttributeUpdated?.(element, name, previousValue);
232
}
233
}
234
···
240
this.#updateProperty(element, "indeterminate", reference.indeterminate);
241
if (
242
element.type !== "file" &&
243
+
!(this.#ignoreActiveValue && document.activeElement === element) &&
244
+
!(this.#preserveModifiedValues && element.name === reference.name && element.value !== element.defaultValue)
245
) {
246
this.#updateProperty(element, "value", reference.value);
247
}
···
250
} else if (
251
isTextArea(element) &&
252
isTextArea(reference) &&
253
+
!(this.#ignoreActiveValue && document.activeElement === element) &&
254
+
!(this.#preserveModifiedValues && element.name === reference.name && element.value !== element.defaultValue)
255
) {
256
this.#updateProperty(element, "value", reference.value);
257
···
290
}
291
292
#morphChildElement(child: Element, reference: ReadonlyNode<Element>, parent: Element): void {
293
+
if (!(this.#beforeNodeMorphed?.(child, reference as ChildNode) ?? true)) return;
294
295
const refIdSet = this.#idMap.get(reference);
296
···
332
this.#morphNode(nextMatchByTagName, reference);
333
} else {
334
const newNode = reference.cloneNode(true);
335
+
if (this.#beforeNodeAdded?.(newNode) ?? true) {
336
this.#insertBefore(parent, newNode, child);
337
+
this.#afterNodeAdded?.(newNode);
338
}
339
}
340
341
+
this.#afterNodeMorphed?.(child, reference as ChildNode);
342
}
343
344
#updateProperty<N extends Node, P extends keyof N>(node: N, propertyName: P, newValue: N[P]): void {
345
const previousValue = node[propertyName];
346
347
+
if (previousValue !== newValue && (this.#beforePropertyUpdated?.(node, propertyName, newValue) ?? true)) {
348
node[propertyName] = newValue;
349
+
this.#afterPropertyUpdated?.(node, propertyName, previousValue);
350
}
351
}
352
353
#replaceNode(node: ChildNode, newNode: Node): void {
354
+
if ((this.#beforeNodeRemoved?.(node) ?? true) && (this.#beforeNodeAdded?.(newNode) ?? true)) {
355
node.replaceWith(newNode);
356
+
this.#afterNodeAdded?.(newNode);
357
+
this.#afterNodeRemoved?.(node);
358
}
359
}
360
···
384
}
385
386
#appendChild(node: ParentNode, newNode: Node): void {
387
+
if (this.#beforeNodeAdded?.(newNode) ?? true) {
388
node.appendChild(newNode);
389
+
this.#afterNodeAdded?.(newNode);
390
}
391
}
392
393
#removeNode(node: ChildNode): void {
394
+
if (this.#beforeNodeRemoved?.(node) ?? true) {
395
node.remove();
396
+
this.#afterNodeRemoved?.(node);
397
}
398
}
399
}