mirror of
https://github.com/material-components/material-web.git
synced 2024-09-19 17:47:46 +03:00
fix(text-field): ensure value
can overwrite defaultValue
PiperOrigin-RevId: 472984918
This commit is contained in:
parent
e77d4726fa
commit
58ae98cbc8
@ -10,7 +10,7 @@ import {redispatchEvent} from '@material/web/controller/events.js';
|
|||||||
import {FormController, getFormValue} from '@material/web/controller/form-controller.js';
|
import {FormController, getFormValue} from '@material/web/controller/form-controller.js';
|
||||||
import {stringConverter} from '@material/web/controller/string-converter.js';
|
import {stringConverter} from '@material/web/controller/string-converter.js';
|
||||||
import {ariaProperty} from '@material/web/decorators/aria-property.js';
|
import {ariaProperty} from '@material/web/decorators/aria-property.js';
|
||||||
import {html, LitElement, TemplateResult} from 'lit';
|
import {html, LitElement, PropertyValues, TemplateResult} from 'lit';
|
||||||
import {property, query, queryAssignedElements, state} from 'lit/decorators.js';
|
import {property, query, queryAssignedElements, state} from 'lit/decorators.js';
|
||||||
import {ClassInfo, classMap} from 'lit/directives/class-map.js';
|
import {ClassInfo, classMap} from 'lit/directives/class-map.js';
|
||||||
import {ifDefined} from 'lit/directives/if-defined.js';
|
import {ifDefined} from 'lit/directives/if-defined.js';
|
||||||
@ -308,6 +308,19 @@ export abstract class TextField extends LitElement {
|
|||||||
*/
|
*/
|
||||||
@state() protected dirty = false;
|
@state() protected dirty = false;
|
||||||
@state() protected focused = false;
|
@state() protected focused = 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() protected valueHasChanged = false;
|
||||||
|
/**
|
||||||
|
* Whether or not to ignore the next `value` change when computing
|
||||||
|
* `valueHasChanged`.
|
||||||
|
*/
|
||||||
|
protected ignoreNextValueChange = false;
|
||||||
@query('.md3-text-field__input')
|
@query('.md3-text-field__input')
|
||||||
protected readonly input?: HTMLInputElement|null;
|
protected readonly input?: HTMLInputElement|null;
|
||||||
protected abstract readonly fieldTag: StaticValue;
|
protected abstract readonly fieldTag: StaticValue;
|
||||||
@ -478,6 +491,8 @@ export abstract class TextField extends LitElement {
|
|||||||
*/
|
*/
|
||||||
reset() {
|
reset() {
|
||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
|
this.valueHasChanged = false;
|
||||||
|
this.ignoreNextValueChange = true;
|
||||||
this.value = this.defaultValue;
|
this.value = this.defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -606,7 +621,8 @@ export abstract class TextField extends LitElement {
|
|||||||
|
|
||||||
/** @soyTemplate */
|
/** @soyTemplate */
|
||||||
protected getInputValue(): string {
|
protected getInputValue(): string {
|
||||||
return this.dirty ? this.value : this.value || this.defaultValue;
|
const alwaysShowValue = this.dirty || this.valueHasChanged;
|
||||||
|
return alwaysShowValue ? this.value : this.defaultValue || this.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @soyTemplate */
|
/** @soyTemplate */
|
||||||
@ -682,11 +698,30 @@ export abstract class TextField extends LitElement {
|
|||||||
return this.hasCounter() ? html`${length} / ${this.maxLength}` : html``;
|
return this.hasCounter() ? html`${length} / ${this.maxLength}` : html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override update(changedProperties: PropertyValues<TextField>) {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.update(changedProperties);
|
||||||
|
}
|
||||||
|
|
||||||
protected override updated() {
|
protected override updated() {
|
||||||
// If a property such as `type` changes and causes the internal <input>
|
// If a property such as `type` changes and causes the internal <input>
|
||||||
// value to change without dispatching an event, re-sync it.
|
// value to change without dispatching an event, re-sync it.
|
||||||
const value = this.getInput().value;
|
const value = this.getInput().value;
|
||||||
if (this.value !== 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
|
// Note this is typically inefficient in updated() since it schedules
|
||||||
// another update. However, it is needed for the <input> to fully render
|
// another update. However, it is needed for the <input> to fully render
|
||||||
// before checking its value.
|
// before checking its value.
|
||||||
|
@ -162,6 +162,27 @@ describe('TextField', () => {
|
|||||||
expect(harness.element.defaultValue).toBe('');
|
expect(harness.element.defaultValue).toBe('');
|
||||||
expect(harness.element.value).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 programatically 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', () => {
|
describe('default value', () => {
|
||||||
@ -174,6 +195,17 @@ describe('TextField', () => {
|
|||||||
expect(harness.element.value).toBe('Default');
|
expect(harness.element.value).toBe('Default');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update `value` multiple times', async () => {
|
||||||
|
const {harness} = await setupTest();
|
||||||
|
|
||||||
|
harness.element.defaultValue = 'First default';
|
||||||
|
await env.waitForStability();
|
||||||
|
harness.element.defaultValue = 'Second default';
|
||||||
|
await env.waitForStability();
|
||||||
|
|
||||||
|
expect(harness.element.value).toBe('Second default');
|
||||||
|
});
|
||||||
|
|
||||||
it('should NOT update `value` after user input', async () => {
|
it('should NOT update `value` after user input', async () => {
|
||||||
const {harness} = await setupTest();
|
const {harness} = await setupTest();
|
||||||
|
|
||||||
@ -187,6 +219,24 @@ describe('TextField', () => {
|
|||||||
|
|
||||||
expect(harness.element.value).toBe('Value');
|
expect(harness.element.value).toBe('Value');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render `value` instead of `defaultValue` when `value` changes',
|
||||||
|
async () => {
|
||||||
|
const {harness, input} = await setupTest();
|
||||||
|
|
||||||
|
harness.element.defaultValue = 'Default';
|
||||||
|
await env.waitForStability();
|
||||||
|
expect(input.value).toBe('Default');
|
||||||
|
|
||||||
|
harness.element.value = 'Value';
|
||||||
|
await env.waitForStability();
|
||||||
|
expect(input.value).toBe('Value');
|
||||||
|
|
||||||
|
harness.element.value = '';
|
||||||
|
await env.waitForStability();
|
||||||
|
expect(input.value).toBe('');
|
||||||
|
expect(harness.element.defaultValue).toBe('Default');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('valueAsDate', () => {
|
describe('valueAsDate', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user