From cacbf67b864282921da8767df652a4fa9a60dff8 Mon Sep 17 00:00:00 2001 From: DavidHarvey Date: Fri, 29 May 2026 13:41:28 -0400 Subject: [PATCH] fix: detach reused components from their old collection in `resetFromString` --- .../src/dom_components/model/Components.ts | 2 ++ .../specs/dom_components/model/Component.ts | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/packages/core/src/dom_components/model/Components.ts b/packages/core/src/dom_components/model/Components.ts index ce45676f5..ff04ff28b 100644 --- a/packages/core/src/dom_components/model/Components.ts +++ b/packages/core/src/dom_components/model/Components.ts @@ -70,6 +70,8 @@ const getComponentsFromDefs = ( result = all[id] as any; const { onAttributes, onStyle } = updateOptions; const component = result as unknown as Component; + // Detach the reused model from its old container so re-inserting it updates `collection`/`parent` + component.collection?.remove(component, { silent: true } as any); const htmlImportOpts = { ...opts, parsedImportSource: 'html' as const }; tagName && component.set({ tagName }, { ...htmlImportOpts, silent: true }); diff --git a/packages/core/test/specs/dom_components/model/Component.ts b/packages/core/test/specs/dom_components/model/Component.ts index 5f984efac..a7bd37978 100644 --- a/packages/core/test/specs/dom_components/model/Component.ts +++ b/packages/core/test/specs/dom_components/model/Component.ts @@ -582,6 +582,31 @@ describe('Component', () => { ); }); + test('resetFromString re-parents a reused component when its wrapper is removed', () => { + const wrapper = dcomp.getWrapper()!; + wrapper.components().resetFromString(`

Insert your text here

`); + + const div = wrapper.components().at(0); + const paragraph = div.components().at(0); + expect(div.getId()).toBe('i5y6'); + expect(paragraph.getId()).toBe('i8sd'); + expect(paragraph.parent()).toBe(div); + + // Remove the wrapping div, promoting the reused

to a direct child of the wrapper. + wrapper.components().resetFromString(`

Insert your text here

`); + + const allById = dcomp.allById(); + // The same

model instance is reused, not rebuilt. + expect(wrapper.components().at(0)).toBe(paragraph); + expect(allById['i8sd']).toBe(paragraph); + // The removed div is gone from the global registry. + expect(allById['i5y6']).toBeUndefined(); + // The reused

reports the wrapper as its parent, not the removed div. + expect(paragraph.parent()).toBe(wrapper); + expect(paragraph.collection).toBe(wrapper.components()); + expect(em.getHtml()).toBe('

Insert your text here

'); + }); + test('Ability to stop/change propagation chain', () => { obj.append({ removable: false,