* Morph oldNode to match content. * @param {Element} oldNode - The existing DOM element to morph * @param {string|Element|DocumentFragment} content - The new content * @param {MorphCallbacks} [callbacks] - Optional lifecycle callbacks
(oldNode, content, callbacks = {})
| 21 | * @param {MorphCallbacks} [callbacks] - Optional lifecycle callbacks |
| 22 | */ |
| 23 | morph(oldNode, content, callbacks = {}) { |
| 24 | var fragment; |
| 25 | if (typeof content === "string") { |
| 26 | var temp = document.createElement("template"); |
| 27 | temp.innerHTML = content; |
| 28 | fragment = temp.content; |
| 29 | } else if (content instanceof DocumentFragment) { |
| 30 | fragment = content; |
| 31 | } else if (content instanceof Element) { |
| 32 | fragment = document.createDocumentFragment(); |
| 33 | fragment.append(content.cloneNode(true)); |
| 34 | } else { |
| 35 | throw new Error("morph requires an HTML string, element, or document fragment"); |
| 36 | } |
| 37 | |
| 38 | // If the fragment has a single root element matching the target tag, |
| 39 | // treat as outer morph: sync attributes, then morph children |
| 40 | var newRoot = fragment.firstElementChild; |
| 41 | if (newRoot && !newRoot.nextElementSibling && newRoot.tagName === oldNode.tagName) { |
| 42 | _copyAttributes(oldNode, newRoot); |
| 43 | fragment = newRoot; |
| 44 | } |
| 45 | |
| 46 | var { persistentIds, idMap } = _createIdMaps(oldNode, fragment); |
| 47 | var pantry = document.createElement("div"); |
| 48 | pantry.hidden = true; |
| 49 | (document.body || oldNode.parentElement).after(pantry); |
| 50 | var ctx = { target: oldNode, idMap, persistentIds, pantry, futureMatches: new WeakSet(), callbacks }; |
| 51 | |
| 52 | _morphChildren(ctx, oldNode, fragment); |
| 53 | |
| 54 | // clean up orphaned nodes in pantry |
| 55 | callbacks.beforeNodeRemoved?.(pantry); |
| 56 | pantry.remove(); |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | function _morphChildren(ctx, oldParent, newParent, insertionPoint = null, endPoint = null) { |
no test coverage detected