diff --git a/packages/runtime-dom/__tests__/nodeOps.spec.ts b/packages/runtime-dom/__tests__/nodeOps.spec.ts new file mode 100644 index 00000000000..3f72379d624 --- /dev/null +++ b/packages/runtime-dom/__tests__/nodeOps.spec.ts @@ -0,0 +1,12 @@ +import { nodeOps } from '../src/nodeOps' + +describe('nodeOps', () => { + test('the _value property should be cloned', () => { + const el = nodeOps.createElement('input') as HTMLDivElement & { + _value: any + } + el._value = 1 + const cloned = nodeOps.cloneNode!(el) as HTMLDivElement & { _value: any } + expect(cloned._value).toBe(1) + }) +}) diff --git a/packages/runtime-dom/src/nodeOps.ts b/packages/runtime-dom/src/nodeOps.ts index 72aa25a3930..06e59221113 100644 --- a/packages/runtime-dom/src/nodeOps.ts +++ b/packages/runtime-dom/src/nodeOps.ts @@ -47,7 +47,20 @@ export const nodeOps: Omit, 'patchProp'> = { }, cloneNode(el) { - return el.cloneNode(true) + const cloned = el.cloneNode(true) + // #3072 + // - in `patchDOMProp`, we store the actual value in the `el._value` property. + // - normally, elements using `:value` bindings will not be hoisted, but if + // the bound value is a constant, e.g. `:value="true"` - they do get + // hoisted. + // - in production, hoisted nodes are cloned when subsequent inserts, but + // cloneNode() does not copy the custom property we attached. + // - This may need to account for other custom DOM properties we attach to + // elements in addition to `_value` in the future. + if (`_value` in el) { + ;(cloned as any)._value = (el as any)._value + } + return cloned }, // __UNSAFE__