···46464747```javascript
4848morph(currentNode, newNode, {
4949- preserveActiveElement: false,
5049 preserveChanges: true,
5150 beforeNodeAdded: (parent, node, insertionPoint) => {
5251 console.log("Adding node:", node)
···5857### Available Options
59586059- **`preserveChanges`**: When `true`, preserves modified form inputs during morphing. This prevents user-entered data from being overwritten. Default: `false`
6161-6262-- **`preserveActiveElement`**: When `true`, preserves the current `document.activeElement` during morphing. Direct updates and replacement of the focused element are deferred until it blurs. Default: `false`
63606461- **`beforeNodeVisited`**: Called before a node is visited during morphing. Return `false` to skip morphing this node.
6562
+4-84
src/morphlex.ts
···3131type IdArrayMap = WeakMap<Node, Array<string>>
3232type CandidateIdBucket = number | Array<number>
33333434-const queuedActiveElementTargets: WeakMap<Element, ChildNode> = new WeakMap()
3535-const queuedActiveElementOptions: WeakMap<Element, Options> = new WeakMap()
3636-const queuedActiveElementListeners: WeakSet<Element> = new WeakSet()
3737-3834/**
3935 * Configuration options for morphing operations.
4036 */
···4541 * @default false
4642 */
4743 preserveChanges?: boolean
4848-4949- /**
5050- * When `true`, preserves the active element during morphing.
5151- * This defers direct updates/replacement of the focused element until blur.
5252- * @default false
5353- */
5454- preserveActiveElement?: boolean
55445645 /**
5746 * Called before a node is visited during morphing.
···161150 if (typeof to === "string") to = parseFragment(to).childNodes
162151163152 if (isParentNode(from)) flagDirtyInputs(from)
164164- new Morph(options, getActiveElement(from, options)).morph(from, to)
153153+ new Morph(options).morph(from, to)
165154}
166155167156/**
···192181 (from as Element).localName === (to as Element).localName
193182 ) {
194183 if (isParentNode(from)) flagDirtyInputs(from)
195195- new Morph(options, getActiveElement(from, options)).visitChildNodes(from as Element, to as Element)
184184+ new Morph(options).visitChildNodes(from as Element, to as Element)
196185 } else {
197186 throw new Error("[Morphlex] You can only do an inner morph with matching elements.")
198187 }
199188}
200189201201-function getActiveElement(from: ChildNode, options: Options): Element | null {
202202- if (!options.preserveActiveElement) return null
203203-204204- const activeElement = document.activeElement
205205- if (!(activeElement instanceof Element)) return null
206206- if (from === activeElement) return activeElement
207207-208208- if (from.nodeType !== ELEMENT_NODE_TYPE) return null
209209- return (from as Element).contains(activeElement) ? activeElement : null
210210-}
211211-212190function flagDirtyInputs(node: ParentNode): void {
213191 if (node.nodeType === ELEMENT_NODE_TYPE) {
214192 const element = node as Element
···275253class Morph {
276254 readonly #idArrayMap: IdArrayMap = new WeakMap()
277255 readonly #idSetMap: IdSetMap = new WeakMap()
278278- readonly #activeElement: Element | null
279256 readonly #options: Options
280280- readonly #skipValuePropertyUpdateFor: Element | null
281257282282- constructor(options: Options = {}, activeElement: Element | null = null, skipValuePropertyUpdateFor: Element | null = null) {
258258+ constructor(options: Options = {}) {
283259 this.#options = options
284284- this.#activeElement = activeElement
285285- this.#skipValuePropertyUpdateFor = skipValuePropertyUpdateFor
286286- }
287287-288288- #isPinnedActiveElement(node: Node): boolean {
289289- return !!this.#options.preserveActiveElement && node === this.#activeElement
290260 }
291261292262 morph(from: ChildNode, to: ChildNode | NodeListOf<ChildNode>): void {
···331301 if (from === to) return
332302 if (from.isEqualNode(to)) return
333303334334- if (this.#isPinnedActiveElement(from)) {
335335- queueActiveElementMorph(from as Element, to, this.#options)
336336- return
337337- }
338338-339304 if (from.nodeType === ELEMENT_NODE_TYPE && to.nodeType === ELEMENT_NODE_TYPE) {
340305 if ((from as Element).localName === (to as Element).localName) {
341306 this.#morphMatchingElements(from as Element, to as Element)
···349314350315 #morphMatchingElements(from: Element, to: Element): void {
351316 if (!(this.#options.beforeNodeVisited?.(from, to) ?? true)) return
352352- if (this.#isPinnedActiveElement(from)) {
353353- queueActiveElementMorph(from, to, this.#options)
354354- this.#options.afterNodeVisited?.(from, to)
355355- return
356356- }
357317358318 if (from.hasAttributes() || to.hasAttributes()) {
359319 this.#visitAttributes(from, to)
···399359 for (const { name, value } of to.attributes) {
400360 if (name === "value") {
401361 if (isInputElement(from) && from.value !== value) {
402402- if (from !== this.#skipValuePropertyUpdateFor && !this.#options.preserveChanges) {
362362+ if (!this.#options.preserveChanges) {
403363 from.value = value
404364 }
405365 }
···813773 }
814774815775 #replaceNode(node: ChildNode, newNode: ChildNode): void {
816816- if (this.#isPinnedActiveElement(node)) return
817817-818776 const parent = node.parentNode || document
819777 const insertionPoint = node
820778 // Check if both removal and addition are allowed before starting the replacement
···830788 }
831789832790 #removeNode(node: ChildNode): void {
833833- if (this.#isPinnedActiveElement(node)) return
834834-835791 if (this.#options.beforeNodeRemoved?.(node) ?? true) {
836792 node.remove()
837793 this.#options.afterNodeRemoved?.(node)
···893849 }
894850 })
895851 }
896896-}
897897-898898-function queueActiveElementMorph(from: Element, to: ChildNode, options: Options): void {
899899- queuedActiveElementTargets.set(from, to.cloneNode(true) as ChildNode)
900900- queuedActiveElementOptions.set(from, options)
901901-902902- if (queuedActiveElementListeners.has(from)) return
903903- queuedActiveElementListeners.add(from)
904904-905905- from.addEventListener(
906906- "blur",
907907- () => {
908908- queuedActiveElementListeners.delete(from)
909909- flushQueuedActiveElementMorph(from)
910910- },
911911- { once: true },
912912- )
913913-}
914914-915915-function flushQueuedActiveElementMorph(from: Element): void {
916916- const to = queuedActiveElementTargets.get(from)
917917- if (!to) return
918918-919919- const options = queuedActiveElementOptions.get(from) ?? {}
920920-921921- queuedActiveElementTargets.delete(from)
922922- queuedActiveElementOptions.delete(from)
923923-924924- if (!from.isConnected) return
925925-926926- const flushOptions: Options = {
927927- ...options,
928928- preserveActiveElement: false,
929929- }
930930-931931- new Morph(flushOptions, null, from).morph(from as ChildNode, to)
932852}
933853934854function forEachDescendantElementWithId(node: ParentNode, callback: (element: Element) => void): void {