@aurelia-mdc-web/all 9.3.0-au2 → 9.3.1-au2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/chips/mdc-chip/mdc-chip.js.map +1 -1
  2. package/dist/data-table/mdc-data-table.js.map +1 -1
  3. package/dist/dialog/mdc-dialog-service.js.map +1 -1
  4. package/dist/expandable/mdc-expandable.js.map +1 -1
  5. package/dist/form-field/mdc-form-field.js.map +1 -1
  6. package/dist/list/mdc-deprecated-list/mdc-deprecated-list-item/mdc-deprecated-list-item.js.map +1 -1
  7. package/dist/list/mdc-list-item/mdc-list-item.js.map +1 -1
  8. package/dist/menu/mdc-menu.js.map +1 -1
  9. package/dist/segmented-button/mdc-segmented-button-segment/mdc-segmented-button-segment.js.map +1 -1
  10. package/dist/select/mdc-select-value-observer.js.map +1 -1
  11. package/dist/select/mdc-select.js.map +1 -1
  12. package/dist/text-field/mdc-text-field.js.map +1 -1
  13. package/dist/tree-view/mdc-tree-view.js.map +1 -1
  14. package/package.json +2 -2
  15. package/src/chips/mdc-chip/mdc-chip.ts +290 -290
  16. package/src/data-table/mdc-data-table.ts +432 -432
  17. package/src/dialog/mdc-dialog-service.ts +80 -80
  18. package/src/expandable/mdc-expandable.ts +104 -104
  19. package/src/form-field/mdc-form-field.ts +60 -60
  20. package/src/list/mdc-deprecated-list/mdc-deprecated-list-item/mdc-deprecated-list-item.ts +108 -108
  21. package/src/list/mdc-list-item/mdc-list-item.ts +136 -136
  22. package/src/menu/mdc-menu.ts +340 -340
  23. package/src/segmented-button/mdc-segmented-button-segment/mdc-segmented-button-segment.ts +170 -170
  24. package/src/select/mdc-select-value-observer.ts +346 -346
  25. package/src/select/mdc-select.ts +480 -480
  26. package/src/text-field/mdc-text-field.ts +535 -535
  27. package/src/tree-view/mdc-tree-view.ts +147 -147
