Precise DOM morphing
morphing typescript dom
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Update callbacks to use objects

+130 -54
+42 -10
dist/morphlex.d.ts
··· 2 2 interface Options { 3 3 ignoreActiveValue?: boolean; 4 4 preserveModifiedValues?: boolean; 5 - beforeNodeMorphed?: (node: Node, referenceNode: Node) => boolean; 6 - afterNodeMorphed?: (node: Node) => void; 7 - beforeNodeAdded?: (newNode: Node, parentNode: ParentNode | null) => boolean; 8 - afterNodeAdded?: (newNode: Node) => void; 9 - beforeNodeRemoved?: (oldNode: Node) => boolean; 10 - afterNodeRemoved?: (oldNode: Node) => void; 11 - beforeAttributeUpdated?: (attributeName: string, newValue: string, element: Element) => boolean; 12 - afterAttributeUpdated?: (attributeName: string, previousValue: string | null, element: Element) => void; 13 - beforePropertyUpdated?: (propertyName: ObjectKey, newValue: unknown, node: Node) => boolean; 14 - afterPropertyUpdated?: (propertyName: ObjectKey, previousValue: unknown, node: Node) => void; 5 + beforeNodeMorphed?: ({ node, referenceNode }: { node: Node; referenceNode: Node }) => boolean; 6 + afterNodeMorphed?: ({ node }: { node: Node }) => void; 7 + beforeNodeAdded?: ({ newNode, parentNode }: { newNode: Node; parentNode: ParentNode | null }) => boolean; 8 + afterNodeAdded?: ({ newNode }: { newNode: Node }) => void; 9 + beforeNodeRemoved?: ({ oldNode }: { oldNode: Node }) => boolean; 10 + afterNodeRemoved?: ({ oldNode }: { oldNode: Node }) => void; 11 + beforeAttributeUpdated?: ({ 12 + attributeName, 13 + newValue, 14 + element, 15 + }: { 16 + attributeName: string; 17 + newValue: string; 18 + element: Element; 19 + }) => boolean; 20 + afterAttributeUpdated?: ({ 21 + attributeName, 22 + previousValue, 23 + element, 24 + }: { 25 + attributeName: string; 26 + previousValue: string | null; 27 + element: Element; 28 + }) => void; 29 + beforePropertyUpdated?: ({ 30 + propertyName, 31 + newValue, 32 + node, 33 + }: { 34 + propertyName: ObjectKey; 35 + newValue: unknown; 36 + node: Node; 37 + }) => boolean; 38 + afterPropertyUpdated?: ({ 39 + propertyName, 40 + previousValue, 41 + node, 42 + }: { 43 + propertyName: ObjectKey; 44 + previousValue: unknown; 45 + node: Node; 46 + }) => void; 15 47 } 16 48 export declare function morph(node: ChildNode, reference: ChildNode, options?: Options): void; 17 49 export {};
+22 -17
dist/morphlex.js
··· 25 25 } 26 26 // This is where we actually morph the nodes. The `morph` function (above) exists only to set up the `idMap`. 27 27 function morphNode(node, ref, context) { 28 - const writableRef = ref; 29 - if (!(context.beforeNodeMorphed?.(node, writableRef) ?? true)) return; 28 + if (!(context.beforeNodeMorphed?.({ node, referenceNode: ref }) ?? true)) return; 30 29 if (isElement(node) && isElement(ref) && node.tagName === ref.tagName) { 31 30 if (node.hasAttributes() || ref.hasAttributes()) morphAttributes(node, ref, context); 32 31 if (isHead(node) && isHead(ref)) { ··· 46 45 updateProperty(node, "nodeValue", ref.nodeValue, context); 47 46 } else replaceNode(node, ref.cloneNode(true), context); 48 47 } 49 - context.afterNodeMorphed?.(node); 48 + context.afterNodeMorphed?.({ node }); 50 49 } 51 50 function morphAttributes(element, ref, context) { 52 51 // Remove any excess attributes from the element that aren’t present in the reference. ··· 54 53 // Copy attributes from the reference to the element, if they don’t already match. 55 54 for (const { name, value } of ref.attributes) { 56 55 const previousValue = element.getAttribute(name); 57 - if (previousValue !== value && (context.beforeAttributeUpdated?.(name, value, element) ?? true)) { 56 + if ( 57 + previousValue !== value && 58 + (context.beforeAttributeUpdated?.({ attributeName: name, newValue: value, element }) ?? true) 59 + ) { 58 60 element.setAttribute(name, value); 59 - context.afterAttributeUpdated?.(name, previousValue, element); 61 + context.afterAttributeUpdated?.({ attributeName: name, previousValue, element }); 60 62 } 61 63 } 62 64 // For certain types of elements, we need to do some extra work to ensure ··· 98 100 if (child) removeNode(child, context); 99 101 } 100 102 } 101 - function updateProperty(element, propertyName, newValue, context) { 102 - const previousValue = element[propertyName]; 103 - if (previousValue !== newValue && (context.beforePropertyUpdated?.(propertyName, newValue, element) ?? true)) { 104 - element[propertyName] = newValue; 105 - context.afterPropertyUpdated?.(propertyName, previousValue, element); 103 + function updateProperty(node, propertyName, newValue, context) { 104 + const previousValue = node[propertyName]; 105 + if (previousValue !== newValue && (context.beforePropertyUpdated?.({ propertyName, newValue, node }) ?? true)) { 106 + node[propertyName] = newValue; 107 + context.afterPropertyUpdated?.({ propertyName, previousValue, node }); 106 108 } 107 109 } 108 110 function morphChildNode(child, ref, parent, context) { ··· 141 143 } else replaceNode(child, ref.cloneNode(true), context); 142 144 } 143 145 function replaceNode(node, newNode, context) { 144 - if ((context.beforeNodeRemoved?.(node) ?? true) && (context.beforeNodeAdded?.(newNode, node.parentNode) ?? true)) { 146 + if ( 147 + (context.beforeNodeRemoved?.({ oldNode: node }) ?? true) && 148 + (context.beforeNodeAdded?.({ newNode, parentNode: node.parentNode }) ?? true) 149 + ) { 145 150 node.replaceWith(newNode); 146 - context.afterNodeAdded?.(newNode); 147 - context.afterNodeRemoved?.(node); 151 + context.afterNodeAdded?.({ newNode }); 152 + context.afterNodeRemoved?.({ oldNode: node }); 148 153 } 149 154 } 150 155 function appendChild(node, newNode, context) { 151 - if (context.beforeNodeAdded?.(newNode, node) ?? true) { 156 + if (context.beforeNodeAdded?.({ newNode, parentNode: node }) ?? true) { 152 157 node.appendChild(newNode); 153 - context.afterNodeAdded?.(newNode); 158 + context.afterNodeAdded?.({ newNode }); 154 159 } 155 160 } 156 161 function removeNode(node, context) { 157 - if (context.beforeNodeRemoved?.(node) ?? true) { 162 + if (context.beforeNodeRemoved?.({ oldNode: node }) ?? true) { 158 163 node.remove(); 159 - context.afterNodeRemoved?.(node); 164 + context.afterNodeRemoved?.({ oldNode: node }); 160 165 } 161 166 } 162 167 function isText(node) {
+66 -27
src/morphlex.ts
··· 32 32 ignoreActiveValue?: boolean; 33 33 preserveModifiedValues?: boolean; 34 34 35 - beforeNodeMorphed?: (node: Node, referenceNode: Node) => boolean; 36 - afterNodeMorphed?: (node: Node) => void; 35 + beforeNodeMorphed?: ({ node, referenceNode }: { node: Node; referenceNode: Node }) => boolean; 36 + afterNodeMorphed?: ({ node }: { node: Node }) => void; 37 37 38 - beforeNodeAdded?: (newNode: Node, parentNode: ParentNode | null) => boolean; 39 - afterNodeAdded?: (newNode: Node) => void; 38 + beforeNodeAdded?: ({ newNode, parentNode }: { newNode: Node; parentNode: ParentNode | null }) => boolean; 39 + afterNodeAdded?: ({ newNode }: { newNode: Node }) => void; 40 40 41 - beforeNodeRemoved?: (oldNode: Node) => boolean; 42 - afterNodeRemoved?: (oldNode: Node) => void; 41 + beforeNodeRemoved?: ({ oldNode }: { oldNode: Node }) => boolean; 42 + afterNodeRemoved?: ({ oldNode }: { oldNode: Node }) => void; 43 + 44 + beforeAttributeUpdated?: ({ 45 + attributeName, 46 + newValue, 47 + element, 48 + }: { 49 + attributeName: string; 50 + newValue: string; 51 + element: Element; 52 + }) => boolean; 53 + 54 + afterAttributeUpdated?: ({ 55 + attributeName, 56 + previousValue, 57 + element, 58 + }: { 59 + attributeName: string; 60 + previousValue: string | null; 61 + element: Element; 62 + }) => void; 43 63 44 - beforeAttributeUpdated?: (attributeName: string, newValue: string, element: Element) => boolean; 45 - afterAttributeUpdated?: (attributeName: string, previousValue: string | null, element: Element) => void; 64 + beforePropertyUpdated?: ({ 65 + propertyName, 66 + newValue, 67 + node, 68 + }: { 69 + propertyName: ObjectKey; 70 + newValue: unknown; 71 + node: Node; 72 + }) => boolean; 46 73 47 - beforePropertyUpdated?: (propertyName: ObjectKey, newValue: unknown, node: Node) => boolean; 48 - afterPropertyUpdated?: (propertyName: ObjectKey, previousValue: unknown, node: Node) => void; 74 + afterPropertyUpdated?: ({ 75 + propertyName, 76 + previousValue, 77 + node, 78 + }: { 79 + propertyName: ObjectKey; 80 + previousValue: unknown; 81 + node: Node; 82 + }) => void; 49 83 } 50 84 51 85 type Context = Options & { idMap: IdMap }; ··· 85 119 86 120 // This is where we actually morph the nodes. The `morph` function (above) exists only to set up the `idMap`. 87 121 function morphNode(node: ChildNode, ref: ReadonlyNode<ChildNode>, context: Context): void { 88 - const writableRef = ref as ChildNode; 89 - if (!(context.beforeNodeMorphed?.(node, writableRef) ?? true)) return; 122 + if (!(context.beforeNodeMorphed?.({ node, referenceNode: ref as ChildNode }) ?? true)) return; 90 123 91 124 if (isElement(node) && isElement(ref) && node.tagName === ref.tagName) { 92 125 if (node.hasAttributes() || ref.hasAttributes()) morphAttributes(node, ref, context); ··· 108 141 } else replaceNode(node, ref.cloneNode(true), context); 109 142 } 110 143 111 - context.afterNodeMorphed?.(node); 144 + context.afterNodeMorphed?.({ node }); 112 145 } 113 146 114 147 function morphAttributes(element: Element, ref: ReadonlyNode<Element>, context: Context): void { ··· 118 151 // Copy attributes from the reference to the element, if they don’t already match. 119 152 for (const { name, value } of ref.attributes) { 120 153 const previousValue = element.getAttribute(name); 121 - if (previousValue !== value && (context.beforeAttributeUpdated?.(name, value, element) ?? true)) { 154 + if ( 155 + previousValue !== value && 156 + (context.beforeAttributeUpdated?.({ attributeName: name, newValue: value, element }) ?? true) 157 + ) { 122 158 element.setAttribute(name, value); 123 - context.afterAttributeUpdated?.(name, previousValue, element); 159 + context.afterAttributeUpdated?.({ attributeName: name, previousValue, element }); 124 160 } 125 161 } 126 162 ··· 169 205 } 170 206 } 171 207 172 - function updateProperty<N extends Node, P extends keyof N>(element: N, propertyName: P, newValue: N[P], context: Context): void { 173 - const previousValue = element[propertyName]; 174 - if (previousValue !== newValue && (context.beforePropertyUpdated?.(propertyName, newValue, element) ?? true)) { 175 - element[propertyName] = newValue; 176 - context.afterPropertyUpdated?.(propertyName, previousValue, element); 208 + function updateProperty<N extends Node, P extends keyof N>(node: N, propertyName: P, newValue: N[P], context: Context): void { 209 + const previousValue = node[propertyName]; 210 + if (previousValue !== newValue && (context.beforePropertyUpdated?.({ propertyName, newValue, node }) ?? true)) { 211 + node[propertyName] = newValue; 212 + context.afterPropertyUpdated?.({ propertyName, previousValue, node }); 177 213 } 178 214 } 179 215 ··· 221 257 } 222 258 223 259 function replaceNode(node: ChildNode, newNode: Node, context: Context): void { 224 - if ((context.beforeNodeRemoved?.(node) ?? true) && (context.beforeNodeAdded?.(newNode, node.parentNode) ?? true)) { 260 + if ( 261 + (context.beforeNodeRemoved?.({ oldNode: node }) ?? true) && 262 + (context.beforeNodeAdded?.({ newNode, parentNode: node.parentNode }) ?? true) 263 + ) { 225 264 node.replaceWith(newNode); 226 - context.afterNodeAdded?.(newNode); 227 - context.afterNodeRemoved?.(node); 265 + context.afterNodeAdded?.({ newNode }); 266 + context.afterNodeRemoved?.({ oldNode: node }); 228 267 } 229 268 } 230 269 231 270 function appendChild(node: ParentNode, newNode: Node, context: Context): void { 232 - if (context.beforeNodeAdded?.(newNode, node) ?? true) { 271 + if (context.beforeNodeAdded?.({ newNode, parentNode: node }) ?? true) { 233 272 node.appendChild(newNode); 234 - context.afterNodeAdded?.(newNode); 273 + context.afterNodeAdded?.({ newNode }); 235 274 } 236 275 } 237 276 238 277 function removeNode(node: ChildNode, context: Context): void { 239 - if (context.beforeNodeRemoved?.(node) ?? true) { 278 + if (context.beforeNodeRemoved?.({ oldNode: node }) ?? true) { 240 279 node.remove(); 241 - context.afterNodeRemoved?.(node); 280 + context.afterNodeRemoved?.({ oldNode: node }); 242 281 } 243 282 } 244 283