Skip to content

Commit 2317c5a

Browse files
asynclizcopybara-github
authored andcommitted
fix(textfield)!: remove defaultValue
BREAKING CHANGE: Explicit "defaultValue" has been removed. Set the 'value' attribute to communicate a default value for resetting (similar to native <input>) PiperOrigin-RevId: 532095818
1 parent 4ddeee1 commit 2317c5a

File tree

2 files changed

+21
-84
lines changed

2 files changed

+21
-84
lines changed

textfield/lib/text-field.ts

Lines changed: 10 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,8 @@ export abstract class TextField extends LitElement {
6666
@property({type: Boolean, reflect: true}) required = false;
6767
/**
6868
* The current value of the text field. It is always a string.
69-
*
70-
* This is equal to `defaultValue` before user input.
7169
*/
7270
@property() value = '';
73-
/**
74-
* The default value of the text field. Before user input, changing the
75-
* default value will update `value` as well.
76-
*
77-
* When the text field is reset, its `value` will be set to this default
78-
* value.
79-
*/
80-
@property() defaultValue = '';
8171
/**
8272
* An optional prefix to display before the input value.
8373
*/
@@ -267,19 +257,6 @@ export abstract class TextField extends LitElement {
267257
* to screen readers.
268258
*/
269259
@state() private refreshErrorAlert = false;
270-
/**
271-
* Returns true when the text field's `value` property has been changed from
272-
* it's initial value.
273-
*
274-
* Setting `value` should always overwrite `defaultValue`, even when `value`
275-
* is an empty string. This flag ensures that behavior.
276-
*/
277-
@state() private valueHasChanged = false;
278-
/**
279-
* Whether or not to ignore the next `value` change when computing
280-
* `valueHasChanged`.
281-
*/
282-
private ignoreNextValueChange = false;
283260
/**
284261
* Whether or not a native error has been reported via `reportValidity()`.
285262
*/
@@ -462,27 +439,20 @@ export abstract class TextField extends LitElement {
462439
*/
463440
reset() {
464441
this.dirty = false;
465-
this.valueHasChanged = false;
466-
this.ignoreNextValueChange = true;
467-
this.value = this.defaultValue;
442+
this.value = this.getAttribute('value') ?? '';
468443
this.nativeError = false;
469444
this.nativeErrorText = '';
470445
}
471446

472-
protected override update(changedProperties: PropertyValues) {
473-
// Consider a value change anything that is not the initial empty string
474-
// value.
475-
const valueHasChanged = changedProperties.has('value') &&
476-
changedProperties.get('value') !== undefined;
477-
if (valueHasChanged && !this.ignoreNextValueChange) {
478-
this.valueHasChanged = true;
479-
}
480-
481-
if (this.ignoreNextValueChange) {
482-
this.ignoreNextValueChange = false;
447+
override attributeChangedCallback(
448+
attribute: string, newValue: string|null, oldValue: string|null) {
449+
if (attribute === 'value' && this.dirty) {
450+
// After user input, changing the value attribute no longer updates the
451+
// text field's value (until reset). This matches native <input> behavior.
452+
return;
483453
}
484454

485-
super.update(changedProperties);
455+
super.attributeChangedCallback(attribute, newValue, oldValue);
486456
}
487457

488458
protected override render() {
@@ -505,9 +475,6 @@ export abstract class TextField extends LitElement {
505475
// value to change without dispatching an event, re-sync it.
506476
const value = this.getInput().value;
507477
if (this.value !== value) {
508-
// Don't consider these updates (such as setting `defaultValue`) as
509-
// the developer directly changing the `value`.
510-
this.ignoreNextValueChange = true;
511478
// Note this is typically inefficient in updated() since it schedules
512479
// another update. However, it is needed for the <input> to fully render
513480
// before checking its value.
@@ -536,7 +503,7 @@ export abstract class TextField extends LitElement {
536503
?hasEnd=${this.hasTrailingIcon}
537504
?hasStart=${this.hasLeadingIcon}
538505
.label=${this.label}
539-
?populated=${!!this.getInputValue()}
506+
?populated=${!!this.value}
540507
?required=${this.required}
541508
>
542509
${this.renderLeadingIcon()}
@@ -588,22 +555,13 @@ export abstract class TextField extends LitElement {
588555
?required=${this.required}
589556
step=${(this.step || nothing) as unknown as number}
590557
type=${this.type}
591-
.value=${live(this.getInputValue())}
558+
.value=${live(this.value)}
592559
@change=${this.redispatchEvent}
593560
@input=${this.handleInput}
594561
@select=${this.redispatchEvent}
595562
>`;
596563
}
597564

598-
private getInputValue() {
599-
const alwaysShowValue = this.dirty || this.valueHasChanged;
600-
if (alwaysShowValue) {
601-
return this.value;
602-
}
603-
604-
return this.defaultValue || this.value;
605-
}
606-
607565
private getAriaDescribedBy() {
608566
const ids: string[] = [];
609567
if (this.getSupportingText()) {

textfield/lib/text-field_test.ts

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,15 @@ describe('TextField', () => {
147147
describe('resetting the input', () => {
148148
it('should set value back to default value', async () => {
149149
const {harness} = await setupTest();
150-
harness.element.defaultValue = 'Default';
150+
harness.element.setAttribute('value', 'Default');
151151
await env.waitForStability();
152152

153+
expect(harness.element.value).toBe('Default');
153154
await harness.deleteValue();
154155
await harness.inputValue('Value');
156+
expect(harness.element.value).toBe('Value');
155157
harness.element.reset();
156158

157-
expect(harness.element.defaultValue).toBe('Default');
158159
expect(harness.element.value).toBe('Default');
159160
});
160161

@@ -164,37 +165,15 @@ describe('TextField', () => {
164165
await harness.inputValue('Value');
165166
harness.element.reset();
166167

167-
expect(harness.element.defaultValue).toBe('');
168168
expect(harness.element.value).toBe('');
169169
});
170-
171-
it('should allow defaultValue to update value again', async () => {
172-
const {harness} = await setupTest();
173-
174-
// defaultValue changes value
175-
harness.element.defaultValue = 'First default';
176-
await env.waitForStability();
177-
expect(harness.element.value).toBe('First default');
178-
179-
// Setting value programmatically causes it to stick
180-
harness.element.value = 'Value';
181-
harness.element.defaultValue = 'Second default';
182-
await env.waitForStability();
183-
expect(harness.element.value).toBe('Value');
184-
185-
// Resetting should return to original functionality
186-
harness.element.reset();
187-
harness.element.defaultValue = 'Third default';
188-
await env.waitForStability();
189-
expect(harness.element.value).toBe('Third default');
190-
});
191170
});
192171

193172
describe('default value', () => {
194173
it('should update `value` before user input', async () => {
195174
const {harness} = await setupTest();
196175

197-
harness.element.defaultValue = 'Default';
176+
harness.element.setAttribute('value', 'Default');
198177
await env.waitForStability();
199178

200179
expect(harness.element.value).toBe('Default');
@@ -203,9 +182,9 @@ describe('TextField', () => {
203182
it('should update `value` multiple times', async () => {
204183
const {harness} = await setupTest();
205184

206-
harness.element.defaultValue = 'First default';
185+
harness.element.setAttribute('value', 'First default');
207186
await env.waitForStability();
208-
harness.element.defaultValue = 'Second default';
187+
harness.element.setAttribute('value', 'Second default');
209188
await env.waitForStability();
210189

211190
expect(harness.element.value).toBe('Second default');
@@ -214,22 +193,22 @@ describe('TextField', () => {
214193
it('should NOT update `value` after user input', async () => {
215194
const {harness} = await setupTest();
216195

217-
harness.element.defaultValue = 'First default';
196+
harness.element.setAttribute('value', 'First default');
218197
await env.waitForStability();
219198
await harness.deleteValue();
220199
await harness.inputValue('Value');
221200

222-
harness.element.defaultValue = 'Second default';
201+
harness.element.setAttribute('value', 'Second default');
223202
await env.waitForStability();
224203

225204
expect(harness.element.value).toBe('Value');
226205
});
227206

228-
it('should render `value` instead of `defaultValue` when `value` changes',
207+
it('should render `value` instead of default value attribute when `value` changes',
229208
async () => {
230209
const {harness, input} = await setupTest();
231210

232-
harness.element.defaultValue = 'Default';
211+
harness.element.setAttribute('value', 'Default');
233212
await env.waitForStability();
234213
expect(input.value).toBe('Default');
235214

@@ -240,7 +219,7 @@ describe('TextField', () => {
240219
harness.element.value = '';
241220
await env.waitForStability();
242221
expect(input.value).toBe('');
243-
expect(harness.element.defaultValue).toBe('Default');
222+
expect(harness.element.getAttribute('value')).toBe('Default');
244223
});
245224
});
246225

0 commit comments

Comments
 (0)