From 642de684e857c428583936a3cf8a2d3ddf08b9c4 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Mon, 28 Feb 2022 09:59:32 -0500 Subject: [PATCH] fixes #699 --- cspell.json | 4 ++++ docs/resources/changelog.md | 1 + src/components/button/button.ts | 35 +++++++++++++++++++++++++++++++-- src/internal/form-control.ts | 18 +++++++++++++---- 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/cspell.json b/cspell.json index 65bb7223..48febacd 100644 --- a/cspell.json +++ b/cspell.json @@ -35,7 +35,11 @@ "esbuild", "exportparts", "fieldsets", + "formaction", "formdata", + "formmethod", + "formnovalidate", + "formtarget", "FOUC", "FOUCE", "fullscreen", diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md index ee0fe207..04f80f2e 100644 --- a/docs/resources/changelog.md +++ b/docs/resources/changelog.md @@ -8,6 +8,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis ## Next +- Added `form`, `formaction`, `formmethod`, `formnovalidate`, and `formtarget` attributes to `` [#699](https://github.com/shoelace-style/shoelace/issues/699) - Improved `autofocus` behavior in Safari for `` and `` [#693](https://github.com/shoelace-style/shoelace/issues/693) - Removed feature detection for `focus({ preventScroll })` since it no longer works in Safari - Removed path aliasing and third-party dependencies that it required diff --git a/src/components/button/button.ts b/src/components/button/button.ts index a9fc77bd..17e3b57f 100644 --- a/src/components/button/button.ts +++ b/src/components/button/button.ts @@ -34,7 +34,20 @@ export default class SlButton extends LitElement { @query('.button') button: HTMLButtonElement | HTMLLinkElement; - private readonly formSubmitController = new FormSubmitController(this); + private readonly formSubmitController = new FormSubmitController(this, { + form: (input: HTMLInputElement) => { + // Buttons support a form attribute that points to an arbitrary form, so if this attribute it set we need to query + // the form from the same root using its id + if (input.hasAttribute('form')) { + const doc = input.getRootNode() as Document | ShadowRoot; + const formId = input.getAttribute('form')!; + return doc.getElementById(formId) as HTMLFormElement; + } + + // Fall back to the closest containing form + return input.closest('form'); + } + }); private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix'); @state() private hasFocus = false; @@ -85,6 +98,24 @@ export default class SlButton extends LitElement { /** Tells the browser to download the linked file as this filename. Only used when `href` is set. */ @property() download?: string; + /** + * The "form owner" to associate the button with. If omitted, the closest containing form will be used instead. The + * value of this attribute must be an id of a form in the same document or shadow root as the button. + */ + @property() form: string; + + /** Used to override the form owner's `action` attribute. */ + @property({ attribute: 'formaction' }) formAction: string; + + /** Used to override the form owner's `method` attribute. */ + @property({ attribute: 'formmethod' }) formMethod: 'post' | 'get'; + + /** Used to override the form owner's `novalidate` attribute. */ + @property({ attribute: 'formnovalidate', type: Boolean }) formNoValidate: boolean; + + /** Used to override the form owner's `target` attribute. */ + @property({ attribute: 'formtarget' }) formTarget: '_self' | '_blank' | '_parent' | '_top' | string; + /** Simulates a click on the button. */ click() { this.button.click(); @@ -118,7 +149,7 @@ export default class SlButton extends LitElement { } if (this.type === 'submit') { - this.formSubmitController.submit(); + this.formSubmitController.submit(this); } } diff --git a/src/internal/form-control.ts b/src/internal/form-control.ts index 4ce8d37b..a4838e18 100644 --- a/src/internal/form-control.ts +++ b/src/internal/form-control.ts @@ -2,6 +2,7 @@ import type { ReactiveController, ReactiveControllerHost, TemplateResult } from import { html } from 'lit'; import { classMap } from 'lit/directives/class-map.js'; import { ifDefined } from 'lit/directives/if-defined.js'; +import type SlButton from '../components/button/button'; import './formdata-event-polyfill'; export interface FormSubmitControllerOptions { @@ -84,11 +85,11 @@ export class FormSubmitController implements ReactiveController { } } - submit() { - // Calling form.submit() seems to bypass the submit event and constraint validation. Instead, we can inject a - // native submit button into the form, click it, then remove it to simulate a standard form submission. - const button = document.createElement('button'); + submit(submitter?: HTMLInputElement | SlButton) { + // Calling form.submit() bypasses the submit event and constraint validation. To prevent this, we can inject a + // native submit button into the form, "click" it, then remove it to simulate a standard form submission. if (this.form) { + const button = document.createElement('button'); button.type = 'submit'; button.style.position = 'absolute'; button.style.width = '0'; @@ -97,6 +98,15 @@ export class FormSubmitController implements ReactiveController { button.style.clipPath = 'inset(50%)'; button.style.overflow = 'hidden'; button.style.whiteSpace = 'nowrap'; + + // Pass form override properties through to the temporary button + if (submitter) { + button.formAction = submitter.formAction; + button.formMethod = submitter.formMethod; + button.formNoValidate = submitter.formNoValidate; + button.formTarget = submitter.formTarget; + } + this.form.append(button); button.click(); button.remove();