From 7e042dd13119b96531aeec34ffe537538c09cae2 Mon Sep 17 00:00:00 2001 From: Iulian Meghea Date: Wed, 1 Jul 2026 18:40:38 +0000 Subject: [PATCH 1/3] fix: useProperty init sets default even when preventDefault is called When a parent uses lift() to listen to property changes without passing the property value to the child, useProperty's initialization event gets preventDefault'd, leaving the host property undefined. Add isInit flag to updater() so initialization bypasses the preventDefault veto while subsequent updates retain the full lift/preventDefault two-way binding semantics. --- src/use-property.ts | 6 +++--- test/use-property.test.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/use-property.ts b/src/use-property.ts index f936259..5952e65 100644 --- a/src/use-property.ts +++ b/src/use-property.ts @@ -52,7 +52,7 @@ export const useProperty = hook( if (initialValue == null) return; - this.updater(initialValue); + this.updater(initialValue, true); } update(ignored: string, ignored2: T): StateTuple { @@ -82,10 +82,10 @@ export const useProperty = hook( return ev; } - updater(valueOrUpdater: NewState): void { + updater(valueOrUpdater: NewState, isInit = false): void { const [previousValue, value, updater] = this.resolve(valueOrUpdater); const ev = this.notify(value, updater); - if (ev.defaultPrevented) return; + if (!isInit && ev.defaultPrevented) return; if (Object.is(previousValue, value)) return; this.state.host[this.property] = value; } diff --git a/test/use-property.test.ts b/test/use-property.test.ts index e16a04b..ba69620 100644 --- a/test/use-property.test.ts +++ b/test/use-property.test.ts @@ -391,6 +391,41 @@ describe("useProperty", () => { expect(span?.textContent).to.equal("a,b,c"); }); + it("initializes default value even when lift prevents default", async () => { + let parentSetter; + + function Parent() { + let [items, setItems] = useState(undefined); + parentSetter = setItems; + return html``; + } + + function Child() { + let [items, setItems] = useProperty("items", () => []); + return html`${(items ?? []).length}`; + } + + customElements.define( + "use-property-lift-no-prop-parent", + component(Parent) + ); + customElements.define( + "use-property-lift-no-prop-child", + component(Child) + ); + + const el = await fixture( + html`` + ); + const child = el.shadowRoot?.firstElementChild as HTMLElement; + const span = child?.shadowRoot?.firstElementChild; + + expect(child?.items).to.deep.equal([]); + expect(span?.textContent).to.equal("0"); + }); + it("lift with direct value still works", async () => { let childSetter; From 2cefed10d05fde50150e47036b69d352efcbbd40 Mon Sep 17 00:00:00 2001 From: Meghea Iulian Date: Thu, 2 Jul 2026 08:46:38 +0300 Subject: [PATCH 2/3] Update test/use-property.test.ts Co-authored-by: Cristian Necula --- test/use-property.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/use-property.test.ts b/test/use-property.test.ts index ba69620..5132666 100644 --- a/test/use-property.test.ts +++ b/test/use-property.test.ts @@ -404,7 +404,7 @@ describe("useProperty", () => { function Child() { let [items, setItems] = useProperty("items", () => []); - return html`${(items ?? []).length}`; + return html`${items.length}`; } customElements.define( From 9ee4a7568726b94421a87de3ef0b9fb8c2dda2b6 Mon Sep 17 00:00:00 2001 From: Iulian Meghea Date: Thu, 2 Jul 2026 05:50:51 +0000 Subject: [PATCH 3/3] Add changeset for useProperty init fix --- .changeset/fe-887-use-property-init.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .changeset/fe-887-use-property-init.md diff --git a/.changeset/fe-887-use-property-init.md b/.changeset/fe-887-use-property-init.md new file mode 100644 index 0000000..3b31224 --- /dev/null +++ b/.changeset/fe-887-use-property-init.md @@ -0,0 +1,14 @@ +--- +"@pionjs/pion": patch +--- + +Fix: useProperty initialization sets default value even when preventDefault is called + +When a parent component uses `lift()` to listen to a property change event without +passing the property value to the child, `useProperty`'s initialization event was +`preventDefault`'d by `lift`, leaving the host property `undefined`. This caused crashes +in components like `cosmoz-omnitable` where `selectedItems` was never initialized. + +The `updater` method now accepts an `isInit` flag that bypasses the `preventDefault` veto +during initialization, ensuring the host always receives its default value. Subsequent +updates retain the full `lift`/`preventDefault` two-way binding semantics. \ No newline at end of file