@@ -1,535 +1,535 @@
1
- import { inject, customElement, INode, bindable } from 'aurelia';
2
- import {
3
- MDCTextFieldFoundation, MDCTextFieldRootAdapter, MDCTextFieldInputAdapter, MDCTextFieldLabelAdapter, MDCTextFieldAdapter, MDCTextFieldFoundationMap,
4
- MDCTextFieldLineRippleAdapter, MDCTextFieldOutlineAdapter, cssClasses, helperTextStrings, characterCountStrings
5
- } from '@material/textfield';
6
- import { applyPassive } from '@material/dom/events';
7
- import { MdcComponent, IValidatedElement, IError, booleanAttr, number } from '../base';
8
- import { MdcTextFieldIcon, mdcIconStrings, IMdcTextFieldIconElement } from './mdc-text-field-icon';
9
- import { MdcTextFieldHelperText } from './mdc-text-field-helper-text/mdc-text-field-helper-text';
10
- import { MdcTextFieldCharacterCounter } from './mdc-text-field-character-counter';
11
- import { MdcTextFieldHelperLine } from './mdc-text-field-helper-line/mdc-text-field-helper-line';
12
- import { processContent, IPlatform, CustomAttribute, CustomElement } from '@aurelia/runtime-html';
13
- import { MdcDefaultTextFieldConfiguration } from './mdc-default-text-field-configuration';
14
- import template from './mdc-text-field.html?raw';
15
- import { MdcFloatingLabel } from '../floating-label/mdc-floating-label';
16
- import { MdcLineRipple } from '../line-ripple/mdc-line-ripple';
17
- import { MdcNotchedOutline } from '../notched-outline/mdc-notched-outline';
18
-
19
- let textFieldId = 0;
20
- const leadingIconSelector = '.mdc-text-field__icon--leading';
21
- const trailingIconSelector = '.mdc-text-field__icon--trailing';
22
-
23
- @inject(Element, IPlatform, MdcDefaultTextFieldConfiguration)
24
- @customElement({ name: 'mdc-text-field', template })
25
- @processContent(function processContent(node: INode) {
26
- const element = node as HTMLElement;
27
- // move icons to slots - this allows omitting slot specification
28
- const leadingIcon = element.querySelector(`[${mdcIconStrings.ATTRIBUTE}][${mdcIconStrings.LEADING}]`);
29
- leadingIcon?.setAttribute('au-slot', 'leading-icon');
30
- const trailingIcon = element.querySelector(`[${mdcIconStrings.ATTRIBUTE}][${mdcIconStrings.TRAILING}]`);
31
- trailingIcon?.setAttribute('au-slot', 'trailing-icon');
32
- }
33
- )
34
- export class MdcTextField extends MdcComponent<MDCTextFieldFoundation> {
35
- constructor(root: HTMLElement, private platform: IPlatform, private defaultConfiguration: MdcDefaultTextFieldConfiguration) {
36
- super(root);
37
- this.outlined = this.defaultConfiguration.outlined;
38
- defineMdcTextFieldElementApis(this.root);
39
- }
40
-
41
- id: string = `mdc-text-field-${++textFieldId}`;
42
- id1: string = `mdc-text-field-${textFieldId}`;
43
- input_: HTMLInputElement;
44
- label_?: MdcFloatingLabel = undefined;
45
- lineRipple_: MdcLineRipple;
46
- outline_!: MdcNotchedOutline | null; // assigned in html
47
- helperText_: MdcTextFieldHelperText | undefined;
48
- characterCounter_?: MdcTextFieldCharacterCounter;
49
- errors = new Map<IError, boolean>();
50
- leadingIcon_: MdcTextFieldIcon | undefined;
51
- trailingIcon_: MdcTextFieldIcon | undefined;
52
- mutationObserver = new MutationObserver(mutations => this.mutated(mutations));
53
-
54
- @bindable()
55
- label: string;
56
- labelChanged() {
57
- this.platform.domQueue.queueTask(() => {
58
- if (this.foundation) {
59
- const openNotch = this.foundation.shouldFloat;
60
- this.foundation.notchOutline(openNotch);
61
- }
62
- });
63
- }
64
-
65
- @bindable({ set: booleanAttr })
66
- textarea: boolean;
67
-
68
- @bindable({ set: booleanAttr })
69
- endAligned: boolean;
70
-
71
- @bindable({ set: booleanAttr })
72
- ltrText: boolean;
73
-
74
- @bindable({ set: booleanAttr })
75
- outlined?: boolean;
76
-
77
- @bindable()
78
- prefix: string;
79
-
80
- @bindable()
81
- suffix: string;
82
-
83
- @bindable({ set: booleanAttr })
84
- required?: boolean;
85
- requiredChanged() {
86
- if (this.required !== undefined) {
87
- this.input_.required = this.required;
88
- this.foundation?.setUseNativeValidation(true);
89
- }
90
- }
91
-
92
- @bindable({ set: booleanAttr })
93
- disabled: boolean;
94
- disabledChanged() {
95
- this.input_.disabled = this.disabled;
96
- this.foundation?.setDisabled(this.disabled);
97
- }
98
-
99
- @bindable({ set: booleanAttr })
100
- readonly: boolean;
101
- readonlyChanged() {
102
- this.input_.readOnly = this.readonly;
103
- }
104
-
105
- /** Makes the element blur on Enter key press */
106
- @bindable({ set: booleanAttr })
107
- blurOnEnter: boolean;
108
-
109
- @bindable()
110
- maxlength: string;
111
- maxlengthChanged() {
112
- if (this.maxlength) {
113
- this.input_.setAttribute('maxlength', this.maxlength);
114
- } else {
115
- this.input_.removeAttribute('maxlength');
116
- }
117
- }
118
-
119
- @bindable()
120
- rows: string;
121
- rowsChanged() {
122
- if (this.rows) {
123
- this.input_.setAttribute('rows', this.rows);
124
- } else {
125
- this.input_.removeAttribute('rows');
126
- }
127
- }
128
-
129
- @bindable()
130
- cols: string;
131
- colsChanged() {
132
- if (this.rows) {
133
- this.input_.setAttribute('cols', this.cols);
134
- } else {
135
- this.input_.removeAttribute('cols');
136
- }
137
- }
138
-
139
- @bindable()
140
- max: string;
141
- maxChanged() {
142
- if (this.max === undefined) {
143
- this.input_.removeAttribute('max');
144
- } else {
145
- this.input_.max = this.max;
146
- }
147
- }
148
-
149
- @bindable()
150
- min: string;
151
- minChanged() {
152
- if (this.min === undefined) {
153
- this.input_.removeAttribute('min');
154
- } else {
155
- this.input_.min = this.min;
156
- }
157
- }
158
-
159
- @bindable()
160
- step: string;
161
- stepChanged() {
162
- if (this.step === undefined) {
163
- this.input_.removeAttribute('step');
164
- } else {
165
- this.input_.step = this.step;
166
- }
167
- }
168
-
169
- @bindable()
170
- autocomplete: AutoFill;
171
- autocompleteChanged() {
172
- if (this.autocomplete === undefined) {
173
- this.input_.removeAttribute('autocomplete');
174
- } else {
175
- this.input_.autocomplete = this.autocomplete;
176
- }
177
- }
178
-
179
- @bindable({ set: number })
180
- tabindex: number;
181
- tabindexChanged() {
182
- if (isNaN(this.tabindex)) {
183
- this.input_.removeAttribute('tabindex');
184
- } else {
185
- this.input_.tabIndex = this.tabindex;
186
- }
187
- }
188
-
189
- @bindable()
190
- type: string;
191
- typeChanged() {
192
- if (!this.textarea) {
193
- if (this.type === undefined) {
194
- this.input_.removeAttribute('type');
195
- } else {
196
- this.input_.type = this.type;
197
- }
198
- }
199
- }
200
-
201
- @bindable()
202
- name: string;
203
- nameChanged() {
204
- if (this.name === undefined) {
205
- this.input_.removeAttribute('name');
206
- } else {
207
- this.input_.name = this.name;
208
- }
209
- }
210
-
211
- @bindable()
212
- placeholder: string = ' '; // non empty placeholder solves the issue of misplaced labels in Safari
213
-
214
- private initialValue: string;
215
- get value(): string {
216
- if (this.foundation) {
217
- return this.foundation.getValue();
218
- } else {
219
- return this.initialValue;
220
- }
221
- }
222
- set value(value: string) {
223
- if (this.foundation) {
224
- if (this.foundation.getValue() !== value) {
225
- this.foundation.setValue(value === null || value === undefined ? '' : value.toString());
226
- }
227
- } else {
228
- this.initialValue = value;
229
- }
230
- }
231
-
232
- addError(error: IError) {
233
- this.errors.set(error, true);
234
- this.valid = false;
235
- }
236
-
237
- removeError(error: IError) {
238
- this.errors.delete(error);
239
- this.valid = this.errors.size === 0;
240
- }
241
-
242
- get valid(): boolean {
243
- return this.foundation?.isValid() ?? true;
244
- }
245
-
246
- set valid(value: boolean) {
247
- this.foundation?.setUseNativeValidation(false);
248
- this.foundation?.setValid(value);
249
- }
250
-
251
- renderErrors() {
252
- const helperLine = this.root.nextElementSibling;
253
- if (helperLine?.tagName === 'MDC-TEXT-FIELD-HELPER-LINE') {
254
- CustomElement.for<MdcTextFieldHelperLine>(helperLine).viewModel.errors = Array.from(this.errors.keys())
255
- .filter(x => x.message !== null).map(x => x.message!);
256
- }
257
- }
258
-
259
- async attaching() {
260
- const nextSibling = this.root.nextElementSibling;
261
- if (nextSibling?.tagName === cssClasses.HELPER_LINE.toUpperCase()) {
262
- await CustomElement.for<MdcTextFieldHelperLine>(nextSibling).viewModel.attachedPromise;
263
- const helperTextEl = nextSibling.querySelector(helperTextStrings.ROOT_SELECTOR);
264
- this.helperText_ = helperTextEl ? CustomElement.for<MdcTextFieldHelperText>(nextSibling).viewModel : undefined;
265
- const characterCounterEl = nextSibling.querySelector(characterCountStrings.ROOT_SELECTOR);
266
- this.characterCounter_ = characterCounterEl ? CustomElement.for<MdcTextFieldCharacterCounter>(characterCounterEl).viewModel : undefined;
267
- }
268
- }
269
-
270
- beforeFoundationCreated() {
271
- this.maxlengthChanged();
272
- this.typeChanged();
273
- this.mutationObserver.observe(this.root, { subtree: true, childList: true });
274
- this.leadingIconChanged();
275
- this.trailingIconChanged();
276
- }
277
-
278
- mutated(mutations: MutationRecord[]) {
279
- if (mutations.find(x => [...Array.from(x.addedNodes), ...Array.from(x.removedNodes)].find(y => y instanceof HTMLElement && y.matches(leadingIconSelector)))) {
280
- this.leadingIconChanged();
281
- }
282
- if (mutations.find(x => [...Array.from(x.addedNodes), ...Array.from(x.removedNodes)].find(y => y instanceof HTMLElement && y.matches(trailingIconSelector)))) {
283
- this.trailingIconChanged();
284
- }
285
- }
286
-
287
- trailingIconChanged() {
288
- const el = this.root.querySelector<IMdcTextFieldIconElement>(trailingIconSelector);
289
- this.trailingIcon_ = el ? CustomAttribute.for<MdcTextFieldIcon>(el, mdcIconStrings.ATTRIBUTE)?.viewModel : undefined;
290
- }
291
-
292
- leadingIconChanged() {
293
- const el = this.root.querySelector<IMdcTextFieldIconElement>(leadingIconSelector);
294
- this.leadingIcon_ = el ? CustomAttribute.for<MdcTextFieldIcon>(el, mdcIconStrings.ATTRIBUTE)?.viewModel : undefined;
295
- }
296
-
297
- override destroy() {
298
- this.mutationObserver.disconnect();
299
- }
300
-
301
- initialSyncWithDOM() {
302
- this.value = this.initialValue;
303
- this.errors = new Map<IError, boolean>();
304
- this.valid = true;
305
-
306
- this.requiredChanged();
307
- this.disabledChanged();
308
- this.readonlyChanged();
309
- this.tabindexChanged();
310
- this.rowsChanged();
311
- this.colsChanged();
312
- this.minChanged();
313
- this.maxChanged();
314
- this.stepChanged();
315
- this.autocompleteChanged();
316
- this.nameChanged();
317
- // handle the case when attribute value was set, not bound, in html
318
- if (this.root.hasAttribute('value')) {
319
- this.value = this.root.getAttribute('value') ?? '';
320
- }
321
- }
322
-
323
- getDefaultFoundation() {
324
- const adapter: Partial<MDCTextFieldAdapter> = {
325
- ...this.getRootAdapterMethods_(),
326
- ...this.getInputAdapterMethods_(),
327
- ...this.getLabelAdapterMethods_(),
328
- ...this.getLineRippleAdapterMethods_(),
329
- ...this.getOutlineAdapterMethods_(),
330
- };
331
- return new MDCTextFieldFoundation(adapter, this.getFoundationMap_());
332
- }
333
-
334
- private getRootAdapterMethods_(): MDCTextFieldRootAdapter {
335
- return {
336
- addClass: (className) => this.root.classList.add(className),
337
- removeClass: (className) => this.root.classList.remove(className),
338
- hasClass: (className) => this.root.classList.contains(className),
339
- registerTextFieldInteractionHandler: (evtType, handler) => this.listen(evtType, handler),
340
- deregisterTextFieldInteractionHandler: (evtType, handler) => this.unlisten(evtType, handler),
341
- registerValidationAttributeChangeHandler: (handler) => {
342
- const getAttributesList = (mutationsList: MutationRecord[]): string[] => {
343
- return mutationsList
344
- .map((mutation) => mutation.attributeName)
345
- .filter((attributeName) => attributeName) as string[];
346
- };
347
- const observer = new MutationObserver((mutationsList) => handler(getAttributesList(mutationsList)));
348
- const config = { attributes: true };
349
- observer.observe(this.input_, config);
350
- return observer;
351
- },
352
- deregisterValidationAttributeChangeHandler: (observer) => observer.disconnect(),
353
- };
354
- }
355
-
356
- private getInputAdapterMethods_(): MDCTextFieldInputAdapter {
357
- return {
358
- getNativeInput: () => this.input_,
359
- setInputAttr: (attr, value) => {
360
- this.input_.setAttribute(attr, value);
361
- },
362
- removeInputAttr: (attr) => {
363
- this.input_.removeAttribute(attr);
364
- },
365
- isFocused: () => document.activeElement === this.input_,
366
- registerInputInteractionHandler: (evtType, handler) => this.input_.addEventListener(evtType, handler, applyPassive()),
367
- deregisterInputInteractionHandler: (evtType, handler) => this.input_?.removeEventListener(evtType, handler, applyPassive()),
368
- };
369
- }
370
-
371
- private getLabelAdapterMethods_(): MDCTextFieldLabelAdapter {
372
- return {
373
- floatLabel: (shouldFloat) => this.label_?.float(shouldFloat),
374
- getLabelWidth: () => this.label_ ? this.label_.getWidth() : 0,
375
- hasLabel: () => Boolean(this.label_),
376
- shakeLabel: (shouldShake) => this.label_?.shake(shouldShake),
377
- setLabelRequired: (isRequired) => this.label_?.setRequired(isRequired),
378
- };
379
- }
380
-
381
- private getLineRippleAdapterMethods_(): MDCTextFieldLineRippleAdapter {
382
- return {
383
- activateLineRipple: () => this.lineRipple_?.activate(),
384
- deactivateLineRipple: () => this.lineRipple_?.deactivate(),
385
- setLineRippleTransformOrigin: (normalizedX) => this.lineRipple_?.setRippleCenter(normalizedX)
386
- };
387
- }
388
-
389
- private getOutlineAdapterMethods_(): MDCTextFieldOutlineAdapter {
390
- return {
391
- closeOutline: () => this.outline_?.closeNotch(),
392
- hasOutline: () => Boolean(this.outline_),
393
- notchOutline: (labelWidth) => this.outline_?.notch(labelWidth),
394
- };
395
- }
396
-
397
- /**
398
- * @return A map of all subcomponents to subfoundations.
399
- */
400
- private getFoundationMap_(): Partial<MDCTextFieldFoundationMap> {
401
- return {
402
- characterCounter: this.characterCounter_ ? this.characterCounter_?.foundationForTextField : undefined,
403
- helperText: this.helperText_ ? this.helperText_.foundationForTextField : undefined,
404
- leadingIcon: this.leadingIcon_ ? this.leadingIcon_.foundationForTextField : undefined,
405
- trailingIcon: this.trailingIcon_ ? this.trailingIcon_.foundationForTextField : undefined,
406
- };
407
- }
408
-
409
- onInput(evt: Event): void {
410
- const value = (evt.target as HTMLInputElement).value;
411
- this.value = value;
412
- }
413
-
414
- onFocus() {
415
- this.foundation?.activateFocus();
416
- this.emit('focus', {}, true);
417
- }
418
-
419
- onChange(evt: Event): void {
420
- const value = (evt.target as HTMLInputElement).value;
421
- this.value = value;
422
- }
423
-
424
- onBlur(): void {
425
- this.foundation?.deactivateFocus();
426
- this.emit('blur', {}, true);
427
- }
428
-
429
- focus() {
430
- this.input_.focus();
431
- }
432
-
433
- blur() {
434
- this.input_.blur();
435
- }
436
-
437
- onKeyup(e: KeyboardEvent) {
438
- if (this.blurOnEnter && e.keyCode === 13) {
439
- this.blur();
440
- }
441
- return true;
442
- }
443
- }
444
-
445
- /** @hidden */
446
- export interface IMdcTextFieldElement extends IValidatedElement {
447
- $au: {
448
- 'au:resource:custom-element': {
449
- viewModel: MdcTextField;
450
- };
451
- };
452
- value: string;
453
- }
454
-
455
- function defineMdcTextFieldElementApis(element: HTMLElement) {
456
- Object.defineProperties(element, {
457
- tagName: {
458
- get() {
459
- return 'MDC-TEXT-FIELD';
460
- }
461
- },
462
- value: {
463
- get(this: IMdcTextFieldElement) {
464
- return CustomElement.for<MdcTextField>(this).viewModel.value;
465
- },
466
- set(this: IMdcTextFieldElement, value: string) {
467
- CustomElement.for<MdcTextField>(this).viewModel.value = value;
468
- },
469
- configurable: true
470
- },
471
- disabled: {
472
- get(this: IMdcTextFieldElement) {
473
- return CustomElement.for<MdcTextField>(this).viewModel.disabled;
474
- },
475
- set(this: IMdcTextFieldElement, value: boolean) {
476
- CustomElement.for<MdcTextField>(this).viewModel.disabled = value;
477
- },
478
- configurable: true
479
- },
480
- readOnly: {
481
- get(this: IMdcTextFieldElement) {
482
- return CustomElement.for<MdcTextField>(this).viewModel.readonly;
483
- },
484
- set(this: IMdcTextFieldElement, value: boolean) {
485
- CustomElement.for<MdcTextField>(this).viewModel.readonly = value;
486
- },
487
- configurable: true
488
- },
489
- valid: {
490
- get(this: IMdcTextFieldElement) {
491
- return CustomElement.for<MdcTextField>(this).viewModel.valid;
492
- },
493
- set(this: IMdcTextFieldElement, value: boolean) {
494
- CustomElement.for<MdcTextField>(this).viewModel.valid = value;
495
- },
496
- configurable: true
497
- },
498
- addError: {
499
- value(this: IMdcTextFieldElement, error: IError) {
500
- CustomElement.for<MdcTextField>(this).viewModel.addError(error);
501
- },
502
- configurable: true
503
- },
504
- removeError: {
505
- value(this: IMdcTextFieldElement, error: IError) {
506
- CustomElement.for<MdcTextField>(this).viewModel.removeError(error);
507
- },
508
- configurable: true
509
- },
510
- renderErrors: {
511
- value(this: IMdcTextFieldElement): void {
512
- CustomElement.for<MdcTextField>(this).viewModel.renderErrors();
513
- },
514
- configurable: true
515
- },
516
- focus: {
517
- value(this: IMdcTextFieldElement) {
518
- CustomElement.for<MdcTextField>(this).viewModel.focus();
519
- },
520
- configurable: true
521
- },
522
- blur: {
523
- value(this: IMdcTextFieldElement) {
524
- CustomElement.for<MdcTextField>(this).viewModel.blur();
525
- },
526
- configurable: true
527
- },
528
- isFocused: {
529
- get(this: IMdcTextFieldElement) {
530
- return document.activeElement === CustomElement.for<MdcTextField>(this).viewModel.input_;
531
- },
532
- configurable: true
533
- }
534
- });
535
- }
1
+ import { inject, customElement, INode, bindable } from 'aurelia';
2
+ import {
3
+ MDCTextFieldFoundation, MDCTextFieldRootAdapter, MDCTextFieldInputAdapter, MDCTextFieldLabelAdapter, MDCTextFieldAdapter, MDCTextFieldFoundationMap,
4
+ MDCTextFieldLineRippleAdapter, MDCTextFieldOutlineAdapter, cssClasses, helperTextStrings, characterCountStrings
5
+ } from '@material/textfield';
6
+ import { applyPassive } from '@material/dom/events';
7
+ import { MdcComponent, IValidatedElement, IError, booleanAttr, number } from '../base';
8
+ import { MdcTextFieldIcon, mdcIconStrings, IMdcTextFieldIconElement } from './mdc-text-field-icon';
9
+ import { MdcTextFieldHelperText } from './mdc-text-field-helper-text/mdc-text-field-helper-text';
10
+ import { MdcTextFieldCharacterCounter } from './mdc-text-field-character-counter';
11
+ import { MdcTextFieldHelperLine } from './mdc-text-field-helper-line/mdc-text-field-helper-line';
12
+ import { processContent, IPlatform, CustomAttribute, CustomElement } from '@aurelia/runtime-html';
13
+ import { MdcDefaultTextFieldConfiguration } from './mdc-default-text-field-configuration';
14
+ import template from './mdc-text-field.html?raw';
15
+ import { MdcFloatingLabel } from '../floating-label/mdc-floating-label';
16
+ import { MdcLineRipple } from '../line-ripple/mdc-line-ripple';
17
+ import { MdcNotchedOutline } from '../notched-outline/mdc-notched-outline';
18
+
19
+ let textFieldId = 0;
20
+ const leadingIconSelector = '.mdc-text-field__icon--leading';
21
+ const trailingIconSelector = '.mdc-text-field__icon--trailing';
22
+
23
+ @inject(Element, IPlatform, MdcDefaultTextFieldConfiguration)
24
+ @customElement({ name: 'mdc-text-field', template })
25
+ @processContent(function processContent(node: INode) {
26
+ const element = node as HTMLElement;
27
+ // move icons to slots - this allows omitting slot specification
28
+ const leadingIcon = element.querySelector(`[${mdcIconStrings.ATTRIBUTE}][${mdcIconStrings.LEADING}]`);
29
+ leadingIcon?.setAttribute('au-slot', 'leading-icon');
30
+ const trailingIcon = element.querySelector(`[${mdcIconStrings.ATTRIBUTE}][${mdcIconStrings.TRAILING}]`);
31
+ trailingIcon?.setAttribute('au-slot', 'trailing-icon');
32
+ }
33
+ )
34
+ export class MdcTextField extends MdcComponent<MDCTextFieldFoundation> {
35
+ constructor(root: HTMLElement, private platform: IPlatform, private defaultConfiguration: MdcDefaultTextFieldConfiguration) {
36
+ super(root);
37
+ this.outlined = this.defaultConfiguration.outlined;
38
+ defineMdcTextFieldElementApis(this.root);
39
+ }
40
+
41
+ id: string = `mdc-text-field-${++textFieldId}`;
42
+ id1: string = `mdc-text-field-${textFieldId}`;
43
+ input_: HTMLInputElement;
44
+ label_?: MdcFloatingLabel = undefined;
45
+ lineRipple_: MdcLineRipple;
46
+ outline_!: MdcNotchedOutline | null; // assigned in html
47
+ helperText_: MdcTextFieldHelperText | undefined;
48
+ characterCounter_?: MdcTextFieldCharacterCounter;
49
+ errors = new Map<IError, boolean>();
50
+ leadingIcon_: MdcTextFieldIcon | undefined;
51
+ trailingIcon_: MdcTextFieldIcon | undefined;
52
+ mutationObserver = new MutationObserver(mutations => this.mutated(mutations));
53
+
54
+ @bindable()
55
+ label: string;
56
+ labelChanged() {
57
+ this.platform.domQueue.queueTask(() => {
58
+ if (this.foundation) {
59
+ const openNotch = this.foundation.shouldFloat;
60
+ this.foundation.notchOutline(openNotch);
61
+ }
62
+ });
63
+ }
64
+
65
+ @bindable({ set: booleanAttr })
66
+ textarea: boolean;
67
+
68
+ @bindable({ set: booleanAttr })
69
+ endAligned: boolean;
70
+
71
+ @bindable({ set: booleanAttr })
72
+ ltrText: boolean;
73
+
74
+ @bindable({ set: booleanAttr })
75
+ outlined?: boolean;
76
+
77
+ @bindable()
78
+ prefix: string;
79
+
80
+ @bindable()
81
+ suffix: string;
82
+
83
+ @bindable({ set: booleanAttr })
84
+ required?: boolean;
85
+ requiredChanged() {
86
+ if (this.required !== undefined) {
87
+ this.input_.required = this.required;
88
+ this.foundation?.setUseNativeValidation(true);
89
+ }
90
+ }
91
+
92
+ @bindable({ set: booleanAttr })
93
+ disabled: boolean;
94
+ disabledChanged() {
95
+ this.input_.disabled = this.disabled;
96
+ this.foundation?.setDisabled(this.disabled);
97
+ }
98
+
99
+ @bindable({ set: booleanAttr })
100
+ readonly: boolean;
101
+ readonlyChanged() {
102
+ this.input_.readOnly = this.readonly;
103
+ }
104
+
105
+ /** Makes the element blur on Enter key press */
106
+ @bindable({ set: booleanAttr })
107
+ blurOnEnter: boolean;
108
+
109
+ @bindable()
110
+ maxlength: string;
111
+ maxlengthChanged() {
112
+ if (this.maxlength) {
113
+ this.input_.setAttribute('maxlength', this.maxlength);
114
+ } else {
115
+ this.input_.removeAttribute('maxlength');
116
+ }
117
+ }
118
+
119
+ @bindable()
120
+ rows: string;
121
+ rowsChanged() {
122
+ if (this.rows) {
123
+ this.input_.setAttribute('rows', this.rows);
124
+ } else {
125
+ this.input_.removeAttribute('rows');
126
+ }
127
+ }
128
+
129
+ @bindable()
130
+ cols: string;
131
+ colsChanged() {
132
+ if (this.rows) {
133
+ this.input_.setAttribute('cols', this.cols);
134
+ } else {
135
+ this.input_.removeAttribute('cols');
136
+ }
137
+ }
138
+
139
+ @bindable()
140
+ max: string;
141
+ maxChanged() {
142
+ if (this.max === undefined) {
143
+ this.input_.removeAttribute('max');
144
+ } else {
145
+ this.input_.max = this.max;
146
+ }
147
+ }
148
+
149
+ @bindable()
150
+ min: string;
151
+ minChanged() {
152
+ if (this.min === undefined) {
153
+ this.input_.removeAttribute('min');
154
+ } else {
155
+ this.input_.min = this.min;
156
+ }
157
+ }
158
+
159
+ @bindable()
160
+ step: string;
161
+ stepChanged() {
162
+ if (this.step === undefined) {
163
+ this.input_.removeAttribute('step');
164
+ } else {
165
+ this.input_.step = this.step;
166
+ }
167
+ }
168
+
169
+ @bindable()
170
+ autocomplete: AutoFill;
171
+ autocompleteChanged() {
172
+ if (this.autocomplete === undefined) {
173
+ this.input_.removeAttribute('autocomplete');
174
+ } else {
175
+ this.input_.autocomplete = this.autocomplete;
176
+ }
177
+ }
178
+
179
+ @bindable({ set: number })
180
+ tabindex: number;
181
+ tabindexChanged() {
182
+ if (isNaN(this.tabindex)) {
183
+ this.input_.removeAttribute('tabindex');
184
+ } else {
185
+ this.input_.tabIndex = this.tabindex;
186
+ }
187
+ }
188
+
189
+ @bindable()
190
+ type: string;
191
+ typeChanged() {
192
+ if (!this.textarea) {
193
+ if (this.type === undefined) {
194
+ this.input_.removeAttribute('type');
195
+ } else {
196
+ this.input_.type = this.type;
197
+ }
198
+ }
199
+ }
200
+
201
+ @bindable()
202
+ name: string;
203
+ nameChanged() {
204
+ if (this.name === undefined) {
205
+ this.input_.removeAttribute('name');
206
+ } else {
207
+ this.input_.name = this.name;
208
+ }
209
+ }
210
+
211
+ @bindable()
212
+ placeholder: string = ' '; // non empty placeholder solves the issue of misplaced labels in Safari
213
+
214
+ private initialValue: string;
215
+ get value(): string {
216
+ if (this.foundation) {
217
+ return this.foundation.getValue();
218
+ } else {
219
+ return this.initialValue;
220
+ }
221
+ }
222
+ set value(value: string) {
223
+ if (this.foundation) {
224
+ if (this.foundation.getValue() !== value) {
225
+ this.foundation.setValue(value === null || value === undefined ? '' : value.toString());
226
+ }
227
+ } else {
228
+ this.initialValue = value;
229
+ }
230
+ }
231
+
232
+ addError(error: IError) {
233
+ this.errors.set(error, true);
234
+ this.valid = false;
235
+ }
236
+
237
+ removeError(error: IError) {
238
+ this.errors.delete(error);
239
+ this.valid = this.errors.size === 0;
240
+ }
241
+
242
+ get valid(): boolean {
243
+ return this.foundation?.isValid() ?? true;
244
+ }
245
+
246
+ set valid(value: boolean) {
247
+ this.foundation?.setUseNativeValidation(false);
248
+ this.foundation?.setValid(value);
249
+ }
250
+
251
+ renderErrors() {
252
+ const helperLine = this.root.nextElementSibling;
253
+ if (helperLine?.tagName === 'MDC-TEXT-FIELD-HELPER-LINE') {
254
+ CustomElement.for<MdcTextFieldHelperLine>(helperLine).viewModel.errors = Array.from(this.errors.keys())
255
+ .filter(x => x.message !== null).map(x => x.message!);
256
+ }
257
+ }
258
+
259
+ async attaching() {
260
+ const nextSibling = this.root.nextElementSibling;
261
+ if (nextSibling?.tagName === cssClasses.HELPER_LINE.toUpperCase()) {
262
+ await CustomElement.for<MdcTextFieldHelperLine>(nextSibling).viewModel.attachedPromise;
263
+ const helperTextEl = nextSibling.querySelector(helperTextStrings.ROOT_SELECTOR);
264
+ this.helperText_ = helperTextEl ? CustomElement.for<MdcTextFieldHelperText>(nextSibling).viewModel : undefined;
265
+ const characterCounterEl = nextSibling.querySelector(characterCountStrings.ROOT_SELECTOR);
266
+ this.characterCounter_ = characterCounterEl ? CustomElement.for<MdcTextFieldCharacterCounter>(characterCounterEl).viewModel : undefined;
267
+ }
268
+ }
269
+
270
+ beforeFoundationCreated() {
271
+ this.maxlengthChanged();
272
+ this.typeChanged();
273
+ this.mutationObserver.observe(this.root, { subtree: true, childList: true });
274
+ this.leadingIconChanged();
275
+ this.trailingIconChanged();
276
+ }
277
+
278
+ mutated(mutations: MutationRecord[]) {
279
+ if (mutations.find(x => [...Array.from(x.addedNodes), ...Array.from(x.removedNodes)].find(y => y instanceof HTMLElement && y.matches(leadingIconSelector)))) {
280
+ this.leadingIconChanged();
281
+ }
282
+ if (mutations.find(x => [...Array.from(x.addedNodes), ...Array.from(x.removedNodes)].find(y => y instanceof HTMLElement && y.matches(trailingIconSelector)))) {
283
+ this.trailingIconChanged();
284
+ }
285
+ }
286
+
287
+ trailingIconChanged() {
288
+ const el = this.root.querySelector<IMdcTextFieldIconElement>(trailingIconSelector);
289
+ this.trailingIcon_ = el ? CustomAttribute.for<MdcTextFieldIcon>(el, mdcIconStrings.ATTRIBUTE)?.viewModel : undefined;
290
+ }
291
+
292
+ leadingIconChanged() {
293
+ const el = this.root.querySelector<IMdcTextFieldIconElement>(leadingIconSelector);
294
+ this.leadingIcon_ = el ? CustomAttribute.for<MdcTextFieldIcon>(el, mdcIconStrings.ATTRIBUTE)?.viewModel : undefined;
295
+ }
296
+
297
+ override destroy() {
298
+ this.mutationObserver.disconnect();
299
+ }
300
+
301
+ initialSyncWithDOM() {
302
+ this.value = this.initialValue;
303
+ this.errors = new Map<IError, boolean>();
304
+ this.valid = true;
305
+
306
+ this.requiredChanged();
307
+ this.disabledChanged();
308
+ this.readonlyChanged();
309
+ this.tabindexChanged();
310
+ this.rowsChanged();
311
+ this.colsChanged();
312
+ this.minChanged();
313
+ this.maxChanged();
314
+ this.stepChanged();
315
+ this.autocompleteChanged();
316
+ this.nameChanged();
317
+ // handle the case when attribute value was set, not bound, in html
318
+ if (this.root.hasAttribute('value')) {
319
+ this.value = this.root.getAttribute('value') ?? '';
320
+ }
321
+ }
322
+
323
+ getDefaultFoundation() {
324
+ const adapter: Partial<MDCTextFieldAdapter> = {
325
+ ...this.getRootAdapterMethods_(),
326
+ ...this.getInputAdapterMethods_(),
327
+ ...this.getLabelAdapterMethods_(),
328
+ ...this.getLineRippleAdapterMethods_(),
329
+ ...this.getOutlineAdapterMethods_(),
330
+ };
331
+ return new MDCTextFieldFoundation(adapter, this.getFoundationMap_());
332
+ }
333
+
334
+ private getRootAdapterMethods_(): MDCTextFieldRootAdapter {
335
+ return {
336
+ addClass: (className) => this.root.classList.add(className),
337
+ removeClass: (className) => this.root.classList.remove(className),
338
+ hasClass: (className) => this.root.classList.contains(className),
339
+ registerTextFieldInteractionHandler: (evtType, handler) => this.listen(evtType, handler),
340
+ deregisterTextFieldInteractionHandler: (evtType, handler) => this.unlisten(evtType, handler),
341
+ registerValidationAttributeChangeHandler: (handler) => {
342
+ const getAttributesList = (mutationsList: MutationRecord[]): string[] => {
343
+ return mutationsList
344
+ .map((mutation) => mutation.attributeName)
345
+ .filter((attributeName) => attributeName) as string[];
346
+ };
347
+ const observer = new MutationObserver((mutationsList) => handler(getAttributesList(mutationsList)));
348
+ const config = { attributes: true };
349
+ observer.observe(this.input_, config);
350
+ return observer;
351
+ },
352
+ deregisterValidationAttributeChangeHandler: (observer) => observer.disconnect(),
353
+ };
354
+ }
355
+
356
+ private getInputAdapterMethods_(): MDCTextFieldInputAdapter {
357
+ return {
358
+ getNativeInput: () => this.input_,
359
+ setInputAttr: (attr, value) => {
360
+ this.input_.setAttribute(attr, value);
361
+ },
362
+ removeInputAttr: (attr) => {
363
+ this.input_.removeAttribute(attr);
364
+ },
365
+ isFocused: () => document.activeElement === this.input_,
366
+ registerInputInteractionHandler: (evtType, handler) => this.input_.addEventListener(evtType, handler, applyPassive()),
367
+ deregisterInputInteractionHandler: (evtType, handler) => this.input_?.removeEventListener(evtType, handler, applyPassive()),
368
+ };
369
+ }
370
+
371
+ private getLabelAdapterMethods_(): MDCTextFieldLabelAdapter {
372
+ return {
373
+ floatLabel: (shouldFloat) => this.label_?.float(shouldFloat),
374
+ getLabelWidth: () => this.label_ ? this.label_.getWidth() : 0,
375
+ hasLabel: () => Boolean(this.label_),
376
+ shakeLabel: (shouldShake) => this.label_?.shake(shouldShake),
377
+ setLabelRequired: (isRequired) => this.label_?.setRequired(isRequired),
378
+ };
379
+ }
380
+
381
+ private getLineRippleAdapterMethods_(): MDCTextFieldLineRippleAdapter {
382
+ return {
383
+ activateLineRipple: () => this.lineRipple_?.activate(),
384
+ deactivateLineRipple: () => this.lineRipple_?.deactivate(),
385
+ setLineRippleTransformOrigin: (normalizedX) => this.lineRipple_?.setRippleCenter(normalizedX)
386
+ };
387
+ }
388
+
389
+ private getOutlineAdapterMethods_(): MDCTextFieldOutlineAdapter {
390
+ return {
391
+ closeOutline: () => this.outline_?.closeNotch(),
392
+ hasOutline: () => Boolean(this.outline_),
393
+ notchOutline: (labelWidth) => this.outline_?.notch(labelWidth),
394
+ };
395
+ }
396
+
397
+ /**
398
+ * @return A map of all subcomponents to subfoundations.
399
+ */
400
+ private getFoundationMap_(): Partial<MDCTextFieldFoundationMap> {
401
+ return {
402
+ characterCounter: this.characterCounter_ ? this.characterCounter_?.foundationForTextField : undefined,
403
+ helperText: this.helperText_ ? this.helperText_.foundationForTextField : undefined,
404
+ leadingIcon: this.leadingIcon_ ? this.leadingIcon_.foundationForTextField : undefined,
405
+ trailingIcon: this.trailingIcon_ ? this.trailingIcon_.foundationForTextField : undefined,
406
+ };
407
+ }
408
+
409
+ onInput(evt: Event): void {
410
+ const value = (evt.target as HTMLInputElement).value;
411
+ this.value = value;
412
+ }
413
+
414
+ onFocus() {
415
+ this.foundation?.activateFocus();
416
+ this.emit('focus', {}, true);
417
+ }
418
+
419
+ onChange(evt: Event): void {
420
+ const value = (evt.target as HTMLInputElement).value;
421
+ this.value = value;
422
+ }
423
+
424
+ onBlur(): void {
425
+ this.foundation?.deactivateFocus();
426
+ this.emit('blur', {}, true);
427
+ }
428
+
429
+ focus() {
430
+ this.input_.focus();
431
+ }
432
+
433
+ blur() {
434
+ this.input_.blur();
435
+ }
436
+
437
+ onKeyup(e: KeyboardEvent) {
438
+ if (this.blurOnEnter && e.keyCode === 13) {
439
+ this.blur();
440
+ }
441
+ return true;
442
+ }
443
+ }
444
+
445
+ /** @hidden */
446
+ export interface IMdcTextFieldElement extends IValidatedElement {
447
+ $au: {
448
+ 'au:resource:custom-element': {
449
+ viewModel: MdcTextField;
450
+ };
451
+ };
452
+ value: string;
453
+ }
454
+
455
+ function defineMdcTextFieldElementApis(element: HTMLElement) {
456
+ Object.defineProperties(element, {
457
+ tagName: {
458
+ get() {
459
+ return 'MDC-TEXT-FIELD';
460
+ }
461
+ },
462
+ value: {
463
+ get(this: IMdcTextFieldElement) {
464
+ return CustomElement.for<MdcTextField>(this).viewModel.value;
465
+ },
466
+ set(this: IMdcTextFieldElement, value: string) {
467
+ CustomElement.for<MdcTextField>(this).viewModel.value = value;
468
+ },
469
+ configurable: true
470
+ },
471
+ disabled: {
472
+ get(this: IMdcTextFieldElement) {
473
+ return CustomElement.for<MdcTextField>(this).viewModel.disabled;
474
+ },
475
+ set(this: IMdcTextFieldElement, value: boolean) {
476
+ CustomElement.for<MdcTextField>(this).viewModel.disabled = value;
477
+ },
478
+ configurable: true
479
+ },
480
+ readOnly: {
481
+ get(this: IMdcTextFieldElement) {
482
+ return CustomElement.for<MdcTextField>(this).viewModel.readonly;
483
+ },
484
+ set(this: IMdcTextFieldElement, value: boolean) {
485
+ CustomElement.for<MdcTextField>(this).viewModel.readonly = value;
486
+ },
487
+ configurable: true
488
+ },
489
+ valid: {
490
+ get(this: IMdcTextFieldElement) {
491
+ return CustomElement.for<MdcTextField>(this).viewModel.valid;
492
+ },
493
+ set(this: IMdcTextFieldElement, value: boolean) {
494
+ CustomElement.for<MdcTextField>(this).viewModel.valid = value;
495
+ },
496
+ configurable: true
497
+ },
498
+ addError: {
499
+ value(this: IMdcTextFieldElement, error: IError) {
500
+ CustomElement.for<MdcTextField>(this).viewModel.addError(error);
501
+ },
502
+ configurable: true
503
+ },
504
+ removeError: {
505
+ value(this: IMdcTextFieldElement, error: IError) {
506
+ CustomElement.for<MdcTextField>(this).viewModel.removeError(error);
507
+ },
508
+ configurable: true
509
+ },
510
+ renderErrors: {
511
+ value(this: IMdcTextFieldElement): void {
512
+ CustomElement.for<MdcTextField>(this).viewModel.renderErrors();
513
+ },
514
+ configurable: true
515
+ },
516
+ focus: {
517
+ value(this: IMdcTextFieldElement) {
518
+ CustomElement.for<MdcTextField>(this).viewModel.focus();
519
+ },
520
+ configurable: true
521
+ },
522
+ blur: {
523
+ value(this: IMdcTextFieldElement) {
524
+ CustomElement.for<MdcTextField>(this).viewModel.blur();
525
+ },
526
+ configurable: true
527
+ },
528
+ isFocused: {
529
+ get(this: IMdcTextFieldElement) {
530
+ return document.activeElement === CustomElement.for<MdcTextField>(this).viewModel.input_;
531
+ },
532
+ configurable: true
533
+ }
534
+ });
535
+ }