Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit 074919a

Browse files
authored
chore(text-field): Split out input into subelement (#1631)
BREAKING CHANGE: Please update implementations of MDCTextFieldAdapter to implement getInputFoundation(). Methods registerInputInteractionHandler()/deregisterInputInteractionHandler() have been renamed to registerInputEventHandler()/deregisterInputEventHandler(). getNativeInput() is no longer in MDCTextFieldAdapter. See the README for mdc-textfield/input for more information.
1 parent 3b8ce1b commit 074919a

15 files changed

+1005
-464
lines changed

packages/mdc-textfield/README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -323,21 +323,21 @@ complicated.
323323

324324
| Method Signature | Description |
325325
| --- | --- |
326-
| addClass(className: string) => void | Adds a class to the root element |
327-
| removeClass(className: string) => void | Removes a class from the root element |
328-
| addClassToLabel(className: string) => void | Adds a class to the label element. We recommend you add a conditional check here, and in `removeClassFromLabel` for whether or not the label is present so that the JS component could be used with text fields that don't require a label, such as the full-width text field. |
329-
| removeClassFromLabel(className: string) => void | Removes a class from the label element |
330-
| eventTargetHasClass(target: HTMLElement, className: string) => boolean | Returns true if classname exists for a given target element |
331-
| registerTextFieldInteractionHandler(evtType: string, handler: EventListener) => void | Registers an event handler on the root element for a given event |
332-
| deregisterTextFieldInteractionHandler(evtType: string, handler: EventListener) => void | Deregisters an event handler on the root element for a given event |
333-
| notifyIconAction() => void | Emits a custom event "MDCTextField:icon" denoting a user has clicked the icon |
334-
| registerInputInteractionHandler(evtType: string, handler: EventListener) => void | Registers an event listener on the native input element for a given event |
335-
| deregisterInputInteractionHandler(evtType: string, handler: EventListener) => void | Deregisters an event listener on the native input element for a given event |
336-
| registerBottomLineEventHandler(evtType: string, handler: EventListener) => void | Registers an event listener on the bottom line element for a given event |
337-
| deregisterBottomLineEventHandler(evtType: string, handler: EventListener) => void | Deregisters an event listener on the bottom line element for a given event |
338-
| getNativeInput() => {value: string, disabled: boolean, badInput: boolean, checkValidity: () => boolean}? | Returns an object representing the native text input element, with a similar API shape. The object returned should include the `value`, `disabled` and `badInput` properties, as well as the `checkValidity()` function. We _never_ alter the value within our code, however we _do_ update the disabled property, so if you choose to duck-type the return value for this method in your implementation it's important to keep this in mind. Also note that this method can return null, which the foundation will handle gracefully. |
339-
| getBottomLineFoundation() => MDCTextFieldBottomLineFoundation | Returns the instance of the bottom line element's foundation |
340-
| getHelperTextFoundation() => MDCTextFieldHelperTextFoundation | Returns the instance of the helper text element's foundation |
326+
| `addClass(className: string) => void` | Adds a class to the root element |
327+
| `removeClass(className: string) => void` | Removes a class from the root element |
328+
| `addClassToLabel(className: string) => void` | Adds a class to the label element. We recommend you add a conditional check here, and in `removeClassFromLabel` for whether or not the label is present so that the JS component could be used with text fields that don't require a label, such as the full-width text field. |
329+
| `removeClassFromLabel(className: string) => void` | Removes a class from the label element |
330+
| `eventTargetHasClass(target: HTMLElement, className: string) => boolean` | Returns true if classname exists for a given target element |
331+
| `registerTextFieldInteractionHandler(evtType: string, handler: EventListener) => void` | Registers an event handler on the root element for a given event |
332+
| `deregisterTextFieldInteractionHandler(evtType: string, handler: EventListener) => void` | Deregisters an event handler on the root element for a given event |
333+
| `notifyIconAction() => void` | Emits a custom event "MDCTextField:icon" denoting a user has clicked the icon |
334+
| `registerInputEventHandler(evtType: string, handler: EventListener) => void` | Registers an event listener on the input element for a given event |
335+
| `deregisterInputEventHandler(evtType: string, handler: EventListener) => void` | Deregisters an event listener on the input element for a given event |
336+
| `registerBottomLineEventHandler(evtType: string, handler: EventListener) => void` | Registers an event listener on the bottom line element for a given event |
337+
| `deregisterBottomLineEventHandler(evtType: string, handler: EventListener) => void` | Deregisters an event listener on the bottom line element for a given event |
338+
| `getBottomLineFoundation() => MDCTextFieldBottomLineFoundation` | Returns the instance of the bottom line element's foundation |
339+
| `getHelperTextFoundation() => MDCTextFieldHelperTextFoundation` | Returns the instance of the helper text element's foundation |
340+
| `getInputFoundation() => MDCTextFieldInputFoundation` | Returns the instance of the input element's foundation |
341341

342342
#### The full foundation API
343343

packages/mdc-textfield/adapter.js

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,10 @@
1818
/* eslint-disable no-unused-vars */
1919
import MDCTextFieldBottomLineFoundation from './bottom-line/foundation';
2020
import MDCTextFieldHelperTextFoundation from './helper-text/foundation';
21+
import MDCTextFieldInputFoundation from './input/foundation';
2122

2223
/* eslint no-unused-vars: [2, {"args": "none"}] */
2324

24-
/**
25-
* @typedef {{
26-
* value: string,
27-
* disabled: boolean,
28-
* badInput: boolean,
29-
* checkValidity: (function(): boolean)
30-
* }}
31-
*/
32-
let NativeInputType;
33-
3425
/**
3526
* Adapter for MDC Text Field.
3627
*
@@ -104,18 +95,20 @@ class MDCTextFieldAdapter {
10495
notifyIconAction() {}
10596

10697
/**
107-
* Registers an event listener on the native input element for a given event.
98+
99+
/**
100+
* Registers an event listener on the input element for a given event.
108101
* @param {string} evtType
109102
* @param {function(!Event): undefined} handler
110103
*/
111-
registerInputInteractionHandler(evtType, handler) {}
104+
registerInputEventHandler(evtType, handler) {}
112105

113106
/**
114-
* Deregisters an event listener on the native input element for a given event.
107+
* Deregisters an event listener on the input element for a given event.
115108
* @param {string} evtType
116109
* @param {function(!Event): undefined} handler
117110
*/
118-
deregisterInputInteractionHandler(evtType, handler) {}
111+
deregisterInputEventHandler(evtType, handler) {}
119112

120113
/**
121114
* Registers an event listener on the bottom line element for a given event.
@@ -131,18 +124,6 @@ class MDCTextFieldAdapter {
131124
*/
132125
deregisterBottomLineEventHandler(evtType, handler) {}
133126

134-
/**
135-
* Returns an object representing the native text input element, with a
136-
* similar API shape. The object returned should include the value, disabled
137-
* and badInput properties, as well as the checkValidity() function. We never
138-
* alter the value within our code, however we do update the disabled
139-
* property, so if you choose to duck-type the return value for this method
140-
* in your implementation it's important to keep this in mind. Also note that
141-
* this method can return null, which the foundation will handle gracefully.
142-
* @return {?Element|?NativeInputType}
143-
*/
144-
getNativeInput() {}
145-
146127
/**
147128
* Returns the foundation for the bottom line element. Returns undefined if
148129
* there is no bottom line element.
@@ -156,6 +137,12 @@ class MDCTextFieldAdapter {
156137
* @return {?MDCTextFieldHelperTextFoundation}
157138
*/
158139
getHelperTextFoundation() {}
140+
141+
/**
142+
* Returns the foundation for the input element.
143+
* @return {!MDCTextFieldInputFoundation}
144+
*/
145+
getInputFoundation() {}
159146
}
160147

