diff --git a/core/src/components/fab-button/fab-button.tsx b/core/src/components/fab-button/fab-button.tsx index 2a7a4a6198a..2fe225aa2e9 100755 --- a/core/src/components/fab-button/fab-button.tsx +++ b/core/src/components/fab-button/fab-button.tsx @@ -1,8 +1,9 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; -import { Component, Element, Event, Host, Prop, h } from '@stencil/core'; +import { Component, Element, Event, Host, Prop, Watch, h } from '@stencil/core'; import type { AnchorInterface, ButtonInterface } from '@utils/element-interface'; -import { inheritAriaAttributes } from '@utils/helpers'; +import { inheritAriaAttributes, hasShadowDom } from '@utils/helpers'; import type { Attributes } from '@utils/helpers'; +import { printIonWarning } from '@utils/logging'; import { createColorClasses, hostContext, openURL } from '@utils/theme'; import { close } from 'ionicons/icons'; @@ -26,6 +27,8 @@ import type { RouterDirection } from '../router/utils/interface'; }) export class FabButton implements ComponentInterface, AnchorInterface, ButtonInterface { private fab: HTMLIonFabElement | null = null; + private formButtonEl: HTMLButtonElement | null = null; + private formEl: HTMLFormElement | null = null; private inheritedAttributes: Attributes = {}; @Element() el!: HTMLElement; @@ -46,6 +49,13 @@ export class FabButton implements ComponentInterface, AnchorInterface, ButtonInt * If `true`, the user cannot interact with the fab button. */ @Prop() disabled = false; + @Watch('disabled') + disabledChanged() { + const { disabled } = this; + if (this.formButtonEl) { + this.formButtonEl.disabled = disabled; + } + } /** * This attribute instructs browsers to download a URL instead of navigating to @@ -103,6 +113,11 @@ export class FabButton implements ComponentInterface, AnchorInterface, ButtonInt */ @Prop() type: 'submit' | 'reset' | 'button' = 'button'; + /** + * The HTML form element or form element id. Used to submit a form when the button is not a child of the form. + */ + @Prop() form?: string | HTMLFormElement; + /** * The size of the button. Set this to `small` in order to have a mini fab button. */ @@ -137,27 +152,91 @@ export class FabButton implements ComponentInterface, AnchorInterface, ButtonInt this.ionBlur.emit(); }; - private onClick = () => { - const { fab } = this; - if (!fab) { - return; + private onClick = (ev: Event) => { + const { el, fab } = this; + if (this.type !== 'button' && hasShadowDom(el)) { + this.submitForm(ev); + } + if (fab) { + fab.toggle(); } - - fab.toggle(); }; + /** + * Renders a hidden native button inside the associated form so that pressing + * Enter on a form field or clicking this component triggers form submission. + * The shadow DOM button does not participate in form submission natively, + * which is why this workaround is necessary. + */ + private renderHiddenButton() { + const formEl = (this.formEl = this.findForm()); + if (formEl) { + const { formButtonEl } = this; + if (formButtonEl !== null && formEl.contains(formButtonEl)) { + return; + } + const newFormButtonEl = (this.formButtonEl = document.createElement('button')); + newFormButtonEl.type = this.type; + newFormButtonEl.style.display = 'none'; + newFormButtonEl.disabled = this.disabled; + formEl.appendChild(newFormButtonEl); + } + } + + private findForm(): HTMLFormElement | null { + const { form } = this; + if (form instanceof HTMLFormElement) { + return form; + } + if (typeof form === 'string') { + const el: HTMLElement | null = document.getElementById(form); + if (el) { + if (el instanceof HTMLFormElement) { + return el; + } else { + printIonWarning( + `[ion-fab-button] - Form with selector: "#${form}" could not be found. Verify that the id is attached to a