Skip to content

Commit dcd7b20

Browse files
revert: "feat(labs): change form-submitter to a behavior mixin"
This reverts commit 4142a69. PiperOrigin-RevId: 878971156
1 parent 4142a69 commit dcd7b20

File tree

5 files changed

+201
-153
lines changed

5 files changed

+201
-153
lines changed

button/internal/button.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import {property, query, queryAssignedElements} from 'lit/decorators.js';
1212

1313
import {ARIAMixinStrict} from '../../internal/aria/aria.js';
1414
import {mixinDelegatesAria} from '../../internal/aria/delegate.js';
15+
import {
16+
FormSubmitter,
17+
setupFormSubmitter,
18+
type FormSubmitterType,
19+
} from '../../internal/controller/form-submitter.js';
1520
import {
1621
dispatchActivationClick,
1722
isActivationClick,
@@ -20,17 +25,18 @@ import {
2025
internals,
2126
mixinElementInternals,
2227
} from '../../labs/behaviors/element-internals.js';
23-
import {mixinFormSubmitter} from '../../labs/behaviors/form-submitter.js';
2428

2529
// Separate variable needed for closure.
26-
const buttonBaseClass = mixinDelegatesAria(
27-
mixinFormSubmitter(mixinElementInternals(LitElement)),
28-
);
30+
const buttonBaseClass = mixinDelegatesAria(mixinElementInternals(LitElement));
2931

3032
/**
3133
* A button component.
3234
*/
33-
export abstract class Button extends buttonBaseClass {
35+
export abstract class Button extends buttonBaseClass implements FormSubmitter {
36+
static {
37+
setupFormSubmitter(Button);
38+
}
39+
3440
/** @nocollapse */
3541
static readonly formAssociated = true;
3642

@@ -89,6 +95,25 @@ export abstract class Button extends buttonBaseClass {
8995
@property({type: Boolean, attribute: 'has-icon', reflect: true}) hasIcon =
9096
false;
9197

98+
/**
99+
* The default behavior of the button. May be "button", "reset", or "submit"
100+
* (default).
101+
*/
102+
@property() type: FormSubmitterType = 'submit';
103+
104+
/**
105+
* The value added to a form with the button's name when the button submits a
106+
* form.
107+
*/
108+
@property({reflect: true}) value = '';
109+
110+
get name() {
111+
return this.getAttribute('name') ?? '';
112+
}
113+
set name(name: string) {
114+
this.setAttribute('name', name);
115+
}
116+
92117
/**
93118
* The associated form element with which this element's value will submit.
94119
*/

iconbutton/internal/icon-button.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import {literal, html as staticHtml} from 'lit/static-html.js';
1414

1515
import {ARIAMixinStrict} from '../../internal/aria/aria.js';
1616
import {mixinDelegatesAria} from '../../internal/aria/delegate.js';
17+
import {
18+
FormSubmitter,
19+
setupFormSubmitter,
20+
type FormSubmitterType,
21+
} from '../../internal/controller/form-submitter.js';
1722
import {isRtl} from '../../internal/controller/is-rtl.js';
1823
import {
1924
afterDispatch,
@@ -23,13 +28,12 @@ import {
2328
internals,
2429
mixinElementInternals,
2530
} from '../../labs/behaviors/element-internals.js';
26-
import {mixinFormSubmitter} from '../../labs/behaviors/form-submitter.js';
2731

2832
type LinkTarget = '_blank' | '_parent' | '_self' | '_top';
2933

3034
// Separate variable needed for closure.
3135
const iconButtonBaseClass = mixinDelegatesAria(
32-
mixinFormSubmitter(mixinElementInternals(LitElement)),
36+
mixinElementInternals(LitElement),
3337
);
3438

3539
/**
@@ -39,7 +43,11 @@ const iconButtonBaseClass = mixinDelegatesAria(
3943
* --composed
4044
* @fires change {Event} Dispatched when a toggle button toggles --bubbles
4145
*/
42-
export class IconButton extends iconButtonBaseClass {
46+
export class IconButton extends iconButtonBaseClass implements FormSubmitter {
47+
static {
48+
setupFormSubmitter(IconButton);
49+
}
50+
4351
/** @nocollapse */
4452
static readonly formAssociated = true;
4553

@@ -105,6 +113,25 @@ export class IconButton extends iconButtonBaseClass {
105113
*/
106114
@property({type: Boolean, reflect: true}) selected = false;
107115

116+
/**
117+
* The default behavior of the button. May be "button", "reset", or "submit"
118+
* (default).
119+
*/
120+
@property() type: FormSubmitterType = 'submit';
121+
122+
/**
123+
* The value added to a form with the button's name when the button submits a
124+
* form.
125+
*/
126+
@property({reflect: true}) value = '';
127+
128+
get name() {
129+
return this.getAttribute('name') ?? '';
130+
}
131+
set name(name: string) {
132+
this.setAttribute('name', name);
133+
}
134+
108135
/**
109136
* The associated form element with which this element's value will submit.
110137
*/
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/**
2+
* @license
3+
* Copyright 2023 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import {isServer, ReactiveElement} from 'lit';
8+
9+
import {
10+
internals,
11+
WithElementInternals,
12+
} from '../../labs/behaviors/element-internals.js';
13+
14+
/**
15+
* A string indicating the form submission behavior of the element.
16+
*
17+
* - submit: The element submits the form. This is the default value if the
18+
* attribute is not specified, or if it is dynamically changed to an empty or
19+
* invalid value.
20+
* - reset: The element resets the form.
21+
* - button: The element does nothing.
22+
*/
23+
export type FormSubmitterType = 'button' | 'submit' | 'reset';
24+
25+
/**
26+
* An element that can submit or reset a `<form>`, similar to
27+
* `<button type="submit">`.
28+
*/
29+
export interface FormSubmitter extends ReactiveElement, WithElementInternals {
30+
/**
31+
* A string indicating the form submission behavior of the element.
32+
*
33+
* - submit: The element submits the form. This is the default value if the
34+
* attribute is not specified, or if it is dynamically changed to an empty or
35+
* invalid value.
36+
* - reset: The element resets the form.
37+
* - button: The element does nothing.
38+
*/
39+
type: FormSubmitterType;
40+
41+
/**
42+
* The HTML name to use in form submission. When combined with a `value`, the
43+
* submitting button's name/value will be added to the form.
44+
*
45+
* Names must reflect to a `name` attribute for form integration.
46+
*/
47+
name: string;
48+
49+
/**
50+
* The value of the button. When combined with a `name`, the submitting
51+
* button's name/value will be added to the form.
52+
*/
53+
value: string;
54+
}
55+
56+
type FormSubmitterConstructor =
57+
| (new () => FormSubmitter)
58+
| (abstract new () => FormSubmitter);
59+
60+
/**
61+
* Sets up an element's constructor to enable form submission. The element
62+
* instance should be form associated and have a `type` property.
63+
*
64+
* A click listener is added to each element instance. If the click is not
65+
* default prevented, it will submit the element's form, if any.
66+
*
67+
* @example
68+
* ```ts
69+
* class MyElement extends mixinElementInternals(LitElement) {
70+
* static {
71+
* setupFormSubmitter(MyElement);
72+
* }
73+
*
74+
* static formAssociated = true;
75+
*
76+
* type: FormSubmitterType = 'submit';
77+
* }
78+
* ```
79+
*
80+
* @param ctor The form submitter element's constructor.
81+
*/
82+
export function setupFormSubmitter(ctor: FormSubmitterConstructor) {
83+
if (isServer) {
84+
return;
85+
}
86+
87+
(ctor as unknown as typeof ReactiveElement).addInitializer((instance) => {
88+
const submitter = instance as FormSubmitter;
89+
submitter.addEventListener('click', async (event) => {
90+
const {type, [internals]: elementInternals} = submitter;
91+
const {form} = elementInternals;
92+
if (!form || type === 'button') {
93+
return;
94+
}
95+
96+
// Wait a full task for event bubbling to complete.
97+
await new Promise<void>((resolve) => {
98+
setTimeout(resolve);
99+
});
100+
101+
if (event.defaultPrevented) {
102+
return;
103+
}
104+
105+
if (type === 'reset') {
106+
form.reset();
107+
return;
108+
}
109+
110+
// form.requestSubmit(submitter) does not work with form associated custom
111+
// elements. This patches the dispatched submit event to add the correct
112+
// `submitter`.
113+
// See https://github.com/WICG/webcomponents/issues/814
114+
form.addEventListener(
115+
'submit',
116+
(submitEvent) => {
117+
Object.defineProperty(submitEvent, 'submitter', {
118+
configurable: true,
119+
enumerable: true,
120+
get: () => submitter,
121+
});
122+
},
123+
{capture: true, once: true},
124+
);
125+
126+
elementInternals.setFormValue(submitter.value);
127+
form.requestSubmit();
128+
});
129+
});
130+
}

labs/behaviors/form-submitter_test.ts renamed to internal/controller/form-submitter_test.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
// import 'jasmine'; (google3-only)
88

99
import {html, LitElement} from 'lit';
10-
import {customElement} from 'lit/decorators.js';
10+
import {customElement, property} from 'lit/decorators.js';
1111

1212
import {mixinElementInternals} from '../../labs/behaviors/element-internals.js';
1313
import {Environment} from '../../testing/environment.js';
1414
import {Harness} from '../../testing/harness.js';
15-
import {mixinFormSubmitter} from './form-submitter.js';
15+
import {FormSubmitterType, setupFormSubmitter} from './form-submitter.js';
1616

1717
declare global {
1818
interface HTMLElementTagNameMap {
@@ -21,10 +21,16 @@ declare global {
2121
}
2222

2323
@customElement('test-form-submitter-button')
24-
class FormSubmitterButton extends mixinFormSubmitter(
25-
mixinElementInternals(LitElement),
26-
) {
24+
class FormSubmitterButton extends mixinElementInternals(LitElement) {
25+
static {
26+
setupFormSubmitter(FormSubmitterButton);
27+
}
28+
2729
static formAssociated = true;
30+
31+
type: FormSubmitterType = 'submit';
32+
@property({reflect: true}) name = '';
33+
value = '';
2834
}
2935

3036
describe('setupFormSubmitter()', () => {

0 commit comments

Comments
 (0)