Skip to content

Commit

Permalink
fix(button)!: type is submit by default
Browse files Browse the repository at this point in the history
Also fixes the button not being the correct `submitter` for submit events.

BREAKING CHANGE: Buttons submit forms by default, like `<button>`. Add `type="button"` to not submit forms.

PiperOrigin-RevId: 553248284
  • Loading branch information
asyncLiz authored and copybara-github committed Aug 2, 2023
1 parent 6aa42f3 commit 97f5b61
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 13 deletions.
23 changes: 23 additions & 0 deletions button/filled-button_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ describe('<md-filled-button>', () => {
return {harness: new ButtonHarness(element), form};
}

it('button is submit type by default', async () => {
const {harness} = await setupTest();
expect(harness.element.type).toBe('submit');
});

it('button with type submit can submit a form', async () => {
const {harness, form} = await setupTest();
harness.element.type = 'submit';
Expand Down Expand Up @@ -69,5 +74,23 @@ describe('<md-filled-button>', () => {

expect(form.requestSubmit).not.toHaveBeenCalled();
});

it('should set the button as the SubmitEvent submitter', async () => {
const {harness, form} = await setupTest();
const submitListener =
jasmine.createSpy('submitListener').and.callFake((event: Event) => {
event.preventDefault();
});

form.addEventListener('submit', submitListener);

await harness.clickWithMouse();

expect(submitListener).toHaveBeenCalled();
const event = submitListener.calls.argsFor(0)[0] as SubmitEvent;
expect(event.submitter)
.withContext('event.submitter')
.toBe(harness.element);
});
});
});
40 changes: 27 additions & 13 deletions button/internal/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,15 @@ export abstract class Button extends LitElement {
@property({type: Boolean, attribute: 'has-icon'}) hasIcon = false;

/**
* Specifies the type of button, used for controlling forms. When type
* is `submit`, the containing form is submitted; when it is `reset` the
* form is reset.
* A string indicating the behavior of the button.
*
* - submit: The button submits the form. This is the default value if the
* attribute is not specified, or if it is dynamically changed to an empty or
* invalid value.
* - reset: The button resets the form.
* - button: The button does nothing.
*/
@property() type: ''|'submit'|'reset' = '';
@property() type: 'button'|'submit'|'reset' = 'submit';

@query('.button') private readonly buttonElement!: HTMLElement|null;

Expand Down Expand Up @@ -185,25 +189,35 @@ export abstract class Button extends LitElement {
}
// based on type, trigger form action.
const {type, internals: {form}} = this;
if (!form) {
return;
}
const isSubmit = type === 'submit', isReset = type === 'reset';
if (!(isSubmit || isReset)) {
if (!form || type === 'button') {
return;
}
event.stopPropagation();

this.isRedispatchingEvent = true;
const prevented = !redispatchEvent(this, event);
this.isRedispatchingEvent = false;
if (prevented) {
return;
}
if (isSubmit) {
form.requestSubmit();
} else if (isReset) {

if (type === 'reset') {
form.reset();
return;
}

// form.requestSubmit(submitter) does not work with form associated custom
// elements. This patches the dispatched submit event to add the correct
// `submitter`.
// See https://github.com/WICG/webcomponents/issues/814
form.addEventListener('submit', submitEvent => {
Object.defineProperty(submitEvent, 'submitter', {
configurable: true,
enumerable: true,
get: () => this,
});
}, {capture: true, once: true});

form.requestSubmit();
}

private handleSlotChange() {
Expand Down

0 comments on commit 97f5b61

Please sign in to comment.