Precise DOM morphing
morphing typescript dom

Move options to `this`

+103 -63
+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;
··· 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; 22 constructor(options = {}) { 23 - this.#options = options; 24 this.#idMap = new WeakMap(); 25 this.#sensivityMap = new WeakMap(); 26 - Object.freeze(this.#options); 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 { 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 = {}) { 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 75 constructor(options: Options = {}) { 76 - this.#options = options; 77 this.#idMap = new WeakMap(); 78 this.#sensivityMap = new WeakMap(); 79 80 - Object.freeze(this.#options); 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; 34 beforeNodeMorphed?: (node: Node, referenceNode: Node) => boolean; 35 afterNodeMorphed?: (node: Node, referenceNode: Node) => void; 36 beforeNodeAdded?: (node: Node) => boolean; 37 afterNodeAdded?: (node: Node) => void; 38 beforeNodeRemoved?: (node: Node) => boolean; 39 afterNodeRemoved?: (node: Node) => void; 40 beforeAttributeUpdated?: (element: Element, attributeName: string, newValue: string | null) => boolean; 41 afterAttributeUpdated?: (element: Element, attributeName: string, previousValue: string | null) => void; 42 beforePropertyUpdated?: (node: Node, propertyName: PropertyKey, newValue: unknown) => boolean; 43 afterPropertyUpdated?: (node: Node, propertyName: PropertyKey, previousValue: unknown) => void; 44 } ··· 63 } 64 65 class Morph { 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 = {}) { 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 }