diff --git a/core/src/components/button/button.tsx b/core/src/components/button/button.tsx index 0e4741f4acd..f8258af2239 100644 --- a/core/src/components/button/button.tsx +++ b/core/src/components/button/button.tsx @@ -195,9 +195,13 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf /** * If the form already has a rendered form button - * then do not append a new one again. + * then do not append a new one again. Sync the + * disabled state and type in it changes after button + * creation (e.g., runtime property updates). */ if (formButtonEl !== null && formEl.contains(formButtonEl)) { + // formButtonEl.disabled = this.disabled; + // formButtonEl.type = this.type; return; } diff --git a/core/src/components/button/test/form-reference/button.e2e.ts b/core/src/components/button/test/form-reference/button.e2e.ts index 5710f52da67..c803dd2c226 100644 --- a/core/src/components/button/test/form-reference/button.e2e.ts +++ b/core/src/components/button/test/form-reference/button.e2e.ts @@ -153,6 +153,41 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) => expect(submitEvent).toHaveReceivedEvent(); }); + + test('should submit the form when disabled state changes asynchronously', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30968', + }); + + await page.setContent( + ` +
+ `, + config + ); + + const submitEvent = await page.spyOnEvent('submit'); + const button = page.locator('ion-button'); + + // Simulate async disabled state change — e.g. async validator resolving + await button.evaluate((el: HTMLIonButtonElement) => { + el.disabled = true; + setTimeout(() => { + el.disabled = false; + }, 0); + }); + + // Wait for the async change to settle + await page.waitForTimeout(100); + + await page.click('ion-button'); + + expect(submitEvent).toHaveReceivedEvent(); + }); }); test.describe(title('should throw a warning if the form cannot be found'), () => {