Skip to content

Commit ad8188f

Browse files
asynclizcopybara-github
authored andcommitted
fix(button,iconbutton): use form-associated mixin behavior
PiperOrigin-RevId: 876381068
1 parent 1cbfc75 commit ad8188f

File tree

7 files changed

+187
-278
lines changed

7 files changed

+187
-278
lines changed

button/internal/button.ts

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,23 @@ 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';
2015
import {
2116
dispatchActivationClick,
2217
isActivationClick,
2318
} from '../../internal/events/form-label-activation.js';
24-
import {
25-
internals,
26-
mixinElementInternals,
27-
} from '../../labs/behaviors/element-internals.js';
19+
import {mixinElementInternals} from '../../labs/behaviors/element-internals.js';
20+
import {mixinFormAssociated} from '../../labs/behaviors/form-associated.js';
21+
import {mixinFormSubmitter} from '../../labs/behaviors/form-submitter.js';
2822

2923
// Separate variable needed for closure.
30-
const buttonBaseClass = mixinDelegatesAria(mixinElementInternals(LitElement));
24+
const buttonBaseClass = mixinDelegatesAria(
25+
mixinFormSubmitter(mixinFormAssociated(mixinElementInternals(LitElement))),
26+
);
3127

3228
/**
3329
* A button component.
3430
*/
35-
export abstract class Button extends buttonBaseClass implements FormSubmitter {
36-
static {
37-
setupFormSubmitter(Button);
38-
}
39-
40-
/** @nocollapse */
41-
static readonly formAssociated = true;
42-
31+
export abstract class Button extends buttonBaseClass {
4332
/** @nocollapse */
4433
static override shadowRootOptions: ShadowRootInit = {
4534
mode: 'open',
@@ -49,7 +38,7 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
4938
/**
5039
* Whether or not the button is disabled.
5140
*/
52-
@property({type: Boolean, reflect: true}) disabled = false;
41+
declare disabled: boolean; // for jsdoc until lit-analyzer is updated
5342

5443
/**
5544
* Whether or not the button is "soft-disabled" (disabled but still
@@ -95,32 +84,6 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
9584
@property({type: Boolean, attribute: 'has-icon', reflect: true}) hasIcon =
9685
false;
9786

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-
117-
/**
118-
* The associated form element with which this element's value will submit.
119-
*/
120-
get form() {
121-
return this[internals].form;
122-
}
123-
12487
@query('.button') private readonly buttonElement!: HTMLElement | null;
12588

12689
@queryAssignedElements({slot: 'icon', flatten: true})

iconbutton/internal/icon-button.ts

Lines changed: 6 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,20 @@ 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';
2217
import {isRtl} from '../../internal/controller/is-rtl.js';
2318
import {
2419
afterDispatch,
2520
setupDispatchHooks,
2621
} from '../../internal/events/dispatch-hooks.js';
27-
import {
28-
internals,
29-
mixinElementInternals,
30-
} from '../../labs/behaviors/element-internals.js';
22+
import {mixinElementInternals} from '../../labs/behaviors/element-internals.js';
23+
import {mixinFormAssociated} from '../../labs/behaviors/form-associated.js';
24+
import {mixinFormSubmitter} from '../../labs/behaviors/form-submitter.js';
3125

3226
type LinkTarget = '_blank' | '_parent' | '_self' | '_top';
3327

3428
// Separate variable needed for closure.
3529
const iconButtonBaseClass = mixinDelegatesAria(
36-
mixinElementInternals(LitElement),
30+
mixinFormSubmitter(mixinFormAssociated(mixinElementInternals(LitElement))),
3731
);
3832

3933
/**
@@ -43,14 +37,7 @@ const iconButtonBaseClass = mixinDelegatesAria(
4337
* --composed
4438
* @fires change {Event} Dispatched when a toggle button toggles --bubbles
4539
*/
46-
export class IconButton extends iconButtonBaseClass implements FormSubmitter {
47-
static {
48-
setupFormSubmitter(IconButton);
49-
}
50-
51-
/** @nocollapse */
52-
static readonly formAssociated = true;
53-
40+
export class IconButton extends iconButtonBaseClass {
5441
/** @nocollapse */
5542
static override shadowRootOptions: ShadowRootInit = {
5643
mode: 'open',
@@ -60,7 +47,7 @@ export class IconButton extends iconButtonBaseClass implements FormSubmitter {
6047
/**
6148
* Disables the icon button and makes it non-interactive.
6249
*/
63-
@property({type: Boolean, reflect: true}) disabled = false;
50+
declare disabled: boolean; // for jsdoc until lit-analyzer is updated
6451

6552
/**
6653
* "Soft-disables" the icon button (disabled but still focusable).
@@ -113,39 +100,6 @@ export class IconButton extends iconButtonBaseClass implements FormSubmitter {
113100
*/
114101
@property({type: Boolean, reflect: true}) selected = false;
115102

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-
135-
/**
136-
* The associated form element with which this element's value will submit.
137-
*/
138-
get form() {
139-
return this[internals].form;
140-
}
141-
142-
/**
143-
* The labels this element is associated with.
144-
*/
145-
get labels() {
146-
return this[internals].labels;
147-
}
148-
149103
@state() private flipIcon = isRtl(this, this.flipIconInRtl);
150104

151105
constructor() {

internal/controller/form-submitter.ts

Lines changed: 0 additions & 130 deletions
This file was deleted.

labs/behaviors/form-associated.ts

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,16 @@ export interface FormAssociated {
4646
disabled: boolean;
4747

4848
/**
49-
* Gets the current form value of a component.
49+
* Gets the current form value of a component. Defaults to the component's
50+
* `value` attribute.
5051
*
5152
* @return The current form value.
5253
*/
5354
[getFormValue](): FormValue | null;
5455

5556
/**
5657
* Gets the current form state of a component. Defaults to the component's
57-
* `[formValue]`.
58+
* `[getFormValue]()` result.
5859
*
5960
* Use this when the state of an element is different from its value, such as
6061
* checkboxes (internal boolean state and a user string value).
@@ -72,25 +73,26 @@ export interface FormAssociated {
7273
formDisabledCallback(disabled: boolean): void;
7374

7475
/**
75-
* A callback for when the form requests to reset its value. Typically, the
76-
* default value that is reset is represented in the attribute of an element.
76+
* An optional callback for when the form requests to reset its value.
77+
* Typically, the default value that is reset is represented in the attribute
78+
* of an element.
7779
*
7880
* This means the attribute used for the value should not update as the value
7981
* changes. For example, a checkbox should not change its default `checked`
8082
* attribute when selected. Ensure form values do not reflect.
8183
*/
82-
formResetCallback(): void;
84+
formResetCallback?(): void;
8385

8486
/**
85-
* A callback for when the form restores the state of a component. For
86-
* example, when a page is reloaded or forms are autofilled.
87+
* An optional callback for when the form restores the state of a component.
88+
* For example, when a page is reloaded or forms are autofilled.
8789
*
8890
* @param state The state to restore, or null to reset the form control's
8991
* value.
9092
* @param reason The reason state was restored, either `'restore'` or
9193
* `'autocomplete'`.
9294
*/
93-
formStateRestoreCallback(
95+
formStateRestoreCallback?(
9496
state: FormRestoreState | null,
9597
reason: FormRestoreReason,
9698
): void;
@@ -238,7 +240,9 @@ export function mixinFormAssociated<
238240
return this.hasAttribute('disabled');
239241
}
240242
set disabled(disabled: boolean) {
241-
this.toggleAttribute('disabled', disabled);
243+
// Coerce `disabled` in `Boolean()` to ensure that setting to `null` or
244+
// `undefined` sets the attribute to `false`.
245+
this.toggleAttribute('disabled', Boolean(disabled));
242246
// We don't need to call `requestUpdate()` since it's called synchronously
243247
// in `attributeChangedCallback()`.
244248
}
@@ -282,9 +286,7 @@ export function mixinFormAssociated<
282286
}
283287

284288
[getFormValue](): FormValue | null {
285-
// Closure does not allow abstract symbol members, so a default
286-
// implementation is needed.
287-
throw new Error('Implement [getFormValue]');
289+
return this.getAttribute('value');
288290
}
289291

290292
[getFormState](): FormValue | null {
@@ -294,13 +296,6 @@ export function mixinFormAssociated<
294296
formDisabledCallback(disabled: boolean) {
295297
this.disabled = disabled;
296298
}
297-
298-
abstract formResetCallback(): void;
299-
300-
abstract formStateRestoreCallback(
301-
state: FormRestoreState | null,
302-
reason: FormRestoreReason,
303-
): void;
304299
}
305300

306301
return FormAssociatedElement;

0 commit comments

Comments
 (0)