161-
export {MDCTextFieldAdapter, NativeInputType};
148+
export {MDCTextFieldAdapter};

packages/mdc-textfield/foundation.js

Lines changed: 30 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
*/
1717

1818
import MDCFoundation from '@material/base/foundation';
19-
import {MDCTextFieldAdapter, NativeInputType} from './adapter';
19+
import {MDCTextFieldAdapter} from './adapter';
2020
import MDCTextFieldBottomLineFoundation from './bottom-line/foundation';
21+
import MDCTextFieldInputFoundation from './input/foundation';
2122
import {cssClasses, strings} from './constants';
2223

2324

@@ -52,13 +53,13 @@ class MDCTextFieldFoundation extends MDCFoundation {
5253
registerTextFieldInteractionHandler: () => {},
5354
deregisterTextFieldInteractionHandler: () => {},
5455
notifyIconAction: () => {},
55-
registerInputInteractionHandler: () => {},
56-
deregisterInputInteractionHandler: () => {},
56+
registerInputEventHandler: () => {},
57+
deregisterInputEventHandler: () => {},
5758
registerBottomLineEventHandler: () => {},
5859
deregisterBottomLineEventHandler: () => {},
59-
getNativeInput: () => {},
6060
getBottomLineFoundation: () => {},
6161
getHelperTextFoundation: () => {},
62+
getInputFoundation: () => {},
6263
});
6364
}
6465

