-
Notifications
You must be signed in to change notification settings - Fork 913
/
Copy pathtext-field.ts
161 lines (140 loc) · 4.59 KB
/
text-field.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
* @requirecss {text_field.lib.shared_styles}
*
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {redispatchEvent} from '@material/web/controller/events';
import {FormController, getFormValue} from '@material/web/controller/form-controller';
import {ariaProperty} from '@material/web/decorators/aria-property';
import {html, LitElement, PropertyValues, TemplateResult} from 'lit';
import {property, query, state} from 'lit/decorators';
import {ClassInfo, classMap} from 'lit/directives/class-map';
import {ifDefined} from 'lit/directives/if-defined';
import {live} from 'lit/directives/live';
/** @soyCompatible */
export class TextField extends LitElement {
static override shadowRootOptions:
ShadowRootInit = {mode: 'open', delegatesFocus: true};
@property({type: Boolean, reflect: true}) disabled = false;
@property({type: Boolean, reflect: true}) error = false;
@property({type: String}) label?: string;
@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({type: String}) 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({type: String}) defaultValue = '';
// ARIA
// TODO(b/210730484): replace with @soyParam annotation
@property({type: String, attribute: 'data-aria-label', noAccessor: true})
@ariaProperty
override ariaLabel!: string;
// FormElement
get form() {
return this.closest('form');
}
@property({type: String, reflect: true}) name = '';
[getFormValue]() {
return this.value;
}
// <input> properties
@property({type: String, reflect: true}) placeholder = '';
@property({type: Boolean, reflect: true}) readonly = false;
@property({type: String, reflect: true}) type = 'text';
/**
* Returns true when the text field has been interacted with. Native
* validation errors only display in response to user interactions.
*/
@state() protected dirty = false;
@state() protected fieldID = 'field';
@query('.md3-text-field__input')
protected readonly input?: HTMLInputElement|null;
constructor() {
super();
this.addController(new FormController(this));
this.addEventListener('click', this.focus);
}
override focus() {
if (this.disabled || this.matches(':focus-within')) {
// Don't shift focus from an element within the text field, like an icon
// button, to the input when focus is requested.
return;
}
// TODO(b/210731759): replace with super.focus() once SSR supports
// delegating focus
this.input?.focus();
}
/**
* Reset the text field to its default value.
*/
reset() {
this.dirty = false;
this.value = this.defaultValue;
}
/** @soyTemplate */
override render(): TemplateResult {
return html`
<span class="md3-text-field ${classMap(this.getRenderClasses())}">
${this.renderField()}
</span>
`;
}
/** @soyTemplate */
protected getRenderClasses(): ClassInfo {
return {
'md3-text-field--disabled': this.disabled,
'md3-text-field--error': this.error,
};
}
/** @soyTemplate */
protected renderField(): TemplateResult {
return html``;
}
/** @soyTemplate */
protected renderFieldContent(): TemplateResult {
return html`
<input
class="md3-text-field__input"
aria-invalid=${this.error}
aria-label=${ifDefined(this.ariaLabel)}
aria-labelledby="${this.fieldID}"
.disabled=${this.disabled}
.placeholder=${this.placeholder}
.readonly=${this.readonly}
.required=${this.required}
.type=${this.type}
.value=${live(this.value)}
@change=${this.redispatchEvent}
@input=${this.handleInput}
@select=${this.redispatchEvent}
>
`;
}
protected override update(changedProperties: PropertyValues<TextField>) {
if (changedProperties.has('defaultValue') && !this.dirty) {
// Do this here instead of in a setter so that the order of setting both
// value and defaultValue does not matter.
this.value = this.defaultValue;
}
super.update(changedProperties);
}
protected handleInput(event: InputEvent) {
this.dirty = true;
this.value = (event.target as HTMLInputElement).value;
this.redispatchEvent(event);
}
protected redispatchEvent(event: Event) {
redispatchEvent(this, event);
}
}