input.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. /**
  2. * @license
  3. * Copyright Google LLC All Rights Reserved.
  4. *
  5. * Use of this source code is governed by an MIT-style license that can be
  6. * found in the LICENSE file at https://angular.io/license
  7. */
  8. import { CdkTextareaAutosize, AutofillMonitor, TextFieldModule } from '@angular/cdk/text-field';
  9. import { Directive, Input, InjectionToken, ElementRef, Inject, NgZone, Optional, Self, NgModule } from '@angular/core';
  10. import { coerceBooleanProperty } from '@angular/cdk/coercion';
  11. import { getSupportedInputTypes, Platform } from '@angular/cdk/platform';
  12. import { FormGroupDirective, NgControl, NgForm } from '@angular/forms';
  13. import { ErrorStateMatcher, mixinErrorState } from '@angular/material/core';
  14. import { MatFormFieldControl, MatFormFieldModule } from '@angular/material/form-field';
  15. import { Subject } from 'rxjs';
  16. import { CommonModule } from '@angular/common';
  17. /**
  18. * @fileoverview added by tsickle
  19. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  20. */
  21. /**
  22. * Directive to automatically resize a textarea to fit its content.
  23. * @deprecated Use `cdkTextareaAutosize` from `\@angular/cdk/text-field` instead.
  24. * \@breaking-change 8.0.0
  25. */
  26. class MatTextareaAutosize extends CdkTextareaAutosize {
  27. /**
  28. * @return {?}
  29. */
  30. get matAutosizeMinRows() { return this.minRows; }
  31. /**
  32. * @param {?} value
  33. * @return {?}
  34. */
  35. set matAutosizeMinRows(value) { this.minRows = value; }
  36. /**
  37. * @return {?}
  38. */
  39. get matAutosizeMaxRows() { return this.maxRows; }
  40. /**
  41. * @param {?} value
  42. * @return {?}
  43. */
  44. set matAutosizeMaxRows(value) { this.maxRows = value; }
  45. /**
  46. * @return {?}
  47. */
  48. get matAutosize() { return this.enabled; }
  49. /**
  50. * @param {?} value
  51. * @return {?}
  52. */
  53. set matAutosize(value) { this.enabled = value; }
  54. /**
  55. * @return {?}
  56. */
  57. get matTextareaAutosize() { return this.enabled; }
  58. /**
  59. * @param {?} value
  60. * @return {?}
  61. */
  62. set matTextareaAutosize(value) { this.enabled = value; }
  63. }
  64. MatTextareaAutosize.decorators = [
  65. { type: Directive, args: [{
  66. selector: 'textarea[mat-autosize], textarea[matTextareaAutosize]',
  67. exportAs: 'matTextareaAutosize',
  68. inputs: ['cdkAutosizeMinRows', 'cdkAutosizeMaxRows'],
  69. host: {
  70. 'class': 'cdk-textarea-autosize mat-autosize',
  71. // Textarea elements that have the directive applied should have a single row by default.
  72. // Browsers normally show two rows by default and therefore this limits the minRows binding.
  73. 'rows': '1',
  74. '(input)': '_noopInputHandler()',
  75. },
  76. },] },
  77. ];
  78. MatTextareaAutosize.propDecorators = {
  79. matAutosizeMinRows: [{ type: Input }],
  80. matAutosizeMaxRows: [{ type: Input }],
  81. matAutosize: [{ type: Input, args: ['mat-autosize',] }],
  82. matTextareaAutosize: [{ type: Input }]
  83. };
  84. /**
  85. * @fileoverview added by tsickle
  86. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  87. */
  88. /**
  89. * \@docs-private
  90. * @param {?} type
  91. * @return {?}
  92. */
  93. function getMatInputUnsupportedTypeError(type) {
  94. return Error(`Input type "${type}" isn't supported by matInput.`);
  95. }
  96. /**
  97. * @fileoverview added by tsickle
  98. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  99. */
  100. /**
  101. * This token is used to inject the object whose value should be set into `MatInput`. If none is
  102. * provided, the native `HTMLInputElement` is used. Directives like `MatDatepickerInput` can provide
  103. * themselves for this token, in order to make `MatInput` delegate the getting and setting of the
  104. * value to them.
  105. * @type {?}
  106. */
  107. const MAT_INPUT_VALUE_ACCESSOR = new InjectionToken('MAT_INPUT_VALUE_ACCESSOR');
  108. /**
  109. * @fileoverview added by tsickle
  110. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  111. */
  112. // Invalid input type. Using one of these will throw an MatInputUnsupportedTypeError.
  113. /** @type {?} */
  114. const MAT_INPUT_INVALID_TYPES = [
  115. 'button',
  116. 'checkbox',
  117. 'file',
  118. 'hidden',
  119. 'image',
  120. 'radio',
  121. 'range',
  122. 'reset',
  123. 'submit'
  124. ];
  125. /** @type {?} */
  126. let nextUniqueId = 0;
  127. // Boilerplate for applying mixins to MatInput.
  128. /**
  129. * \@docs-private
  130. */
  131. class MatInputBase {
  132. /**
  133. * @param {?} _defaultErrorStateMatcher
  134. * @param {?} _parentForm
  135. * @param {?} _parentFormGroup
  136. * @param {?} ngControl
  137. */
  138. constructor(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl) {
  139. this._defaultErrorStateMatcher = _defaultErrorStateMatcher;
  140. this._parentForm = _parentForm;
  141. this._parentFormGroup = _parentFormGroup;
  142. this.ngControl = ngControl;
  143. }
  144. }
  145. /** @type {?} */
  146. const _MatInputMixinBase = mixinErrorState(MatInputBase);
  147. /**
  148. * Directive that allows a native input to work inside a `MatFormField`.
  149. */
  150. class MatInput extends _MatInputMixinBase {
  151. /**
  152. * @param {?} _elementRef
  153. * @param {?} _platform
  154. * @param {?} ngControl
  155. * @param {?} _parentForm
  156. * @param {?} _parentFormGroup
  157. * @param {?} _defaultErrorStateMatcher
  158. * @param {?} inputValueAccessor
  159. * @param {?} _autofillMonitor
  160. * @param {?} ngZone
  161. */
  162. constructor(_elementRef, _platform, ngControl, _parentForm, _parentFormGroup, _defaultErrorStateMatcher, inputValueAccessor, _autofillMonitor, ngZone) {
  163. super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl);
  164. this._elementRef = _elementRef;
  165. this._platform = _platform;
  166. this.ngControl = ngControl;
  167. this._autofillMonitor = _autofillMonitor;
  168. this._uid = `mat-input-${nextUniqueId++}`;
  169. /**
  170. * Whether the component is being rendered on the server.
  171. */
  172. this._isServer = false;
  173. /**
  174. * Whether the component is a native html select.
  175. */
  176. this._isNativeSelect = false;
  177. /**
  178. * Implemented as part of MatFormFieldControl.
  179. * \@docs-private
  180. */
  181. this.focused = false;
  182. /**
  183. * Implemented as part of MatFormFieldControl.
  184. * \@docs-private
  185. */
  186. this.stateChanges = new Subject();
  187. /**
  188. * Implemented as part of MatFormFieldControl.
  189. * \@docs-private
  190. */
  191. this.controlType = 'mat-input';
  192. /**
  193. * Implemented as part of MatFormFieldControl.
  194. * \@docs-private
  195. */
  196. this.autofilled = false;
  197. this._disabled = false;
  198. this._required = false;
  199. this._type = 'text';
  200. this._readonly = false;
  201. this._neverEmptyInputTypes = [
  202. 'date',
  203. 'datetime',
  204. 'datetime-local',
  205. 'month',
  206. 'time',
  207. 'week'
  208. ].filter((/**
  209. * @param {?} t
  210. * @return {?}
  211. */
  212. t => getSupportedInputTypes().has(t)));
  213. /** @type {?} */
  214. const element = this._elementRef.nativeElement;
  215. // If no input value accessor was explicitly specified, use the element as the input value
  216. // accessor.
  217. this._inputValueAccessor = inputValueAccessor || element;
  218. this._previousNativeValue = this.value;
  219. // Force setter to be called in case id was not specified.
  220. this.id = this.id;
  221. // On some versions of iOS the caret gets stuck in the wrong place when holding down the delete
  222. // key. In order to get around this we need to "jiggle" the caret loose. Since this bug only
  223. // exists on iOS, we only bother to install the listener on iOS.
  224. if (_platform.IOS) {
  225. ngZone.runOutsideAngular((/**
  226. * @return {?}
  227. */
  228. () => {
  229. _elementRef.nativeElement.addEventListener('keyup', (/**
  230. * @param {?} event
  231. * @return {?}
  232. */
  233. (event) => {
  234. /** @type {?} */
  235. let el = (/** @type {?} */ (event.target));
  236. if (!el.value && !el.selectionStart && !el.selectionEnd) {
  237. // Note: Just setting `0, 0` doesn't fix the issue. Setting
  238. // `1, 1` fixes it for the first time that you type text and
  239. // then hold delete. Toggling to `1, 1` and then back to
  240. // `0, 0` seems to completely fix it.
  241. el.setSelectionRange(1, 1);
  242. el.setSelectionRange(0, 0);
  243. }
  244. }));
  245. }));
  246. }
  247. this._isServer = !this._platform.isBrowser;
  248. this._isNativeSelect = element.nodeName.toLowerCase() === 'select';
  249. if (this._isNativeSelect) {
  250. this.controlType = ((/** @type {?} */ (element))).multiple ? 'mat-native-select-multiple' :
  251. 'mat-native-select';
  252. }
  253. }
  254. /**
  255. * Implemented as part of MatFormFieldControl.
  256. * \@docs-private
  257. * @return {?}
  258. */
  259. get disabled() {
  260. if (this.ngControl && this.ngControl.disabled !== null) {
  261. return this.ngControl.disabled;
  262. }
  263. return this._disabled;
  264. }
  265. /**
  266. * @param {?} value
  267. * @return {?}
  268. */
  269. set disabled(value) {
  270. this._disabled = coerceBooleanProperty(value);
  271. // Browsers may not fire the blur event if the input is disabled too quickly.
  272. // Reset from here to ensure that the element doesn't become stuck.
  273. if (this.focused) {
  274. this.focused = false;
  275. this.stateChanges.next();
  276. }
  277. }
  278. /**
  279. * Implemented as part of MatFormFieldControl.
  280. * \@docs-private
  281. * @return {?}
  282. */
  283. get id() { return this._id; }
  284. /**
  285. * @param {?} value
  286. * @return {?}
  287. */
  288. set id(value) { this._id = value || this._uid; }
  289. /**
  290. * Implemented as part of MatFormFieldControl.
  291. * \@docs-private
  292. * @return {?}
  293. */
  294. get required() { return this._required; }
  295. /**
  296. * @param {?} value
  297. * @return {?}
  298. */
  299. set required(value) { this._required = coerceBooleanProperty(value); }
  300. /**
  301. * Input type of the element.
  302. * @return {?}
  303. */
  304. get type() { return this._type; }
  305. /**
  306. * @param {?} value
  307. * @return {?}
  308. */
  309. set type(value) {
  310. this._type = value || 'text';
  311. this._validateType();
  312. // When using Angular inputs, developers are no longer able to set the properties on the native
  313. // input element. To ensure that bindings for `type` work, we need to sync the setter
  314. // with the native property. Textarea elements don't support the type property or attribute.
  315. if (!this._isTextarea() && getSupportedInputTypes().has(this._type)) {
  316. ((/** @type {?} */ (this._elementRef.nativeElement))).type = this._type;
  317. }
  318. }
  319. /**
  320. * Implemented as part of MatFormFieldControl.
  321. * \@docs-private
  322. * @return {?}
  323. */
  324. get value() { return this._inputValueAccessor.value; }
  325. /**
  326. * @param {?} value
  327. * @return {?}
  328. */
  329. set value(value) {
  330. if (value !== this.value) {
  331. this._inputValueAccessor.value = value;
  332. this.stateChanges.next();
  333. }
  334. }
  335. /**
  336. * Whether the element is readonly.
  337. * @return {?}
  338. */
  339. get readonly() { return this._readonly; }
  340. /**
  341. * @param {?} value
  342. * @return {?}
  343. */
  344. set readonly(value) { this._readonly = coerceBooleanProperty(value); }
  345. /**
  346. * @return {?}
  347. */
  348. ngOnInit() {
  349. if (this._platform.isBrowser) {
  350. this._autofillMonitor.monitor(this._elementRef.nativeElement).subscribe((/**
  351. * @param {?} event
  352. * @return {?}
  353. */
  354. event => {
  355. this.autofilled = event.isAutofilled;
  356. this.stateChanges.next();
  357. }));
  358. }
  359. }
  360. /**
  361. * @return {?}
  362. */
  363. ngOnChanges() {
  364. this.stateChanges.next();
  365. }
  366. /**
  367. * @return {?}
  368. */
  369. ngOnDestroy() {
  370. this.stateChanges.complete();
  371. if (this._platform.isBrowser) {
  372. this._autofillMonitor.stopMonitoring(this._elementRef.nativeElement);
  373. }
  374. }
  375. /**
  376. * @return {?}
  377. */
  378. ngDoCheck() {
  379. if (this.ngControl) {
  380. // We need to re-evaluate this on every change detection cycle, because there are some
  381. // error triggers that we can't subscribe to (e.g. parent form submissions). This means
  382. // that whatever logic is in here has to be super lean or we risk destroying the performance.
  383. this.updateErrorState();
  384. }
  385. // We need to dirty-check the native element's value, because there are some cases where
  386. // we won't be notified when it changes (e.g. the consumer isn't using forms or they're
  387. // updating the value using `emitEvent: false`).
  388. this._dirtyCheckNativeValue();
  389. }
  390. /**
  391. * Focuses the input.
  392. * @param {?=} options
  393. * @return {?}
  394. */
  395. focus(options) {
  396. this._elementRef.nativeElement.focus(options);
  397. }
  398. /**
  399. * Callback for the cases where the focused state of the input changes.
  400. * @param {?} isFocused
  401. * @return {?}
  402. */
  403. _focusChanged(isFocused) {
  404. if (isFocused !== this.focused && (!this.readonly || !isFocused)) {
  405. this.focused = isFocused;
  406. this.stateChanges.next();
  407. }
  408. }
  409. /**
  410. * @return {?}
  411. */
  412. _onInput() {
  413. // This is a noop function and is used to let Angular know whenever the value changes.
  414. // Angular will run a new change detection each time the `input` event has been dispatched.
  415. // It's necessary that Angular recognizes the value change, because when floatingLabel
  416. // is set to false and Angular forms aren't used, the placeholder won't recognize the
  417. // value changes and will not disappear.
  418. // Listening to the input event wouldn't be necessary when the input is using the
  419. // FormsModule or ReactiveFormsModule, because Angular forms also listens to input events.
  420. }
  421. /**
  422. * Does some manual dirty checking on the native input `value` property.
  423. * @protected
  424. * @return {?}
  425. */
  426. _dirtyCheckNativeValue() {
  427. /** @type {?} */
  428. const newValue = this._elementRef.nativeElement.value;
  429. if (this._previousNativeValue !== newValue) {
  430. this._previousNativeValue = newValue;
  431. this.stateChanges.next();
  432. }
  433. }
  434. /**
  435. * Make sure the input is a supported type.
  436. * @protected
  437. * @return {?}
  438. */
  439. _validateType() {
  440. if (MAT_INPUT_INVALID_TYPES.indexOf(this._type) > -1) {
  441. throw getMatInputUnsupportedTypeError(this._type);
  442. }
  443. }
  444. /**
  445. * Checks whether the input type is one of the types that are never empty.
  446. * @protected
  447. * @return {?}
  448. */
  449. _isNeverEmpty() {
  450. return this._neverEmptyInputTypes.indexOf(this._type) > -1;
  451. }
  452. /**
  453. * Checks whether the input is invalid based on the native validation.
  454. * @protected
  455. * @return {?}
  456. */
  457. _isBadInput() {
  458. // The `validity` property won't be present on platform-server.
  459. /** @type {?} */
  460. let validity = ((/** @type {?} */ (this._elementRef.nativeElement))).validity;
  461. return validity && validity.badInput;
  462. }
  463. /**
  464. * Determines if the component host is a textarea.
  465. * @protected
  466. * @return {?}
  467. */
  468. _isTextarea() {
  469. return this._elementRef.nativeElement.nodeName.toLowerCase() === 'textarea';
  470. }
  471. /**
  472. * Implemented as part of MatFormFieldControl.
  473. * \@docs-private
  474. * @return {?}
  475. */
  476. get empty() {
  477. return !this._isNeverEmpty() && !this._elementRef.nativeElement.value && !this._isBadInput() &&
  478. !this.autofilled;
  479. }
  480. /**
  481. * Implemented as part of MatFormFieldControl.
  482. * \@docs-private
  483. * @return {?}
  484. */
  485. get shouldLabelFloat() {
  486. if (this._isNativeSelect) {
  487. // For a single-selection `<select>`, the label should float when the selected option has
  488. // a non-empty display value. For a `<select multiple>`, the label *always* floats to avoid
  489. // overlapping the label with the options.
  490. /** @type {?} */
  491. const selectElement = (/** @type {?} */ (this._elementRef.nativeElement));
  492. /** @type {?} */
  493. const firstOption = selectElement.options[0];
  494. // On most browsers the `selectedIndex` will always be 0, however on IE and Edge it'll be
  495. // -1 if the `value` is set to something, that isn't in the list of options, at a later point.
  496. return this.focused || selectElement.multiple || !this.empty ||
  497. !!(selectElement.selectedIndex > -1 && firstOption && firstOption.label);
  498. }
  499. else {
  500. return this.focused || !this.empty;
  501. }
  502. }
  503. /**
  504. * Implemented as part of MatFormFieldControl.
  505. * \@docs-private
  506. * @param {?} ids
  507. * @return {?}
  508. */
  509. setDescribedByIds(ids) {
  510. this._ariaDescribedby = ids.join(' ');
  511. }
  512. /**
  513. * Implemented as part of MatFormFieldControl.
  514. * \@docs-private
  515. * @return {?}
  516. */
  517. onContainerClick() {
  518. // Do not re-focus the input element if the element is already focused. Otherwise it can happen
  519. // that someone clicks on a time input and the cursor resets to the "hours" field while the
  520. // "minutes" field was actually clicked. See: https://github.com/angular/components/issues/12849
  521. if (!this.focused) {
  522. this.focus();
  523. }
  524. }
  525. }
  526. MatInput.decorators = [
  527. { type: Directive, args: [{
  528. selector: `input[matInput], textarea[matInput], select[matNativeControl],
  529. input[matNativeControl], textarea[matNativeControl]`,
  530. exportAs: 'matInput',
  531. host: {
  532. /**
  533. * \@breaking-change 8.0.0 remove .mat-form-field-autofill-control in favor of AutofillMonitor.
  534. */
  535. 'class': 'mat-input-element mat-form-field-autofill-control',
  536. '[class.mat-input-server]': '_isServer',
  537. // Native input properties that are overwritten by Angular inputs need to be synced with
  538. // the native input element. Otherwise property bindings for those don't work.
  539. '[attr.id]': 'id',
  540. '[attr.placeholder]': 'placeholder',
  541. '[disabled]': 'disabled',
  542. '[required]': 'required',
  543. '[attr.readonly]': 'readonly && !_isNativeSelect || null',
  544. '[attr.aria-describedby]': '_ariaDescribedby || null',
  545. '[attr.aria-invalid]': 'errorState',
  546. '[attr.aria-required]': 'required.toString()',
  547. '(blur)': '_focusChanged(false)',
  548. '(focus)': '_focusChanged(true)',
  549. '(input)': '_onInput()',
  550. },
  551. providers: [{ provide: MatFormFieldControl, useExisting: MatInput }],
  552. },] },
  553. ];
  554. /** @nocollapse */
  555. MatInput.ctorParameters = () => [
  556. { type: ElementRef },
  557. { type: Platform },
  558. { type: NgControl, decorators: [{ type: Optional }, { type: Self }] },
  559. { type: NgForm, decorators: [{ type: Optional }] },
  560. { type: FormGroupDirective, decorators: [{ type: Optional }] },
  561. { type: ErrorStateMatcher },
  562. { type: undefined, decorators: [{ type: Optional }, { type: Self }, { type: Inject, args: [MAT_INPUT_VALUE_ACCESSOR,] }] },
  563. { type: AutofillMonitor },
  564. { type: NgZone }
  565. ];
  566. MatInput.propDecorators = {
  567. disabled: [{ type: Input }],
  568. id: [{ type: Input }],
  569. placeholder: [{ type: Input }],
  570. required: [{ type: Input }],
  571. type: [{ type: Input }],
  572. errorStateMatcher: [{ type: Input }],
  573. value: [{ type: Input }],
  574. readonly: [{ type: Input }]
  575. };
  576. /**
  577. * @fileoverview added by tsickle
  578. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  579. */
  580. class MatInputModule {
  581. }
  582. MatInputModule.decorators = [
  583. { type: NgModule, args: [{
  584. declarations: [MatInput, MatTextareaAutosize],
  585. imports: [
  586. CommonModule,
  587. TextFieldModule,
  588. MatFormFieldModule,
  589. ],
  590. exports: [
  591. TextFieldModule,
  592. // We re-export the `MatFormFieldModule` since `MatInput` will almost always
  593. // be used together with `MatFormField`.
  594. MatFormFieldModule,
  595. MatInput,
  596. MatTextareaAutosize,
  597. ],
  598. providers: [ErrorStateMatcher],
  599. },] },
  600. ];
  601. /**
  602. * @fileoverview added by tsickle
  603. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  604. */
  605. /**
  606. * @fileoverview added by tsickle
  607. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  608. */
  609. export { MatTextareaAutosize, MatInput, getMatInputUnsupportedTypeError, MatInputModule, MAT_INPUT_VALUE_ACCESSOR };
  610. //# sourceMappingURL=input.js.map