@@ -71,15 +72,11 @@ class MDCTextFieldFoundation extends MDCFoundation {
7172
/** @private {boolean} */
7273
this.isFocused_ = false;
7374
/** @private {boolean} */
74-
this.receivedUserInput_ = false;
75-
/** @private {boolean} */
7675
this.useCustomValidityChecking_ = false;
7776
/** @private {function(): undefined} */
7877
this.inputFocusHandler_ = () => this.activateFocus();
7978
/** @private {function(): undefined} */
8079
this.inputBlurHandler_ = () => this.deactivateFocus();
81-
/** @private {function(): undefined} */
82-
this.inputInputHandler_ = () => this.autoCompleteFocus();
8380
/** @private {function(!Event): undefined} */
8481
this.setPointerXOffset_ = (evt) => this.setBottomLineTransformOrigin(evt);
8582
/** @private {function(!Event): undefined} */
@@ -91,16 +88,17 @@ class MDCTextFieldFoundation extends MDCFoundation {
9188
init() {
9289
this.adapter_.addClass(MDCTextFieldFoundation.cssClasses.UPGRADED);
9390
// Ensure label does not collide with any pre-filled value.
94-
if (this.getNativeInput_().value) {
91+
const input = this.adapter_.getInputFoundation();
92+
if (input.getValue()) {
9593
this.adapter_.addClassToLabel(MDCTextFieldFoundation.cssClasses.LABEL_FLOAT_ABOVE);
9694
}
9795

98-
this.adapter_.registerInputInteractionHandler('focus', this.inputFocusHandler_);
99-
this.adapter_.registerInputInteractionHandler('blur', this.inputBlurHandler_);
100-
this.adapter_.registerInputInteractionHandler('input', this.inputInputHandler_);
101-
['mousedown', 'touchstart'].forEach((evtType) => {
102-
this.adapter_.registerInputInteractionHandler(evtType, this.setPointerXOffset_);
103-
});
96+
this.adapter_.registerInputEventHandler(
97+
MDCTextFieldInputFoundation.strings.FOCUS_EVENT, this.inputFocusHandler_);
98+
this.adapter_.registerInputEventHandler(
99+
MDCTextFieldInputFoundation.strings.BLUR_EVENT, this.inputBlurHandler_);
100+
this.adapter_.registerInputEventHandler(
101+
MDCTextFieldInputFoundation.strings.PRESSED_EVENT, this.setPointerXOffset_);
104102
['click', 'keydown'].forEach((evtType) => {
105103
this.adapter_.registerTextFieldInteractionHandler(evtType, this.textFieldInteractionHandler_);
106104
});
@@ -110,12 +108,12 @@ class MDCTextFieldFoundation extends MDCFoundation {
110108

111109
destroy() {
112110
this.adapter_.removeClass(MDCTextFieldFoundation.cssClasses.UPGRADED);
113-
this.adapter_.deregisterInputInteractionHandler('focus', this.inputFocusHandler_);
114-
this.adapter_.deregisterInputInteractionHandler('blur', this.inputBlurHandler_);
115-
this.adapter_.deregisterInputInteractionHandler('input', this.inputInputHandler_);
116-
['mousedown', 'touchstart'].forEach((evtType) => {
117-
this.adapter_.deregisterInputInteractionHandler(evtType, this.setPointerXOffset_);
118-
});
111+
this.adapter_.deregisterInputEventHandler(
112+
MDCTextFieldInputFoundation.strings.FOCUS_EVENT, this.inputFocusHandler_);
113+
this.adapter_.deregisterInputEventHandler(
114+
MDCTextFieldInputFoundation.strings.BLUR_EVENT, this.inputBlurHandler_);
115+
this.adapter_.deregisterInputEventHandler(
116+
MDCTextFieldInputFoundation.strings.PRESSED_EVENT, this.setPointerXOffset_);
119117
['click', 'keydown'].forEach((evtType) => {
120118
this.adapter_.deregisterTextFieldInteractionHandler(evtType, this.textFieldInteractionHandler_);
121119
});
@@ -128,11 +126,11 @@ class MDCTextFieldFoundation extends MDCFoundation {
128126
* @param {!Event} evt
129127
*/
130128
handleTextFieldInteraction(evt) {
131-
if (this.adapter_.getNativeInput().disabled) {
129+
if (this.isDisabled()) {
132130
return;
133131
}
134-
135-
this.receivedUserInput_ = true;
132+
const input = this.adapter_.getInputFoundation();
133+
input.handleTextFieldInteraction();
136134

137135
const {target, type} = evt;
138136
const {TEXT_FIELD_ICON} = MDCTextFieldFoundation.cssClasses;
@@ -175,16 +173,6 @@ class MDCTextFieldFoundation extends MDCFoundation {
175173
}
176174
}
177175

178-
/**
179-
* Activates the Text Field's focus state in cases when the input value
180-
* changes without user input (e.g. programatically).
181-
*/
182-
autoCompleteFocus() {
183-
if (!this.receivedUserInput_) {
184-
this.activateFocus();
185-
}
186-
}
187-
188176
/**
189177
* Handles when bottom line animation ends, performing actions that must wait
190178
* for animations to finish.
@@ -204,15 +192,14 @@ class MDCTextFieldFoundation extends MDCFoundation {
204192
*/
205193
deactivateFocus() {
206194
const {FOCUSED, LABEL_FLOAT_ABOVE, LABEL_SHAKE} = MDCTextFieldFoundation.cssClasses;
207-
const input = this.getNativeInput_();
195+
const input = this.adapter_.getInputFoundation();
208196

209197
this.isFocused_ = false;
210198
this.adapter_.removeClass(FOCUSED);
211199
this.adapter_.removeClassFromLabel(LABEL_SHAKE);
212200

213-
if (!input.value && !this.isBadInput_()) {
201+
if (!input.getValue() && !this.isBadInput_()) {
214202
this.adapter_.removeClassFromLabel(LABEL_FLOAT_ABOVE);
215-
this.receivedUserInput_ = false;
216203
}
217204

218205
if (!this.useCustomValidityChecking_) {
@@ -244,23 +231,25 @@ class MDCTextFieldFoundation extends MDCFoundation {
244231
* @private
245232
*/
246233
isBadInput_() {
247-
const input = this.getNativeInput_();
248-
return input.validity ? input.validity.badInput : input.badInput;
234+
const input = this.adapter_.getInputFoundation();
235+
return input.isBadInput();
249236
}
250237

251238
/**
252239
* @return {boolean} True if the Text Field is disabled.
253240
*/
254241
isDisabled() {
255-
return this.getNativeInput_().disabled;
242+
const input = this.adapter_.getInputFoundation();
243+
return input.isDisabled();
256244
}
257245

258246
/**
259247
* @param {boolean} disabled Sets the text-field disabled or enabled.
260248
*/
261249
setDisabled(disabled) {
262250
const {DISABLED, INVALID} = MDCTextFieldFoundation.cssClasses;
263-
this.getNativeInput_().disabled = disabled;
251+
const input = this.adapter_.getInputFoundation();
252+
input.setDisabled(disabled);
264253
if (disabled) {
265254
this.adapter_.addClass(DISABLED);
266255
this.adapter_.removeClass(INVALID);
@@ -281,21 +270,6 @@ class MDCTextFieldFoundation extends MDCFoundation {
281270
}
282271
}
283272

284-
/**
285-
* @return {!Element|!NativeInputType} The native text input from the
286-
* host environment, or a dummy if none exists.
287-
* @private
288-
*/
289-
getNativeInput_() {
290-
return this.adapter_.getNativeInput() ||
291-
/** @type {!NativeInputType} */ ({
292-
checkValidity: () => true,
293-
value: '',
294-
disabled: false,
295-
badInput: false,
296-
});
297-
}
298-
299273
/**
300274
* @param {boolean} isValid Sets the validity state of the Text Field.
301275
*/

0 commit comments

Comments
 (0)