chips.js 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721
  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 { coerceBooleanProperty } from '@angular/cdk/coercion';
  9. import { BACKSPACE, DELETE, SPACE, END, HOME, hasModifierKey, TAB, ENTER } from '@angular/cdk/keycodes';
  10. import { Platform } from '@angular/cdk/platform';
  11. import { ContentChild, Directive, ElementRef, EventEmitter, forwardRef, Inject, Input, NgZone, Optional, Output, InjectionToken, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, Self, ViewEncapsulation, NgModule } from '@angular/core';
  12. import { MAT_RIPPLE_GLOBAL_OPTIONS, mixinColor, mixinDisabled, mixinDisableRipple, RippleRenderer, ErrorStateMatcher, mixinErrorState } from '@angular/material/core';
  13. import { Subject, merge } from 'rxjs';
  14. import { take, startWith, takeUntil } from 'rxjs/operators';
  15. import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations';
  16. import { FocusKeyManager } from '@angular/cdk/a11y';
  17. import { Directionality } from '@angular/cdk/bidi';
  18. import { SelectionModel } from '@angular/cdk/collections';
  19. import { FormGroupDirective, NgControl, NgForm } from '@angular/forms';
  20. import { MatFormFieldControl } from '@angular/material/form-field';
  21. /**
  22. * @fileoverview added by tsickle
  23. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  24. */
  25. /**
  26. * Event object emitted by MatChip when selected or deselected.
  27. */
  28. class MatChipSelectionChange {
  29. /**
  30. * @param {?} source
  31. * @param {?} selected
  32. * @param {?=} isUserInput
  33. */
  34. constructor(source, selected, isUserInput = false) {
  35. this.source = source;
  36. this.selected = selected;
  37. this.isUserInput = isUserInput;
  38. }
  39. }
  40. // Boilerplate for applying mixins to MatChip.
  41. /**
  42. * \@docs-private
  43. */
  44. class MatChipBase {
  45. /**
  46. * @param {?} _elementRef
  47. */
  48. constructor(_elementRef) {
  49. this._elementRef = _elementRef;
  50. }
  51. }
  52. /** @type {?} */
  53. const _MatChipMixinBase = mixinColor(mixinDisableRipple(mixinDisabled(MatChipBase)), 'primary');
  54. /**
  55. * Dummy directive to add CSS class to chip avatar.
  56. * \@docs-private
  57. */
  58. class MatChipAvatar {
  59. }
  60. MatChipAvatar.decorators = [
  61. { type: Directive, args: [{
  62. selector: 'mat-chip-avatar, [matChipAvatar]',
  63. host: { 'class': 'mat-chip-avatar' }
  64. },] },
  65. ];
  66. /**
  67. * Dummy directive to add CSS class to chip trailing icon.
  68. * \@docs-private
  69. */
  70. class MatChipTrailingIcon {
  71. }
  72. MatChipTrailingIcon.decorators = [
  73. { type: Directive, args: [{
  74. selector: 'mat-chip-trailing-icon, [matChipTrailingIcon]',
  75. host: { 'class': 'mat-chip-trailing-icon' }
  76. },] },
  77. ];
  78. /**
  79. * Material design styled Chip component. Used inside the MatChipList component.
  80. */
  81. class MatChip extends _MatChipMixinBase {
  82. /**
  83. * @param {?} _elementRef
  84. * @param {?} _ngZone
  85. * @param {?} platform
  86. * @param {?} globalRippleOptions
  87. * @param {?=} animationMode
  88. */
  89. constructor(_elementRef, _ngZone, platform, globalRippleOptions,
  90. // @breaking-change 8.0.0 `animationMode` parameter to become required.
  91. animationMode) {
  92. super(_elementRef);
  93. this._elementRef = _elementRef;
  94. this._ngZone = _ngZone;
  95. /**
  96. * Whether the chip has focus.
  97. */
  98. this._hasFocus = false;
  99. /**
  100. * Whether the chip list is selectable
  101. */
  102. this.chipListSelectable = true;
  103. /**
  104. * Whether the chip list is in multi-selection mode.
  105. */
  106. this._chipListMultiple = false;
  107. this._selected = false;
  108. this._selectable = true;
  109. this._removable = true;
  110. /**
  111. * Emits when the chip is focused.
  112. */
  113. this._onFocus = new Subject();
  114. /**
  115. * Emits when the chip is blured.
  116. */
  117. this._onBlur = new Subject();
  118. /**
  119. * Emitted when the chip is selected or deselected.
  120. */
  121. this.selectionChange = new EventEmitter();
  122. /**
  123. * Emitted when the chip is destroyed.
  124. */
  125. this.destroyed = new EventEmitter();
  126. /**
  127. * Emitted when a chip is to be removed.
  128. */
  129. this.removed = new EventEmitter();
  130. this._addHostClassName();
  131. this._chipRipple = new RippleRenderer(this, _ngZone, _elementRef, platform);
  132. this._chipRipple.setupTriggerEvents(_elementRef.nativeElement);
  133. this.rippleConfig = globalRippleOptions || {};
  134. this._animationsDisabled = animationMode === 'NoopAnimations';
  135. }
  136. /**
  137. * Whether ripples are disabled on interaction
  138. * \@docs-private
  139. * @return {?}
  140. */
  141. get rippleDisabled() {
  142. return this.disabled || this.disableRipple || !!this.rippleConfig.disabled;
  143. }
  144. /**
  145. * Whether the chip is selected.
  146. * @return {?}
  147. */
  148. get selected() { return this._selected; }
  149. /**
  150. * @param {?} value
  151. * @return {?}
  152. */
  153. set selected(value) {
  154. /** @type {?} */
  155. const coercedValue = coerceBooleanProperty(value);
  156. if (coercedValue !== this._selected) {
  157. this._selected = coercedValue;
  158. this._dispatchSelectionChange();
  159. }
  160. }
  161. /**
  162. * The value of the chip. Defaults to the content inside `<mat-chip>` tags.
  163. * @return {?}
  164. */
  165. get value() {
  166. return this._value != undefined
  167. ? this._value
  168. : this._elementRef.nativeElement.textContent;
  169. }
  170. /**
  171. * @param {?} value
  172. * @return {?}
  173. */
  174. set value(value) { this._value = value; }
  175. /**
  176. * Whether or not the chip is selectable. When a chip is not selectable,
  177. * changes to its selected state are always ignored. By default a chip is
  178. * selectable, and it becomes non-selectable if its parent chip list is
  179. * not selectable.
  180. * @return {?}
  181. */
  182. get selectable() { return this._selectable && this.chipListSelectable; }
  183. /**
  184. * @param {?} value
  185. * @return {?}
  186. */
  187. set selectable(value) {
  188. this._selectable = coerceBooleanProperty(value);
  189. }
  190. /**
  191. * Determines whether or not the chip displays the remove styling and emits (removed) events.
  192. * @return {?}
  193. */
  194. get removable() { return this._removable; }
  195. /**
  196. * @param {?} value
  197. * @return {?}
  198. */
  199. set removable(value) {
  200. this._removable = coerceBooleanProperty(value);
  201. }
  202. /**
  203. * The ARIA selected applied to the chip.
  204. * @return {?}
  205. */
  206. get ariaSelected() {
  207. // Remove the `aria-selected` when the chip is deselected in single-selection mode, because
  208. // it adds noise to NVDA users where "not selected" will be read out for each chip.
  209. return this.selectable && (this._chipListMultiple || this.selected) ?
  210. this.selected.toString() : null;
  211. }
  212. /**
  213. * @return {?}
  214. */
  215. _addHostClassName() {
  216. /** @type {?} */
  217. const basicChipAttrName = 'mat-basic-chip';
  218. /** @type {?} */
  219. const element = (/** @type {?} */ (this._elementRef.nativeElement));
  220. if (element.hasAttribute(basicChipAttrName) ||
  221. element.tagName.toLowerCase() === basicChipAttrName) {
  222. element.classList.add(basicChipAttrName);
  223. return;
  224. }
  225. else {
  226. element.classList.add('mat-standard-chip');
  227. }
  228. }
  229. /**
  230. * @return {?}
  231. */
  232. ngOnDestroy() {
  233. this.destroyed.emit({ chip: this });
  234. this._chipRipple._removeTriggerEvents();
  235. }
  236. /**
  237. * Selects the chip.
  238. * @return {?}
  239. */
  240. select() {
  241. if (!this._selected) {
  242. this._selected = true;
  243. this._dispatchSelectionChange();
  244. }
  245. }
  246. /**
  247. * Deselects the chip.
  248. * @return {?}
  249. */
  250. deselect() {
  251. if (this._selected) {
  252. this._selected = false;
  253. this._dispatchSelectionChange();
  254. }
  255. }
  256. /**
  257. * Select this chip and emit selected event
  258. * @return {?}
  259. */
  260. selectViaInteraction() {
  261. if (!this._selected) {
  262. this._selected = true;
  263. this._dispatchSelectionChange(true);
  264. }
  265. }
  266. /**
  267. * Toggles the current selected state of this chip.
  268. * @param {?=} isUserInput
  269. * @return {?}
  270. */
  271. toggleSelected(isUserInput = false) {
  272. this._selected = !this.selected;
  273. this._dispatchSelectionChange(isUserInput);
  274. return this.selected;
  275. }
  276. /**
  277. * Allows for programmatic focusing of the chip.
  278. * @return {?}
  279. */
  280. focus() {
  281. if (!this._hasFocus) {
  282. this._elementRef.nativeElement.focus();
  283. this._onFocus.next({ chip: this });
  284. }
  285. this._hasFocus = true;
  286. }
  287. /**
  288. * Allows for programmatic removal of the chip. Called by the MatChipList when the DELETE or
  289. * BACKSPACE keys are pressed.
  290. *
  291. * Informs any listeners of the removal request. Does not remove the chip from the DOM.
  292. * @return {?}
  293. */
  294. remove() {
  295. if (this.removable) {
  296. this.removed.emit({ chip: this });
  297. }
  298. }
  299. /**
  300. * Handles click events on the chip.
  301. * @param {?} event
  302. * @return {?}
  303. */
  304. _handleClick(event) {
  305. if (this.disabled) {
  306. event.preventDefault();
  307. }
  308. else {
  309. event.stopPropagation();
  310. }
  311. }
  312. /**
  313. * Handle custom key presses.
  314. * @param {?} event
  315. * @return {?}
  316. */
  317. _handleKeydown(event) {
  318. if (this.disabled) {
  319. return;
  320. }
  321. switch (event.keyCode) {
  322. case DELETE:
  323. case BACKSPACE:
  324. // If we are removable, remove the focused chip
  325. this.remove();
  326. // Always prevent so page navigation does not occur
  327. event.preventDefault();
  328. break;
  329. case SPACE:
  330. // If we are selectable, toggle the focused chip
  331. if (this.selectable) {
  332. this.toggleSelected(true);
  333. }
  334. // Always prevent space from scrolling the page since the list has focus
  335. event.preventDefault();
  336. break;
  337. }
  338. }
  339. /**
  340. * @return {?}
  341. */
  342. _blur() {
  343. // When animations are enabled, Angular may end up removing the chip from the DOM a little
  344. // earlier than usual, causing it to be blurred and throwing off the logic in the chip list
  345. // that moves focus not the next item. To work around the issue, we defer marking the chip
  346. // as not focused until the next time the zone stabilizes.
  347. this._ngZone.onStable
  348. .asObservable()
  349. .pipe(take(1))
  350. .subscribe((/**
  351. * @return {?}
  352. */
  353. () => {
  354. this._ngZone.run((/**
  355. * @return {?}
  356. */
  357. () => {
  358. this._hasFocus = false;
  359. this._onBlur.next({ chip: this });
  360. }));
  361. }));
  362. }
  363. /**
  364. * @private
  365. * @param {?=} isUserInput
  366. * @return {?}
  367. */
  368. _dispatchSelectionChange(isUserInput = false) {
  369. this.selectionChange.emit({
  370. source: this,
  371. isUserInput,
  372. selected: this._selected
  373. });
  374. }
  375. }
  376. MatChip.decorators = [
  377. { type: Directive, args: [{
  378. selector: `mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]`,
  379. inputs: ['color', 'disabled', 'disableRipple'],
  380. exportAs: 'matChip',
  381. host: {
  382. 'class': 'mat-chip',
  383. '[attr.tabindex]': 'disabled ? null : -1',
  384. 'role': 'option',
  385. '[class.mat-chip-selected]': 'selected',
  386. '[class.mat-chip-with-avatar]': 'avatar',
  387. '[class.mat-chip-with-trailing-icon]': 'trailingIcon || removeIcon',
  388. '[class.mat-chip-disabled]': 'disabled',
  389. '[class._mat-animation-noopable]': '_animationsDisabled',
  390. '[attr.disabled]': 'disabled || null',
  391. '[attr.aria-disabled]': 'disabled.toString()',
  392. '[attr.aria-selected]': 'ariaSelected',
  393. '(click)': '_handleClick($event)',
  394. '(keydown)': '_handleKeydown($event)',
  395. '(focus)': 'focus()',
  396. '(blur)': '_blur()',
  397. },
  398. },] },
  399. ];
  400. /** @nocollapse */
  401. MatChip.ctorParameters = () => [
  402. { type: ElementRef },
  403. { type: NgZone },
  404. { type: Platform },
  405. { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_RIPPLE_GLOBAL_OPTIONS,] }] },
  406. { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] }
  407. ];
  408. MatChip.propDecorators = {
  409. avatar: [{ type: ContentChild, args: [MatChipAvatar, { static: false },] }],
  410. trailingIcon: [{ type: ContentChild, args: [MatChipTrailingIcon, { static: false },] }],
  411. removeIcon: [{ type: ContentChild, args: [forwardRef((/**
  412. * @return {?}
  413. */
  414. () => MatChipRemove)), { static: false },] }],
  415. selected: [{ type: Input }],
  416. value: [{ type: Input }],
  417. selectable: [{ type: Input }],
  418. removable: [{ type: Input }],
  419. selectionChange: [{ type: Output }],
  420. destroyed: [{ type: Output }],
  421. removed: [{ type: Output }]
  422. };
  423. /**
  424. * Applies proper (click) support and adds styling for use with the Material Design "cancel" icon
  425. * available at https://material.io/icons/#ic_cancel.
  426. *
  427. * Example:
  428. *
  429. * `<mat-chip>
  430. * <mat-icon matChipRemove>cancel</mat-icon>
  431. * </mat-chip>`
  432. *
  433. * You *may* use a custom icon, but you may need to override the `mat-chip-remove` positioning
  434. * styles to properly center the icon within the chip.
  435. */
  436. class MatChipRemove {
  437. /**
  438. * @param {?} _parentChip
  439. */
  440. constructor(_parentChip) {
  441. this._parentChip = _parentChip;
  442. }
  443. /**
  444. * Calls the parent chip's public `remove()` method if applicable.
  445. * @param {?} event
  446. * @return {?}
  447. */
  448. _handleClick(event) {
  449. /** @type {?} */
  450. const parentChip = this._parentChip;
  451. if (parentChip.removable && !parentChip.disabled) {
  452. parentChip.remove();
  453. }
  454. // We need to stop event propagation because otherwise the event will bubble up to the
  455. // form field and cause the `onContainerClick` method to be invoked. This method would then
  456. // reset the focused chip that has been focused after chip removal. Usually the parent
  457. // the parent click listener of the `MatChip` would prevent propagation, but it can happen
  458. // that the chip is being removed before the event bubbles up.
  459. event.stopPropagation();
  460. }
  461. }
  462. MatChipRemove.decorators = [
  463. { type: Directive, args: [{
  464. selector: '[matChipRemove]',
  465. host: {
  466. 'class': 'mat-chip-remove mat-chip-trailing-icon',
  467. '(click)': '_handleClick($event)',
  468. }
  469. },] },
  470. ];
  471. /** @nocollapse */
  472. MatChipRemove.ctorParameters = () => [
  473. { type: MatChip }
  474. ];
  475. /**
  476. * @fileoverview added by tsickle
  477. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  478. */
  479. /**
  480. * Injection token to be used to override the default options for the chips module.
  481. * @type {?}
  482. */
  483. const MAT_CHIPS_DEFAULT_OPTIONS = new InjectionToken('mat-chips-default-options');
  484. /**
  485. * @fileoverview added by tsickle
  486. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  487. */
  488. // Boilerplate for applying mixins to MatChipList.
  489. /**
  490. * \@docs-private
  491. */
  492. class MatChipListBase {
  493. /**
  494. * @param {?} _defaultErrorStateMatcher
  495. * @param {?} _parentForm
  496. * @param {?} _parentFormGroup
  497. * @param {?} ngControl
  498. */
  499. constructor(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl) {
  500. this._defaultErrorStateMatcher = _defaultErrorStateMatcher;
  501. this._parentForm = _parentForm;
  502. this._parentFormGroup = _parentFormGroup;
  503. this.ngControl = ngControl;
  504. }
  505. }
  506. /** @type {?} */
  507. const _MatChipListMixinBase = mixinErrorState(MatChipListBase);
  508. // Increasing integer for generating unique ids for chip-list components.
  509. /** @type {?} */
  510. let nextUniqueId = 0;
  511. /**
  512. * Change event object that is emitted when the chip list value has changed.
  513. */
  514. class MatChipListChange {
  515. /**
  516. * @param {?} source
  517. * @param {?} value
  518. */
  519. constructor(source, value) {
  520. this.source = source;
  521. this.value = value;
  522. }
  523. }
  524. /**
  525. * A material design chips component (named ChipList for its similarity to the List component).
  526. */
  527. class MatChipList extends _MatChipListMixinBase {
  528. /**
  529. * @param {?} _elementRef
  530. * @param {?} _changeDetectorRef
  531. * @param {?} _dir
  532. * @param {?} _parentForm
  533. * @param {?} _parentFormGroup
  534. * @param {?} _defaultErrorStateMatcher
  535. * @param {?} ngControl
  536. */
  537. constructor(_elementRef, _changeDetectorRef, _dir, _parentForm, _parentFormGroup, _defaultErrorStateMatcher, ngControl) {
  538. super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl);
  539. this._elementRef = _elementRef;
  540. this._changeDetectorRef = _changeDetectorRef;
  541. this._dir = _dir;
  542. this.ngControl = ngControl;
  543. /**
  544. * Implemented as part of MatFormFieldControl.
  545. * \@docs-private
  546. */
  547. this.controlType = 'mat-chip-list';
  548. /**
  549. * When a chip is destroyed, we store the index of the destroyed chip until the chips
  550. * query list notifies about the update. This is necessary because we cannot determine an
  551. * appropriate chip that should receive focus until the array of chips updated completely.
  552. */
  553. this._lastDestroyedChipIndex = null;
  554. /**
  555. * Subject that emits when the component has been destroyed.
  556. */
  557. this._destroyed = new Subject();
  558. /**
  559. * Uid of the chip list
  560. */
  561. this._uid = `mat-chip-list-${nextUniqueId++}`;
  562. /**
  563. * Tab index for the chip list.
  564. */
  565. this._tabIndex = 0;
  566. /**
  567. * User defined tab index.
  568. * When it is not null, use user defined tab index. Otherwise use _tabIndex
  569. */
  570. this._userTabIndex = null;
  571. /**
  572. * Function when touched
  573. */
  574. this._onTouched = (/**
  575. * @return {?}
  576. */
  577. () => { });
  578. /**
  579. * Function when changed
  580. */
  581. this._onChange = (/**
  582. * @return {?}
  583. */
  584. () => { });
  585. this._multiple = false;
  586. this._compareWith = (/**
  587. * @param {?} o1
  588. * @param {?} o2
  589. * @return {?}
  590. */
  591. (o1, o2) => o1 === o2);
  592. this._required = false;
  593. this._disabled = false;
  594. /**
  595. * Orientation of the chip list.
  596. */
  597. this.ariaOrientation = 'horizontal';
  598. this._selectable = true;
  599. /**
  600. * Event emitted when the selected chip list value has been changed by the user.
  601. */
  602. this.change = new EventEmitter();
  603. /**
  604. * Event that emits whenever the raw value of the chip-list changes. This is here primarily
  605. * to facilitate the two-way binding for the `value` input.
  606. * \@docs-private
  607. */
  608. this.valueChange = new EventEmitter();
  609. if (this.ngControl) {
  610. this.ngControl.valueAccessor = this;
  611. }
  612. }
  613. /**
  614. * The array of selected chips inside chip list.
  615. * @return {?}
  616. */
  617. get selected() {
  618. return this.multiple ? this._selectionModel.selected : this._selectionModel.selected[0];
  619. }
  620. /**
  621. * The ARIA role applied to the chip list.
  622. * @return {?}
  623. */
  624. get role() { return this.empty ? null : 'listbox'; }
  625. /**
  626. * Whether the user should be allowed to select multiple chips.
  627. * @return {?}
  628. */
  629. get multiple() { return this._multiple; }
  630. /**
  631. * @param {?} value
  632. * @return {?}
  633. */
  634. set multiple(value) {
  635. this._multiple = coerceBooleanProperty(value);
  636. this._syncChipsState();
  637. }
  638. /**
  639. * A function to compare the option values with the selected values. The first argument
  640. * is a value from an option. The second is a value from the selection. A boolean
  641. * should be returned.
  642. * @return {?}
  643. */
  644. get compareWith() { return this._compareWith; }
  645. /**
  646. * @param {?} fn
  647. * @return {?}
  648. */
  649. set compareWith(fn) {
  650. this._compareWith = fn;
  651. if (this._selectionModel) {
  652. // A different comparator means the selection could change.
  653. this._initializeSelection();
  654. }
  655. }
  656. /**
  657. * Implemented as part of MatFormFieldControl.
  658. * \@docs-private
  659. * @return {?}
  660. */
  661. get value() { return this._value; }
  662. /**
  663. * @param {?} value
  664. * @return {?}
  665. */
  666. set value(value) {
  667. this.writeValue(value);
  668. this._value = value;
  669. }
  670. /**
  671. * Implemented as part of MatFormFieldControl.
  672. * \@docs-private
  673. * @return {?}
  674. */
  675. get id() {
  676. return this._chipInput ? this._chipInput.id : this._uid;
  677. }
  678. /**
  679. * Implemented as part of MatFormFieldControl.
  680. * \@docs-private
  681. * @return {?}
  682. */
  683. get required() { return this._required; }
  684. /**
  685. * @param {?} value
  686. * @return {?}
  687. */
  688. set required(value) {
  689. this._required = coerceBooleanProperty(value);
  690. this.stateChanges.next();
  691. }
  692. /**
  693. * Implemented as part of MatFormFieldControl.
  694. * \@docs-private
  695. * @return {?}
  696. */
  697. get placeholder() {
  698. return this._chipInput ? this._chipInput.placeholder : this._placeholder;
  699. }
  700. /**
  701. * @param {?} value
  702. * @return {?}
  703. */
  704. set placeholder(value) {
  705. this._placeholder = value;
  706. this.stateChanges.next();
  707. }
  708. /**
  709. * Whether any chips or the matChipInput inside of this chip-list has focus.
  710. * @return {?}
  711. */
  712. get focused() {
  713. return (this._chipInput && this._chipInput.focused) || this._hasFocusedChip();
  714. }
  715. /**
  716. * Implemented as part of MatFormFieldControl.
  717. * \@docs-private
  718. * @return {?}
  719. */
  720. get empty() {
  721. return (!this._chipInput || this._chipInput.empty) && this.chips.length === 0;
  722. }
  723. /**
  724. * Implemented as part of MatFormFieldControl.
  725. * \@docs-private
  726. * @return {?}
  727. */
  728. get shouldLabelFloat() { return !this.empty || this.focused; }
  729. /**
  730. * Implemented as part of MatFormFieldControl.
  731. * \@docs-private
  732. * @return {?}
  733. */
  734. get disabled() { return this.ngControl ? !!this.ngControl.disabled : this._disabled; }
  735. /**
  736. * @param {?} value
  737. * @return {?}
  738. */
  739. set disabled(value) {
  740. this._disabled = coerceBooleanProperty(value);
  741. this._syncChipsState();
  742. }
  743. /**
  744. * Whether or not this chip list is selectable. When a chip list is not selectable,
  745. * the selected states for all the chips inside the chip list are always ignored.
  746. * @return {?}
  747. */
  748. get selectable() { return this._selectable; }
  749. /**
  750. * @param {?} value
  751. * @return {?}
  752. */
  753. set selectable(value) {
  754. this._selectable = coerceBooleanProperty(value);
  755. if (this.chips) {
  756. this.chips.forEach((/**
  757. * @param {?} chip
  758. * @return {?}
  759. */
  760. chip => chip.chipListSelectable = this._selectable));
  761. }
  762. }
  763. /**
  764. * @param {?} value
  765. * @return {?}
  766. */
  767. set tabIndex(value) {
  768. this._userTabIndex = value;
  769. this._tabIndex = value;
  770. }
  771. /**
  772. * Combined stream of all of the child chips' selection change events.
  773. * @return {?}
  774. */
  775. get chipSelectionChanges() {
  776. return merge(...this.chips.map((/**
  777. * @param {?} chip
  778. * @return {?}
  779. */
  780. chip => chip.selectionChange)));
  781. }
  782. /**
  783. * Combined stream of all of the child chips' focus change events.
  784. * @return {?}
  785. */
  786. get chipFocusChanges() {
  787. return merge(...this.chips.map((/**
  788. * @param {?} chip
  789. * @return {?}
  790. */
  791. chip => chip._onFocus)));
  792. }
  793. /**
  794. * Combined stream of all of the child chips' blur change events.
  795. * @return {?}
  796. */
  797. get chipBlurChanges() {
  798. return merge(...this.chips.map((/**
  799. * @param {?} chip
  800. * @return {?}
  801. */
  802. chip => chip._onBlur)));
  803. }
  804. /**
  805. * Combined stream of all of the child chips' remove change events.
  806. * @return {?}
  807. */
  808. get chipRemoveChanges() {
  809. return merge(...this.chips.map((/**
  810. * @param {?} chip
  811. * @return {?}
  812. */
  813. chip => chip.destroyed)));
  814. }
  815. /**
  816. * @return {?}
  817. */
  818. ngAfterContentInit() {
  819. this._keyManager = new FocusKeyManager(this.chips)
  820. .withWrap()
  821. .withVerticalOrientation()
  822. .withHorizontalOrientation(this._dir ? this._dir.value : 'ltr');
  823. if (this._dir) {
  824. this._dir.change
  825. .pipe(takeUntil(this._destroyed))
  826. .subscribe((/**
  827. * @param {?} dir
  828. * @return {?}
  829. */
  830. dir => this._keyManager.withHorizontalOrientation(dir)));
  831. }
  832. this._keyManager.tabOut.pipe(takeUntil(this._destroyed)).subscribe((/**
  833. * @return {?}
  834. */
  835. () => {
  836. this._allowFocusEscape();
  837. }));
  838. // When the list changes, re-subscribe
  839. this.chips.changes.pipe(startWith(null), takeUntil(this._destroyed)).subscribe((/**
  840. * @return {?}
  841. */
  842. () => {
  843. if (this.disabled) {
  844. // Since this happens after the content has been
  845. // checked, we need to defer it to the next tick.
  846. Promise.resolve().then((/**
  847. * @return {?}
  848. */
  849. () => {
  850. this._syncChipsState();
  851. }));
  852. }
  853. this._resetChips();
  854. // Reset chips selected/deselected status
  855. this._initializeSelection();
  856. // Check to see if we need to update our tab index
  857. this._updateTabIndex();
  858. // Check to see if we have a destroyed chip and need to refocus
  859. this._updateFocusForDestroyedChips();
  860. this.stateChanges.next();
  861. }));
  862. }
  863. /**
  864. * @return {?}
  865. */
  866. ngOnInit() {
  867. this._selectionModel = new SelectionModel(this.multiple, undefined, false);
  868. this.stateChanges.next();
  869. }
  870. /**
  871. * @return {?}
  872. */
  873. ngDoCheck() {
  874. if (this.ngControl) {
  875. // We need to re-evaluate this on every change detection cycle, because there are some
  876. // error triggers that we can't subscribe to (e.g. parent form submissions). This means
  877. // that whatever logic is in here has to be super lean or we risk destroying the performance.
  878. this.updateErrorState();
  879. }
  880. }
  881. /**
  882. * @return {?}
  883. */
  884. ngOnDestroy() {
  885. this._destroyed.next();
  886. this._destroyed.complete();
  887. this.stateChanges.complete();
  888. this._dropSubscriptions();
  889. }
  890. /**
  891. * Associates an HTML input element with this chip list.
  892. * @param {?} inputElement
  893. * @return {?}
  894. */
  895. registerInput(inputElement) {
  896. this._chipInput = inputElement;
  897. }
  898. /**
  899. * Implemented as part of MatFormFieldControl.
  900. * \@docs-private
  901. * @param {?} ids
  902. * @return {?}
  903. */
  904. setDescribedByIds(ids) { this._ariaDescribedby = ids.join(' '); }
  905. // Implemented as part of ControlValueAccessor.
  906. /**
  907. * @param {?} value
  908. * @return {?}
  909. */
  910. writeValue(value) {
  911. if (this.chips) {
  912. this._setSelectionByValue(value, false);
  913. }
  914. }
  915. // Implemented as part of ControlValueAccessor.
  916. /**
  917. * @param {?} fn
  918. * @return {?}
  919. */
  920. registerOnChange(fn) {
  921. this._onChange = fn;
  922. }
  923. // Implemented as part of ControlValueAccessor.
  924. /**
  925. * @param {?} fn
  926. * @return {?}
  927. */
  928. registerOnTouched(fn) {
  929. this._onTouched = fn;
  930. }
  931. // Implemented as part of ControlValueAccessor.
  932. /**
  933. * @param {?} isDisabled
  934. * @return {?}
  935. */
  936. setDisabledState(isDisabled) {
  937. this.disabled = isDisabled;
  938. this.stateChanges.next();
  939. }
  940. /**
  941. * Implemented as part of MatFormFieldControl.
  942. * \@docs-private
  943. * @param {?} event
  944. * @return {?}
  945. */
  946. onContainerClick(event) {
  947. if (!this._originatesFromChip(event)) {
  948. this.focus();
  949. }
  950. }
  951. /**
  952. * Focuses the first non-disabled chip in this chip list, or the associated input when there
  953. * are no eligible chips.
  954. * @param {?=} options
  955. * @return {?}
  956. */
  957. focus(options) {
  958. if (this.disabled) {
  959. return;
  960. }
  961. // TODO: ARIA says this should focus the first `selected` chip if any are selected.
  962. // Focus on first element if there's no chipInput inside chip-list
  963. if (this._chipInput && this._chipInput.focused) {
  964. // do nothing
  965. }
  966. else if (this.chips.length > 0) {
  967. this._keyManager.setFirstItemActive();
  968. this.stateChanges.next();
  969. }
  970. else {
  971. this._focusInput(options);
  972. this.stateChanges.next();
  973. }
  974. }
  975. /**
  976. * Attempt to focus an input if we have one.
  977. * @param {?=} options
  978. * @return {?}
  979. */
  980. _focusInput(options) {
  981. if (this._chipInput) {
  982. this._chipInput.focus(options);
  983. }
  984. }
  985. /**
  986. * Pass events to the keyboard manager. Available here for tests.
  987. * @param {?} event
  988. * @return {?}
  989. */
  990. _keydown(event) {
  991. /** @type {?} */
  992. const target = (/** @type {?} */ (event.target));
  993. // If they are on an empty input and hit backspace, focus the last chip
  994. if (event.keyCode === BACKSPACE && this._isInputEmpty(target)) {
  995. this._keyManager.setLastItemActive();
  996. event.preventDefault();
  997. }
  998. else if (target && target.classList.contains('mat-chip')) {
  999. if (event.keyCode === HOME) {
  1000. this._keyManager.setFirstItemActive();
  1001. event.preventDefault();
  1002. }
  1003. else if (event.keyCode === END) {
  1004. this._keyManager.setLastItemActive();
  1005. event.preventDefault();
  1006. }
  1007. else {
  1008. this._keyManager.onKeydown(event);
  1009. }
  1010. this.stateChanges.next();
  1011. }
  1012. }
  1013. /**
  1014. * Check the tab index as you should not be allowed to focus an empty list.
  1015. * @protected
  1016. * @return {?}
  1017. */
  1018. _updateTabIndex() {
  1019. // If we have 0 chips, we should not allow keyboard focus
  1020. this._tabIndex = this._userTabIndex || (this.chips.length === 0 ? -1 : 0);
  1021. }
  1022. /**
  1023. * If the amount of chips changed, we need to update the
  1024. * key manager state and focus the next closest chip.
  1025. * @protected
  1026. * @return {?}
  1027. */
  1028. _updateFocusForDestroyedChips() {
  1029. // Move focus to the closest chip. If no other chips remain, focus the chip-list itself.
  1030. if (this._lastDestroyedChipIndex != null) {
  1031. if (this.chips.length) {
  1032. /** @type {?} */
  1033. const newChipIndex = Math.min(this._lastDestroyedChipIndex, this.chips.length - 1);
  1034. this._keyManager.setActiveItem(newChipIndex);
  1035. }
  1036. else {
  1037. this.focus();
  1038. }
  1039. }
  1040. this._lastDestroyedChipIndex = null;
  1041. }
  1042. /**
  1043. * Utility to ensure all indexes are valid.
  1044. *
  1045. * @private
  1046. * @param {?} index The index to be checked.
  1047. * @return {?} True if the index is valid for our list of chips.
  1048. */
  1049. _isValidIndex(index) {
  1050. return index >= 0 && index < this.chips.length;
  1051. }
  1052. /**
  1053. * @private
  1054. * @param {?} element
  1055. * @return {?}
  1056. */
  1057. _isInputEmpty(element) {
  1058. if (element && element.nodeName.toLowerCase() === 'input') {
  1059. /** @type {?} */
  1060. let input = (/** @type {?} */ (element));
  1061. return !input.value;
  1062. }
  1063. return false;
  1064. }
  1065. /**
  1066. * @param {?} value
  1067. * @param {?=} isUserInput
  1068. * @return {?}
  1069. */
  1070. _setSelectionByValue(value, isUserInput = true) {
  1071. this._clearSelection();
  1072. this.chips.forEach((/**
  1073. * @param {?} chip
  1074. * @return {?}
  1075. */
  1076. chip => chip.deselect()));
  1077. if (Array.isArray(value)) {
  1078. value.forEach((/**
  1079. * @param {?} currentValue
  1080. * @return {?}
  1081. */
  1082. currentValue => this._selectValue(currentValue, isUserInput)));
  1083. this._sortValues();
  1084. }
  1085. else {
  1086. /** @type {?} */
  1087. const correspondingChip = this._selectValue(value, isUserInput);
  1088. // Shift focus to the active item. Note that we shouldn't do this in multiple
  1089. // mode, because we don't know what chip the user interacted with last.
  1090. if (correspondingChip) {
  1091. if (isUserInput) {
  1092. this._keyManager.setActiveItem(correspondingChip);
  1093. }
  1094. }
  1095. }
  1096. }
  1097. /**
  1098. * Finds and selects the chip based on its value.
  1099. * @private
  1100. * @param {?} value
  1101. * @param {?=} isUserInput
  1102. * @return {?} Chip that has the corresponding value.
  1103. */
  1104. _selectValue(value, isUserInput = true) {
  1105. /** @type {?} */
  1106. const correspondingChip = this.chips.find((/**
  1107. * @param {?} chip
  1108. * @return {?}
  1109. */
  1110. chip => {
  1111. return chip.value != null && this._compareWith(chip.value, value);
  1112. }));
  1113. if (correspondingChip) {
  1114. isUserInput ? correspondingChip.selectViaInteraction() : correspondingChip.select();
  1115. this._selectionModel.select(correspondingChip);
  1116. }
  1117. return correspondingChip;
  1118. }
  1119. /**
  1120. * @private
  1121. * @return {?}
  1122. */
  1123. _initializeSelection() {
  1124. // Defer setting the value in order to avoid the "Expression
  1125. // has changed after it was checked" errors from Angular.
  1126. Promise.resolve().then((/**
  1127. * @return {?}
  1128. */
  1129. () => {
  1130. if (this.ngControl || this._value) {
  1131. this._setSelectionByValue(this.ngControl ? this.ngControl.value : this._value, false);
  1132. this.stateChanges.next();
  1133. }
  1134. }));
  1135. }
  1136. /**
  1137. * Deselects every chip in the list.
  1138. * @private
  1139. * @param {?=} skip Chip that should not be deselected.
  1140. * @return {?}
  1141. */
  1142. _clearSelection(skip) {
  1143. this._selectionModel.clear();
  1144. this.chips.forEach((/**
  1145. * @param {?} chip
  1146. * @return {?}
  1147. */
  1148. chip => {
  1149. if (chip !== skip) {
  1150. chip.deselect();
  1151. }
  1152. }));
  1153. this.stateChanges.next();
  1154. }
  1155. /**
  1156. * Sorts the model values, ensuring that they keep the same
  1157. * order that they have in the panel.
  1158. * @private
  1159. * @return {?}
  1160. */
  1161. _sortValues() {
  1162. if (this._multiple) {
  1163. this._selectionModel.clear();
  1164. this.chips.forEach((/**
  1165. * @param {?} chip
  1166. * @return {?}
  1167. */
  1168. chip => {
  1169. if (chip.selected) {
  1170. this._selectionModel.select(chip);
  1171. }
  1172. }));
  1173. this.stateChanges.next();
  1174. }
  1175. }
  1176. /**
  1177. * Emits change event to set the model value.
  1178. * @private
  1179. * @param {?=} fallbackValue
  1180. * @return {?}
  1181. */
  1182. _propagateChanges(fallbackValue) {
  1183. /** @type {?} */
  1184. let valueToEmit = null;
  1185. if (Array.isArray(this.selected)) {
  1186. valueToEmit = this.selected.map((/**
  1187. * @param {?} chip
  1188. * @return {?}
  1189. */
  1190. chip => chip.value));
  1191. }
  1192. else {
  1193. valueToEmit = this.selected ? this.selected.value : fallbackValue;
  1194. }
  1195. this._value = valueToEmit;
  1196. this.change.emit(new MatChipListChange(this, valueToEmit));
  1197. this.valueChange.emit(valueToEmit);
  1198. this._onChange(valueToEmit);
  1199. this._changeDetectorRef.markForCheck();
  1200. }
  1201. /**
  1202. * When blurred, mark the field as touched when focus moved outside the chip list.
  1203. * @return {?}
  1204. */
  1205. _blur() {
  1206. if (!this._hasFocusedChip()) {
  1207. this._keyManager.setActiveItem(-1);
  1208. }
  1209. if (!this.disabled) {
  1210. if (this._chipInput) {
  1211. // If there's a chip input, we should check whether the focus moved to chip input.
  1212. // If the focus is not moved to chip input, mark the field as touched. If the focus moved
  1213. // to chip input, do nothing.
  1214. // Timeout is needed to wait for the focus() event trigger on chip input.
  1215. setTimeout((/**
  1216. * @return {?}
  1217. */
  1218. () => {
  1219. if (!this.focused) {
  1220. this._markAsTouched();
  1221. }
  1222. }));
  1223. }
  1224. else {
  1225. // If there's no chip input, then mark the field as touched.
  1226. this._markAsTouched();
  1227. }
  1228. }
  1229. }
  1230. /**
  1231. * Mark the field as touched
  1232. * @return {?}
  1233. */
  1234. _markAsTouched() {
  1235. this._onTouched();
  1236. this._changeDetectorRef.markForCheck();
  1237. this.stateChanges.next();
  1238. }
  1239. /**
  1240. * Removes the `tabindex` from the chip list and resets it back afterwards, allowing the
  1241. * user to tab out of it. This prevents the list from capturing focus and redirecting
  1242. * it back to the first chip, creating a focus trap, if it user tries to tab away.
  1243. * @return {?}
  1244. */
  1245. _allowFocusEscape() {
  1246. if (this._tabIndex !== -1) {
  1247. this._tabIndex = -1;
  1248. setTimeout((/**
  1249. * @return {?}
  1250. */
  1251. () => {
  1252. this._tabIndex = this._userTabIndex || 0;
  1253. this._changeDetectorRef.markForCheck();
  1254. }));
  1255. }
  1256. }
  1257. /**
  1258. * @private
  1259. * @return {?}
  1260. */
  1261. _resetChips() {
  1262. this._dropSubscriptions();
  1263. this._listenToChipsFocus();
  1264. this._listenToChipsSelection();
  1265. this._listenToChipsRemoved();
  1266. }
  1267. /**
  1268. * @private
  1269. * @return {?}
  1270. */
  1271. _dropSubscriptions() {
  1272. if (this._chipFocusSubscription) {
  1273. this._chipFocusSubscription.unsubscribe();
  1274. this._chipFocusSubscription = null;
  1275. }
  1276. if (this._chipBlurSubscription) {
  1277. this._chipBlurSubscription.unsubscribe();
  1278. this._chipBlurSubscription = null;
  1279. }
  1280. if (this._chipSelectionSubscription) {
  1281. this._chipSelectionSubscription.unsubscribe();
  1282. this._chipSelectionSubscription = null;
  1283. }
  1284. if (this._chipRemoveSubscription) {
  1285. this._chipRemoveSubscription.unsubscribe();
  1286. this._chipRemoveSubscription = null;
  1287. }
  1288. }
  1289. /**
  1290. * Listens to user-generated selection events on each chip.
  1291. * @private
  1292. * @return {?}
  1293. */
  1294. _listenToChipsSelection() {
  1295. this._chipSelectionSubscription = this.chipSelectionChanges.subscribe((/**
  1296. * @param {?} event
  1297. * @return {?}
  1298. */
  1299. event => {
  1300. event.source.selected
  1301. ? this._selectionModel.select(event.source)
  1302. : this._selectionModel.deselect(event.source);
  1303. // For single selection chip list, make sure the deselected value is unselected.
  1304. if (!this.multiple) {
  1305. this.chips.forEach((/**
  1306. * @param {?} chip
  1307. * @return {?}
  1308. */
  1309. chip => {
  1310. if (!this._selectionModel.isSelected(chip) && chip.selected) {
  1311. chip.deselect();
  1312. }
  1313. }));
  1314. }
  1315. if (event.isUserInput) {
  1316. this._propagateChanges();
  1317. }
  1318. }));
  1319. }
  1320. /**
  1321. * Listens to user-generated selection events on each chip.
  1322. * @private
  1323. * @return {?}
  1324. */
  1325. _listenToChipsFocus() {
  1326. this._chipFocusSubscription = this.chipFocusChanges.subscribe((/**
  1327. * @param {?} event
  1328. * @return {?}
  1329. */
  1330. event => {
  1331. /** @type {?} */
  1332. let chipIndex = this.chips.toArray().indexOf(event.chip);
  1333. if (this._isValidIndex(chipIndex)) {
  1334. this._keyManager.updateActiveItemIndex(chipIndex);
  1335. }
  1336. this.stateChanges.next();
  1337. }));
  1338. this._chipBlurSubscription = this.chipBlurChanges.subscribe((/**
  1339. * @return {?}
  1340. */
  1341. () => {
  1342. this._blur();
  1343. this.stateChanges.next();
  1344. }));
  1345. }
  1346. /**
  1347. * @private
  1348. * @return {?}
  1349. */
  1350. _listenToChipsRemoved() {
  1351. this._chipRemoveSubscription = this.chipRemoveChanges.subscribe((/**
  1352. * @param {?} event
  1353. * @return {?}
  1354. */
  1355. event => {
  1356. /** @type {?} */
  1357. const chip = event.chip;
  1358. /** @type {?} */
  1359. const chipIndex = this.chips.toArray().indexOf(event.chip);
  1360. // In case the chip that will be removed is currently focused, we temporarily store
  1361. // the index in order to be able to determine an appropriate sibling chip that will
  1362. // receive focus.
  1363. if (this._isValidIndex(chipIndex) && chip._hasFocus) {
  1364. this._lastDestroyedChipIndex = chipIndex;
  1365. }
  1366. }));
  1367. }
  1368. /**
  1369. * Checks whether an event comes from inside a chip element.
  1370. * @private
  1371. * @param {?} event
  1372. * @return {?}
  1373. */
  1374. _originatesFromChip(event) {
  1375. /** @type {?} */
  1376. let currentElement = (/** @type {?} */ (event.target));
  1377. while (currentElement && currentElement !== this._elementRef.nativeElement) {
  1378. if (currentElement.classList.contains('mat-chip')) {
  1379. return true;
  1380. }
  1381. currentElement = currentElement.parentElement;
  1382. }
  1383. return false;
  1384. }
  1385. /**
  1386. * Checks whether any of the chips is focused.
  1387. * @private
  1388. * @return {?}
  1389. */
  1390. _hasFocusedChip() {
  1391. return this.chips.some((/**
  1392. * @param {?} chip
  1393. * @return {?}
  1394. */
  1395. chip => chip._hasFocus));
  1396. }
  1397. /**
  1398. * Syncs the list's state with the individual chips.
  1399. * @private
  1400. * @return {?}
  1401. */
  1402. _syncChipsState() {
  1403. if (this.chips) {
  1404. this.chips.forEach((/**
  1405. * @param {?} chip
  1406. * @return {?}
  1407. */
  1408. chip => {
  1409. chip.disabled = this._disabled;
  1410. chip._chipListMultiple = this.multiple;
  1411. }));
  1412. }
  1413. }
  1414. }
  1415. MatChipList.decorators = [
  1416. { type: Component, args: [{selector: 'mat-chip-list',
  1417. template: `<div class="mat-chip-list-wrapper"><ng-content></ng-content></div>`,
  1418. exportAs: 'matChipList',
  1419. host: {
  1420. '[attr.tabindex]': 'disabled ? null : _tabIndex',
  1421. '[attr.aria-describedby]': '_ariaDescribedby || null',
  1422. '[attr.aria-required]': 'required.toString()',
  1423. '[attr.aria-disabled]': 'disabled.toString()',
  1424. '[attr.aria-invalid]': 'errorState',
  1425. '[attr.aria-multiselectable]': 'multiple',
  1426. '[attr.role]': 'role',
  1427. '[class.mat-chip-list-disabled]': 'disabled',
  1428. '[class.mat-chip-list-invalid]': 'errorState',
  1429. '[class.mat-chip-list-required]': 'required',
  1430. '[attr.aria-orientation]': 'ariaOrientation',
  1431. 'class': 'mat-chip-list',
  1432. '(focus)': 'focus()',
  1433. '(blur)': '_blur()',
  1434. '(keydown)': '_keydown($event)',
  1435. '[id]': '_uid',
  1436. },
  1437. providers: [{ provide: MatFormFieldControl, useExisting: MatChipList }],
  1438. styles: [".mat-chip{position:relative;overflow:hidden;box-sizing:border-box;-webkit-tap-highlight-color:transparent;transform:translateZ(0)}.mat-standard-chip{transition:box-shadow 280ms cubic-bezier(.4,0,.2,1);display:inline-flex;padding:7px 12px;border-radius:16px;align-items:center;cursor:default;min-height:32px;height:1px}._mat-animation-noopable.mat-standard-chip{transition:none;animation:none}.mat-standard-chip .mat-chip-remove.mat-icon{width:18px;height:18px}.mat-standard-chip::after{top:0;left:0;right:0;bottom:0;position:absolute;border-radius:inherit;opacity:0;content:'';pointer-events:none;transition:opacity .2s cubic-bezier(.35,0,.25,1)}.mat-standard-chip:hover::after{opacity:.12}.mat-standard-chip:focus{outline:0}.mat-standard-chip:focus::after{opacity:.16}@media (-ms-high-contrast:active){.mat-standard-chip{outline:solid 1px}.mat-standard-chip:focus{outline:dotted 2px}}.mat-standard-chip.mat-chip-disabled::after{opacity:0}.mat-standard-chip.mat-chip-disabled .mat-chip-remove,.mat-standard-chip.mat-chip-disabled .mat-chip-trailing-icon{cursor:default}.mat-standard-chip.mat-chip-with-avatar,.mat-standard-chip.mat-chip-with-trailing-icon.mat-chip-with-avatar{padding-top:0;padding-bottom:0}.mat-standard-chip.mat-chip-with-trailing-icon.mat-chip-with-avatar{padding-right:8px;padding-left:0}[dir=rtl] .mat-standard-chip.mat-chip-with-trailing-icon.mat-chip-with-avatar{padding-left:8px;padding-right:0}.mat-standard-chip.mat-chip-with-trailing-icon{padding-top:7px;padding-bottom:7px;padding-right:8px;padding-left:12px}[dir=rtl] .mat-standard-chip.mat-chip-with-trailing-icon{padding-left:8px;padding-right:12px}.mat-standard-chip.mat-chip-with-avatar{padding-left:0;padding-right:12px}[dir=rtl] .mat-standard-chip.mat-chip-with-avatar{padding-right:0;padding-left:12px}.mat-standard-chip .mat-chip-avatar{width:24px;height:24px;margin-right:8px;margin-left:4px}[dir=rtl] .mat-standard-chip .mat-chip-avatar{margin-left:8px;margin-right:4px}.mat-standard-chip .mat-chip-remove,.mat-standard-chip .mat-chip-trailing-icon{width:18px;height:18px;cursor:pointer}.mat-standard-chip .mat-chip-remove,.mat-standard-chip .mat-chip-trailing-icon{margin-left:8px;margin-right:0}[dir=rtl] .mat-standard-chip .mat-chip-remove,[dir=rtl] .mat-standard-chip .mat-chip-trailing-icon{margin-right:8px;margin-left:0}.mat-chip-list-wrapper{display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;margin:-4px}.mat-chip-list-wrapper .mat-standard-chip,.mat-chip-list-wrapper input.mat-input-element{margin:4px}.mat-chip-list-stacked .mat-chip-list-wrapper{flex-direction:column;align-items:flex-start}.mat-chip-list-stacked .mat-chip-list-wrapper .mat-standard-chip{width:100%}.mat-chip-avatar{border-radius:50%;justify-content:center;align-items:center;display:flex;overflow:hidden;object-fit:cover}input.mat-chip-input{width:150px;margin:4px;flex:1 0 150px}"],
  1439. encapsulation: ViewEncapsulation.None,
  1440. changeDetection: ChangeDetectionStrategy.OnPush
  1441. },] },
  1442. ];
  1443. /** @nocollapse */
  1444. MatChipList.ctorParameters = () => [
  1445. { type: ElementRef },
  1446. { type: ChangeDetectorRef },
  1447. { type: Directionality, decorators: [{ type: Optional }] },
  1448. { type: NgForm, decorators: [{ type: Optional }] },
  1449. { type: FormGroupDirective, decorators: [{ type: Optional }] },
  1450. { type: ErrorStateMatcher },
  1451. { type: NgControl, decorators: [{ type: Optional }, { type: Self }] }
  1452. ];
  1453. MatChipList.propDecorators = {
  1454. errorStateMatcher: [{ type: Input }],
  1455. multiple: [{ type: Input }],
  1456. compareWith: [{ type: Input }],
  1457. value: [{ type: Input }],
  1458. required: [{ type: Input }],
  1459. placeholder: [{ type: Input }],
  1460. disabled: [{ type: Input }],
  1461. ariaOrientation: [{ type: Input, args: ['aria-orientation',] }],
  1462. selectable: [{ type: Input }],
  1463. tabIndex: [{ type: Input }],
  1464. change: [{ type: Output }],
  1465. valueChange: [{ type: Output }],
  1466. chips: [{ type: ContentChildren, args: [MatChip, {
  1467. // We need to use `descendants: true`, because Ivy will no longer match
  1468. // indirect descendants if it's left as false.
  1469. descendants: true
  1470. },] }]
  1471. };
  1472. /**
  1473. * @fileoverview added by tsickle
  1474. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1475. */
  1476. // Increasing integer for generating unique ids.
  1477. /** @type {?} */
  1478. let nextUniqueId$1 = 0;
  1479. /**
  1480. * Directive that adds chip-specific behaviors to an input element inside `<mat-form-field>`.
  1481. * May be placed inside or outside of an `<mat-chip-list>`.
  1482. */
  1483. class MatChipInput {
  1484. /**
  1485. * @param {?} _elementRef
  1486. * @param {?} _defaultOptions
  1487. */
  1488. constructor(_elementRef, _defaultOptions) {
  1489. this._elementRef = _elementRef;
  1490. this._defaultOptions = _defaultOptions;
  1491. /**
  1492. * Whether the control is focused.
  1493. */
  1494. this.focused = false;
  1495. this._addOnBlur = false;
  1496. /**
  1497. * The list of key codes that will trigger a chipEnd event.
  1498. *
  1499. * Defaults to `[ENTER]`.
  1500. */
  1501. this.separatorKeyCodes = this._defaultOptions.separatorKeyCodes;
  1502. /**
  1503. * Emitted when a chip is to be added.
  1504. */
  1505. this.chipEnd = new EventEmitter();
  1506. /**
  1507. * The input's placeholder text.
  1508. */
  1509. this.placeholder = '';
  1510. /**
  1511. * Unique id for the input.
  1512. */
  1513. this.id = `mat-chip-list-input-${nextUniqueId$1++}`;
  1514. this._disabled = false;
  1515. this._inputElement = (/** @type {?} */ (this._elementRef.nativeElement));
  1516. }
  1517. /**
  1518. * Register input for chip list
  1519. * @param {?} value
  1520. * @return {?}
  1521. */
  1522. set chipList(value) {
  1523. if (value) {
  1524. this._chipList = value;
  1525. this._chipList.registerInput(this);
  1526. }
  1527. }
  1528. /**
  1529. * Whether or not the chipEnd event will be emitted when the input is blurred.
  1530. * @return {?}
  1531. */
  1532. get addOnBlur() { return this._addOnBlur; }
  1533. /**
  1534. * @param {?} value
  1535. * @return {?}
  1536. */
  1537. set addOnBlur(value) { this._addOnBlur = coerceBooleanProperty(value); }
  1538. /**
  1539. * Whether the input is disabled.
  1540. * @return {?}
  1541. */
  1542. get disabled() { return this._disabled || (this._chipList && this._chipList.disabled); }
  1543. /**
  1544. * @param {?} value
  1545. * @return {?}
  1546. */
  1547. set disabled(value) { this._disabled = coerceBooleanProperty(value); }
  1548. /**
  1549. * Whether the input is empty.
  1550. * @return {?}
  1551. */
  1552. get empty() { return !this._inputElement.value; }
  1553. /**
  1554. * @return {?}
  1555. */
  1556. ngOnChanges() {
  1557. this._chipList.stateChanges.next();
  1558. }
  1559. /**
  1560. * Utility method to make host definition/tests more clear.
  1561. * @param {?=} event
  1562. * @return {?}
  1563. */
  1564. _keydown(event) {
  1565. // Allow the user's focus to escape when they're tabbing forward. Note that we don't
  1566. // want to do this when going backwards, because focus should go back to the first chip.
  1567. if (event && event.keyCode === TAB && !hasModifierKey(event, 'shiftKey')) {
  1568. this._chipList._allowFocusEscape();
  1569. }
  1570. this._emitChipEnd(event);
  1571. }
  1572. /**
  1573. * Checks to see if the blur should emit the (chipEnd) event.
  1574. * @return {?}
  1575. */
  1576. _blur() {
  1577. if (this.addOnBlur) {
  1578. this._emitChipEnd();
  1579. }
  1580. this.focused = false;
  1581. // Blur the chip list if it is not focused
  1582. if (!this._chipList.focused) {
  1583. this._chipList._blur();
  1584. }
  1585. this._chipList.stateChanges.next();
  1586. }
  1587. /**
  1588. * @return {?}
  1589. */
  1590. _focus() {
  1591. this.focused = true;
  1592. this._chipList.stateChanges.next();
  1593. }
  1594. /**
  1595. * Checks to see if the (chipEnd) event needs to be emitted.
  1596. * @param {?=} event
  1597. * @return {?}
  1598. */
  1599. _emitChipEnd(event) {
  1600. if (!this._inputElement.value && !!event) {
  1601. this._chipList._keydown(event);
  1602. }
  1603. if (!event || this._isSeparatorKey(event)) {
  1604. this.chipEnd.emit({ input: this._inputElement, value: this._inputElement.value });
  1605. if (event) {
  1606. event.preventDefault();
  1607. }
  1608. }
  1609. }
  1610. /**
  1611. * @return {?}
  1612. */
  1613. _onInput() {
  1614. // Let chip list know whenever the value changes.
  1615. this._chipList.stateChanges.next();
  1616. }
  1617. /**
  1618. * Focuses the input.
  1619. * @param {?=} options
  1620. * @return {?}
  1621. */
  1622. focus(options) {
  1623. this._inputElement.focus(options);
  1624. }
  1625. /**
  1626. * Checks whether a keycode is one of the configured separators.
  1627. * @private
  1628. * @param {?} event
  1629. * @return {?}
  1630. */
  1631. _isSeparatorKey(event) {
  1632. if (hasModifierKey(event)) {
  1633. return false;
  1634. }
  1635. /** @type {?} */
  1636. const separators = this.separatorKeyCodes;
  1637. /** @type {?} */
  1638. const keyCode = event.keyCode;
  1639. return Array.isArray(separators) ? separators.indexOf(keyCode) > -1 : separators.has(keyCode);
  1640. }
  1641. }
  1642. MatChipInput.decorators = [
  1643. { type: Directive, args: [{
  1644. selector: 'input[matChipInputFor]',
  1645. exportAs: 'matChipInput, matChipInputFor',
  1646. host: {
  1647. 'class': 'mat-chip-input mat-input-element',
  1648. '(keydown)': '_keydown($event)',
  1649. '(blur)': '_blur()',
  1650. '(focus)': '_focus()',
  1651. '(input)': '_onInput()',
  1652. '[id]': 'id',
  1653. '[attr.disabled]': 'disabled || null',
  1654. '[attr.placeholder]': 'placeholder || null',
  1655. '[attr.aria-invalid]': '_chipList && _chipList.ngControl ? _chipList.ngControl.invalid : null',
  1656. }
  1657. },] },
  1658. ];
  1659. /** @nocollapse */
  1660. MatChipInput.ctorParameters = () => [
  1661. { type: ElementRef },
  1662. { type: undefined, decorators: [{ type: Inject, args: [MAT_CHIPS_DEFAULT_OPTIONS,] }] }
  1663. ];
  1664. MatChipInput.propDecorators = {
  1665. chipList: [{ type: Input, args: ['matChipInputFor',] }],
  1666. addOnBlur: [{ type: Input, args: ['matChipInputAddOnBlur',] }],
  1667. separatorKeyCodes: [{ type: Input, args: ['matChipInputSeparatorKeyCodes',] }],
  1668. chipEnd: [{ type: Output, args: ['matChipInputTokenEnd',] }],
  1669. placeholder: [{ type: Input }],
  1670. id: [{ type: Input }],
  1671. disabled: [{ type: Input }]
  1672. };
  1673. /**
  1674. * @fileoverview added by tsickle
  1675. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1676. */
  1677. /** @type {?} */
  1678. const CHIP_DECLARATIONS = [
  1679. MatChipList,
  1680. MatChip,
  1681. MatChipInput,
  1682. MatChipRemove,
  1683. MatChipAvatar,
  1684. MatChipTrailingIcon,
  1685. ];
  1686. const ɵ0 = ({
  1687. separatorKeyCodes: [ENTER]
  1688. });
  1689. class MatChipsModule {
  1690. }
  1691. MatChipsModule.decorators = [
  1692. { type: NgModule, args: [{
  1693. exports: CHIP_DECLARATIONS,
  1694. declarations: CHIP_DECLARATIONS,
  1695. providers: [
  1696. ErrorStateMatcher,
  1697. {
  1698. provide: MAT_CHIPS_DEFAULT_OPTIONS,
  1699. useValue: (/** @type {?} */ (ɵ0))
  1700. }
  1701. ]
  1702. },] },
  1703. ];
  1704. /**
  1705. * @fileoverview added by tsickle
  1706. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1707. */
  1708. /**
  1709. * @fileoverview added by tsickle
  1710. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1711. */
  1712. export { MatChipsModule, MatChipListChange, MatChipList, MatChipSelectionChange, MatChipAvatar, MatChipTrailingIcon, MatChip, MatChipRemove, MatChipInput, MAT_CHIPS_DEFAULT_OPTIONS };
  1713. //# sourceMappingURL=chips.js.map