diff --git a/textfield/lib/text-field.ts b/textfield/lib/text-field.ts index 6c9194c9e9..f6887542b5 100644 --- a/textfield/lib/text-field.ts +++ b/textfield/lib/text-field.ts @@ -66,18 +66,8 @@ export abstract class TextField extends LitElement { @property({type: Boolean, reflect: true}) required = false; /** * The current value of the text field. It is always a string. - * - * This is equal to `defaultValue` before user input. */ @property() value = ''; - /** - * The default value of the text field. Before user input, changing the - * default value will update `value` as well. - * - * When the text field is reset, its `value` will be set to this default - * value. - */ - @property() defaultValue = ''; /** * An optional prefix to display before the input value. */ @@ -267,19 +257,6 @@ export abstract class TextField extends LitElement { * to screen readers. */ @state() private refreshErrorAlert = false; - /** - * Returns true when the text field's `value` property has been changed from - * it's initial value. - * - * Setting `value` should always overwrite `defaultValue`, even when `value` - * is an empty string. This flag ensures that behavior. - */ - @state() private valueHasChanged = false; - /** - * Whether or not to ignore the next `value` change when computing - * `valueHasChanged`. - */ - private ignoreNextValueChange = false; /** * Whether or not a native error has been reported via `reportValidity()`. */ @@ -462,27 +439,20 @@ export abstract class TextField extends LitElement { */ reset() { this.dirty = false; - this.valueHasChanged = false; - this.ignoreNextValueChange = true; - this.value = this.defaultValue; + this.value = this.getAttribute('value') ?? ''; this.nativeError = false; this.nativeErrorText = ''; } - protected override update(changedProperties: PropertyValues) { - // Consider a value change anything that is not the initial empty string - // value. - const valueHasChanged = changedProperties.has('value') && - changedProperties.get('value') !== undefined; - if (valueHasChanged && !this.ignoreNextValueChange) { - this.valueHasChanged = true; - } - - if (this.ignoreNextValueChange) { - this.ignoreNextValueChange = false; + override attributeChangedCallback( + attribute: string, newValue: string|null, oldValue: string|null) { + if (attribute === 'value' && this.dirty) { + // After user input, changing the value attribute no longer updates the + // text field's value (until reset). This matches native behavior. + return; } - super.update(changedProperties); + super.attributeChangedCallback(attribute, newValue, oldValue); } protected override render() { @@ -505,9 +475,6 @@ export abstract class TextField extends LitElement { // value to change without dispatching an event, re-sync it. const value = this.getInput().value; if (this.value !== value) { - // Don't consider these updates (such as setting `defaultValue`) as - // the developer directly changing the `value`. - this.ignoreNextValueChange = true; // Note this is typically inefficient in updated() since it schedules // another update. However, it is needed for the to fully render // before checking its value. @@ -536,7 +503,7 @@ export abstract class TextField extends LitElement { ?hasEnd=${this.hasTrailingIcon} ?hasStart=${this.hasLeadingIcon} .label=${this.label} - ?populated=${!!this.getInputValue()} + ?populated=${!!this.value} ?required=${this.required} > ${this.renderLeadingIcon()} @@ -588,22 +555,13 @@ export abstract class TextField extends LitElement { ?required=${this.required} step=${(this.step || nothing) as unknown as number} type=${this.type} - .value=${live(this.getInputValue())} + .value=${live(this.value)} @change=${this.redispatchEvent} @input=${this.handleInput} @select=${this.redispatchEvent} >`; } - private getInputValue() { - const alwaysShowValue = this.dirty || this.valueHasChanged; - if (alwaysShowValue) { - return this.value; - } - - return this.defaultValue || this.value; - } - private getAriaDescribedBy() { const ids: string[] = []; if (this.getSupportingText()) { diff --git a/textfield/lib/text-field_test.ts b/textfield/lib/text-field_test.ts index 26846026b4..0a98e8e741 100644 --- a/textfield/lib/text-field_test.ts +++ b/textfield/lib/text-field_test.ts @@ -147,14 +147,15 @@ describe('TextField', () => { describe('resetting the input', () => { it('should set value back to default value', async () => { const {harness} = await setupTest(); - harness.element.defaultValue = 'Default'; + harness.element.setAttribute('value', 'Default'); await env.waitForStability(); + expect(harness.element.value).toBe('Default'); await harness.deleteValue(); await harness.inputValue('Value'); + expect(harness.element.value).toBe('Value'); harness.element.reset(); - expect(harness.element.defaultValue).toBe('Default'); expect(harness.element.value).toBe('Default'); }); @@ -164,37 +165,15 @@ describe('TextField', () => { await harness.inputValue('Value'); harness.element.reset(); - expect(harness.element.defaultValue).toBe(''); expect(harness.element.value).toBe(''); }); - - it('should allow defaultValue to update value again', async () => { - const {harness} = await setupTest(); - - // defaultValue changes value - harness.element.defaultValue = 'First default'; - await env.waitForStability(); - expect(harness.element.value).toBe('First default'); - - // Setting value programmatically causes it to stick - harness.element.value = 'Value'; - harness.element.defaultValue = 'Second default'; - await env.waitForStability(); - expect(harness.element.value).toBe('Value'); - - // Resetting should return to original functionality - harness.element.reset(); - harness.element.defaultValue = 'Third default'; - await env.waitForStability(); - expect(harness.element.value).toBe('Third default'); - }); }); describe('default value', () => { it('should update `value` before user input', async () => { const {harness} = await setupTest(); - harness.element.defaultValue = 'Default'; + harness.element.setAttribute('value', 'Default'); await env.waitForStability(); expect(harness.element.value).toBe('Default'); @@ -203,9 +182,9 @@ describe('TextField', () => { it('should update `value` multiple times', async () => { const {harness} = await setupTest(); - harness.element.defaultValue = 'First default'; + harness.element.setAttribute('value', 'First default'); await env.waitForStability(); - harness.element.defaultValue = 'Second default'; + harness.element.setAttribute('value', 'Second default'); await env.waitForStability(); expect(harness.element.value).toBe('Second default'); @@ -214,22 +193,22 @@ describe('TextField', () => { it('should NOT update `value` after user input', async () => { const {harness} = await setupTest(); - harness.element.defaultValue = 'First default'; + harness.element.setAttribute('value', 'First default'); await env.waitForStability(); await harness.deleteValue(); await harness.inputValue('Value'); - harness.element.defaultValue = 'Second default'; + harness.element.setAttribute('value', 'Second default'); await env.waitForStability(); expect(harness.element.value).toBe('Value'); }); - it('should render `value` instead of `defaultValue` when `value` changes', + it('should render `value` instead of default value attribute when `value` changes', async () => { const {harness, input} = await setupTest(); - harness.element.defaultValue = 'Default'; + harness.element.setAttribute('value', 'Default'); await env.waitForStability(); expect(input.value).toBe('Default'); @@ -240,7 +219,7 @@ describe('TextField', () => { harness.element.value = ''; await env.waitForStability(); expect(input.value).toBe(''); - expect(harness.element.defaultValue).toBe('Default'); + expect(harness.element.getAttribute('value')).toBe('Default'); }); });