{"version":3,"file":"index.cjs","sources":["../src/morph.ts","../src/index.ts"],"sourcesContent":["import { type Options as MorphlexOptions, morph as morphlex } from 'morphlex';\n\n/**\n * Callback to decide whether an element should be morphed.\n * Return `false` to prevent morphing of this element and its subtree.\n */\nexport type UpdateCallback = (fromEl: HTMLElement, toEl: HTMLElement) => boolean;\n\n/**\n * Returns false for elements that should not be morphed.\n * Elements inside a `[data-swup-morph-ignore]` container are left untouched.\n */\nfunction isElementMorphable(fromEl: HTMLElement, toEl: HTMLElement): boolean {\n\tif (fromEl.closest('[data-swup-morph-ignore]') || toEl.closest('[data-swup-morph-ignore]')) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nconst builtInCallbacks: UpdateCallback[] = [isElementMorphable];\n\n/**\n * Morph DOM nodes using morphlex.\n */\nfunction morph(\n\tfrom: ChildNode,\n\tto: ChildNode | string,\n\tupdateCallbacks: UpdateCallback[] = [],\n\tmorphlexOptions: MorphlexOptions = {}\n): void {\n\tconst callbacks = [...builtInCallbacks, ...updateCallbacks];\n\n\tmorphlex(from, to, {\n\t\tpreserveChanges: true,\n\t\tbeforeNodeVisited: (fromNode, toNode) => {\n\t\t\t// Callback only runs on elements, not text nodes\n\t\t\tif (!(fromNode instanceof HTMLElement) || !(toNode instanceof HTMLElement)) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Only morph if no callback cancels it\n\t\t\treturn callbacks\n\t\t\t\t.filter((cb) => typeof cb === 'function')\n\t\t\t\t.every((cb) => cb(fromNode, toNode) !== false);\n\t\t},\n\t\t...morphlexOptions\n\t});\n}\n\nexport default morph;\n","import Plugin from '@swup/plugin';\nimport type { Options as MorphlexOptions } from 'morphlex';\nimport { type Handler, queryAll } from 'swup';\n\nimport morph, { type UpdateCallback } from './morph.js';\n\ntype Options = {\n\tcontainers: string[];\n\tupdateCallbacks: UpdateCallback[];\n\tmorphlexOptions: MorphlexOptions;\n};\n\nexport default class SwupMorphPlugin extends Plugin {\n\tname = 'SwupMorphPlugin';\n\n\trequires = { swup: '>=4.6' };\n\n\tdefaults: Options = {\n\t\tcontainers: [],\n\t\tupdateCallbacks: [],\n\t\tmorphlexOptions: {}\n\t};\n\n\toptions: Options;\n\n\tconstructor(options: Partial<Options> = {}) {\n\t\tsuper();\n\t\tthis.options = { ...this.defaults, ...options };\n\t}\n\n\tmount() {\n\t\tthis.before('content:replace', this.validateContainers, { priority: 1 });\n\t\tthis.on('content:replace', this.morphContainers);\n\t}\n\n\tprotected validateContainers: Handler<'content:replace'> = (visit) => {\n\t\t// Filter out containers that are already managed by the morph plugin\n\t\tvisit.containers = visit.containers.filter(\n\t\t\t(selector) => !this.options.containers.includes(selector)\n\t\t);\n\t};\n\n\tprotected morphContainers: Handler<'content:replace'> = (visit) => {\n\t\tconst containers = this.getContainers(document, visit.to.document!);\n\t\tconst { updateCallbacks = [], morphlexOptions = {} } = this.options;\n\n\t\tfor (const { selector, outgoing, incoming } of containers) {\n\t\t\tif (outgoing && incoming) {\n\t\t\t\tmorph(outgoing, incoming, updateCallbacks, morphlexOptions);\n\t\t\t} else if (this.options.containers.includes(selector)) {\n\t\t\t\tconsole.warn(`SwupMorphPlugin: No container found for selector: ${selector}`);\n\t\t\t}\n\t\t}\n\t};\n\n\tprotected getContainers(oldDoc: Document, newDoc: Document) {\n\t\tconst selectors = this.getContainerSelectors();\n\t\treturn selectors.map((selector) => {\n\t\t\tconst outgoing = oldDoc.querySelector(selector);\n\t\t\tconst incoming = newDoc.querySelector(selector);\n\t\t\treturn { selector, outgoing, incoming };\n\t\t});\n\t}\n\n\tprotected getContainerSelectors() {\n\t\tconst explit = this.options.containers;\n\t\tconst implicit = queryAll('[data-swup-morph]:not([data-swup-morph=\"\"])').map(\n\t\t\t(el) => `[data-swup-morph='${el.dataset.swupMorph}']`\n\t\t);\n\t\treturn this.uniq([...explit, ...implicit]);\n\t}\n\n\tprotected uniq<T>(array: T[]): T[] {\n\t\treturn [...new Set(array)];\n\t}\n}\n"],"names":["builtInCallbacks","fromEl","toEl","closest","morph","from","to","updateCallbacks","morphlexOptions","callbacks","morphlex","preserveChanges","beforeNodeVisited","fromNode","toNode","HTMLElement","filter","cb","every","Plugin","constructor","options","super","this","name","requires","swup","defaults","containers","validateContainers","visit","selector","includes","morphContainers","getContainers","document","outgoing","incoming","console","warn","mount","before","priority","on","oldDoc","newDoc","getContainerSelectors","map","querySelector","explit","implicit","queryAll","el","dataset","swupMorph","uniq","array","Set"],"mappings":"sKAmBA,MAAMA,EAAqC,CAP3C,SAA4BC,EAAqBC,GAChD,OAAID,EAAOE,QAAQ,8BAA+BD,EAAKC,QAAQ,2BAIhE,GAOA,SAASC,EACRC,EACAC,EACAC,EAAoC,GACpCC,EAAmC,CAAE,GAErC,MAAMC,EAAY,IAAIT,KAAqBO,GAE3CG,EAAQN,MAACC,EAAMC,EAAI,CAClBK,iBAAiB,EACjBC,kBAAmBA,CAACC,EAAUC,MAEvBD,aAAoBE,aAAkBD,aAAkBC,cAKvDN,EACLO,OAAQC,GAAqB,mBAAPA,GACtBC,MAAOD,IAAgC,IAAzBA,EAAGJ,EAAUC,OAE3BN,GAEL,gBCnCqB,cAAwBW,EAAAA,QAa5CC,WAAAA,CAAYC,EAA4B,CAAA,GACvCC,QAAQC,KAbTC,KAAO,kBAEPC,KAAAA,SAAW,CAAEC,KAAM,SAEnBC,KAAAA,SAAoB,CACnBC,WAAY,GACZrB,gBAAiB,GACjBC,gBAAiB,SAGlBa,aAAO,EAAAE,KAYGM,mBAAkDC,IAE3DA,EAAMF,WAAaE,EAAMF,WAAWZ,OAClCe,IAAcR,KAAKF,QAAQO,WAAWI,SAASD,KAEjDR,KAESU,gBAA+CH,IACxD,MAAMF,EAAaL,KAAKW,cAAcC,SAAUL,EAAMxB,GAAG6B,WACnD5B,gBAAEA,EAAkB,GAAEC,gBAAEA,EAAkB,CAAA,GAAOe,KAAKF,QAE5D,IAAK,MAAMU,SAAEA,EAAQK,SAAEA,EAAQC,SAAEA,KAAcT,EAC1CQ,GAAYC,EACfjC,EAAMgC,EAAUC,EAAU9B,EAAiBC,GACjCe,KAAKF,QAAQO,WAAWI,SAASD,IAC3CO,QAAQC,KAAK,qDAAqDR,MAvBpER,KAAKF,QAAU,IAAKE,KAAKI,YAAaN,EACvC,CAEAmB,KAAAA,GACCjB,KAAKkB,OAAO,kBAAmBlB,KAAKM,mBAAoB,CAAEa,SAAU,IACpEnB,KAAKoB,GAAG,kBAAmBpB,KAAKU,gBACjC,CAsBUC,aAAAA,CAAcU,EAAkBC,GAEzC,OADkBtB,KAAKuB,wBACNC,IAAKhB,IAGd,CAAEA,WAAUK,SAFFQ,EAAOI,cAAcjB,GAETM,SADZQ,EAAOG,cAAcjB,KAGxC,CAEUe,qBAAAA,GACT,MAAMG,EAAS1B,KAAKF,QAAQO,WACtBsB,EAAWC,EAAQA,SAAC,+CAA+CJ,IACvEK,GAAO,qBAAqBA,EAAGC,QAAQC,eAEzC,OAAW/B,KAACgC,KAAK,IAAIN,KAAWC,GACjC,CAEUK,IAAAA,CAAQC,GACjB,MAAO,IAAI,IAAIC,IAAID,GACpB"}