import { type Options as MorphlexOptions, morph as morphlex } from 'morphlex'; /** * Callback to decide whether an element should be morphed. * Return `false` to prevent morphing of this element and its subtree. */ export type UpdateCallback = (fromEl: HTMLElement, toEl: HTMLElement) => boolean; /** * Returns false for elements that should not be morphed. * Elements inside a `[data-swup-morph-ignore]` container are left untouched. */ function isElementMorphable(fromEl: HTMLElement, toEl: HTMLElement): boolean { if (fromEl.closest('[data-swup-morph-ignore]') || toEl.closest('[data-swup-morph-ignore]')) { return false; } return true; } const builtInCallbacks: UpdateCallback[] = [isElementMorphable]; /** * Morph DOM nodes using morphlex. */ function morph( from: ChildNode, to: ChildNode | string, updateCallbacks: UpdateCallback[] = [], morphlexOptions: MorphlexOptions = {} ): void { const callbacks = [...builtInCallbacks, ...updateCallbacks]; morphlex(from, to, { preserveChanges: true, beforeNodeVisited: (fromNode, toNode) => { // Callback only runs on elements, not text nodes if (!(fromNode instanceof HTMLElement) || !(toNode instanceof HTMLElement)) { return true; } // Only morph if no callback cancels it return callbacks .filter((cb) => typeof cb === 'function') .every((cb) => cb(fromNode, toNode) !== false); }, ...morphlexOptions }); } export default morph;