a11y.js 87 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407
  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 { DOCUMENT, CommonModule } from '@angular/common';
  9. import { Inject, Injectable, Optional, SkipSelf, QueryList, Directive, ElementRef, Input, NgZone, isDevMode, InjectionToken, EventEmitter, Output, NgModule, ɵɵdefineInjectable, ɵɵinject } from '@angular/core';
  10. import { Subject, Subscription, of } from 'rxjs';
  11. import { UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, TAB, A, Z, ZERO, NINE, hasModifierKey } from '@angular/cdk/keycodes';
  12. import { debounceTime, filter, map, tap, take } from 'rxjs/operators';
  13. import { Platform, normalizePassiveListenerOptions, PlatformModule } from '@angular/cdk/platform';
  14. import { coerceBooleanProperty, coerceElement } from '@angular/cdk/coercion';
  15. import { ContentObserver, ObserversModule } from '@angular/cdk/observers';
  16. /**
  17. * @fileoverview added by tsickle
  18. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  19. */
  20. /**
  21. * IDs are deliminated by an empty space, as per the spec.
  22. * @type {?}
  23. */
  24. const ID_DELIMINATOR = ' ';
  25. /**
  26. * Adds the given ID to the specified ARIA attribute on an element.
  27. * Used for attributes such as aria-labelledby, aria-owns, etc.
  28. * @param {?} el
  29. * @param {?} attr
  30. * @param {?} id
  31. * @return {?}
  32. */
  33. function addAriaReferencedId(el, attr, id) {
  34. /** @type {?} */
  35. const ids = getAriaReferenceIds(el, attr);
  36. if (ids.some((/**
  37. * @param {?} existingId
  38. * @return {?}
  39. */
  40. existingId => existingId.trim() == id.trim()))) {
  41. return;
  42. }
  43. ids.push(id.trim());
  44. el.setAttribute(attr, ids.join(ID_DELIMINATOR));
  45. }
  46. /**
  47. * Removes the given ID from the specified ARIA attribute on an element.
  48. * Used for attributes such as aria-labelledby, aria-owns, etc.
  49. * @param {?} el
  50. * @param {?} attr
  51. * @param {?} id
  52. * @return {?}
  53. */
  54. function removeAriaReferencedId(el, attr, id) {
  55. /** @type {?} */
  56. const ids = getAriaReferenceIds(el, attr);
  57. /** @type {?} */
  58. const filteredIds = ids.filter((/**
  59. * @param {?} val
  60. * @return {?}
  61. */
  62. val => val != id.trim()));
  63. if (filteredIds.length) {
  64. el.setAttribute(attr, filteredIds.join(ID_DELIMINATOR));
  65. }
  66. else {
  67. el.removeAttribute(attr);
  68. }
  69. }
  70. /**
  71. * Gets the list of IDs referenced by the given ARIA attribute on an element.
  72. * Used for attributes such as aria-labelledby, aria-owns, etc.
  73. * @param {?} el
  74. * @param {?} attr
  75. * @return {?}
  76. */
  77. function getAriaReferenceIds(el, attr) {
  78. // Get string array of all individual ids (whitespace deliminated) in the attribute value
  79. return (el.getAttribute(attr) || '').match(/\S+/g) || [];
  80. }
  81. /**
  82. * @fileoverview added by tsickle
  83. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  84. */
  85. /**
  86. * ID used for the body container where all messages are appended.
  87. * @type {?}
  88. */
  89. const MESSAGES_CONTAINER_ID = 'cdk-describedby-message-container';
  90. /**
  91. * ID prefix used for each created message element.
  92. * @type {?}
  93. */
  94. const CDK_DESCRIBEDBY_ID_PREFIX = 'cdk-describedby-message';
  95. /**
  96. * Attribute given to each host element that is described by a message element.
  97. * @type {?}
  98. */
  99. const CDK_DESCRIBEDBY_HOST_ATTRIBUTE = 'cdk-describedby-host';
  100. /**
  101. * Global incremental identifier for each registered message element.
  102. * @type {?}
  103. */
  104. let nextId = 0;
  105. /**
  106. * Global map of all registered message elements that have been placed into the document.
  107. * @type {?}
  108. */
  109. const messageRegistry = new Map();
  110. /**
  111. * Container for all registered messages.
  112. * @type {?}
  113. */
  114. let messagesContainer = null;
  115. /**
  116. * Utility that creates visually hidden elements with a message content. Useful for elements that
  117. * want to use aria-describedby to further describe themselves without adding additional visual
  118. * content.
  119. */
  120. class AriaDescriber {
  121. /**
  122. * @param {?} _document
  123. */
  124. constructor(_document) {
  125. this._document = _document;
  126. }
  127. /**
  128. * Adds to the host element an aria-describedby reference to a hidden element that contains
  129. * the message. If the same message has already been registered, then it will reuse the created
  130. * message element.
  131. * @param {?} hostElement
  132. * @param {?} message
  133. * @return {?}
  134. */
  135. describe(hostElement, message) {
  136. if (!this._canBeDescribed(hostElement, message)) {
  137. return;
  138. }
  139. if (typeof message !== 'string') {
  140. // We need to ensure that the element has an ID.
  141. this._setMessageId(message);
  142. messageRegistry.set(message, { messageElement: message, referenceCount: 0 });
  143. }
  144. else if (!messageRegistry.has(message)) {
  145. this._createMessageElement(message);
  146. }
  147. if (!this._isElementDescribedByMessage(hostElement, message)) {
  148. this._addMessageReference(hostElement, message);
  149. }
  150. }
  151. /**
  152. * Removes the host element's aria-describedby reference to the message element.
  153. * @param {?} hostElement
  154. * @param {?} message
  155. * @return {?}
  156. */
  157. removeDescription(hostElement, message) {
  158. if (!this._isElementNode(hostElement)) {
  159. return;
  160. }
  161. if (this._isElementDescribedByMessage(hostElement, message)) {
  162. this._removeMessageReference(hostElement, message);
  163. }
  164. // If the message is a string, it means that it's one that we created for the
  165. // consumer so we can remove it safely, otherwise we should leave it in place.
  166. if (typeof message === 'string') {
  167. /** @type {?} */
  168. const registeredMessage = messageRegistry.get(message);
  169. if (registeredMessage && registeredMessage.referenceCount === 0) {
  170. this._deleteMessageElement(message);
  171. }
  172. }
  173. if (messagesContainer && messagesContainer.childNodes.length === 0) {
  174. this._deleteMessagesContainer();
  175. }
  176. }
  177. /**
  178. * Unregisters all created message elements and removes the message container.
  179. * @return {?}
  180. */
  181. ngOnDestroy() {
  182. /** @type {?} */
  183. const describedElements = this._document.querySelectorAll(`[${CDK_DESCRIBEDBY_HOST_ATTRIBUTE}]`);
  184. for (let i = 0; i < describedElements.length; i++) {
  185. this._removeCdkDescribedByReferenceIds(describedElements[i]);
  186. describedElements[i].removeAttribute(CDK_DESCRIBEDBY_HOST_ATTRIBUTE);
  187. }
  188. if (messagesContainer) {
  189. this._deleteMessagesContainer();
  190. }
  191. messageRegistry.clear();
  192. }
  193. /**
  194. * Creates a new element in the visually hidden message container element with the message
  195. * as its content and adds it to the message registry.
  196. * @private
  197. * @param {?} message
  198. * @return {?}
  199. */
  200. _createMessageElement(message) {
  201. /** @type {?} */
  202. const messageElement = this._document.createElement('div');
  203. this._setMessageId(messageElement);
  204. messageElement.textContent = message;
  205. this._createMessagesContainer();
  206. (/** @type {?} */ (messagesContainer)).appendChild(messageElement);
  207. messageRegistry.set(message, { messageElement, referenceCount: 0 });
  208. }
  209. /**
  210. * Assigns a unique ID to an element, if it doesn't have one already.
  211. * @private
  212. * @param {?} element
  213. * @return {?}
  214. */
  215. _setMessageId(element) {
  216. if (!element.id) {
  217. element.id = `${CDK_DESCRIBEDBY_ID_PREFIX}-${nextId++}`;
  218. }
  219. }
  220. /**
  221. * Deletes the message element from the global messages container.
  222. * @private
  223. * @param {?} message
  224. * @return {?}
  225. */
  226. _deleteMessageElement(message) {
  227. /** @type {?} */
  228. const registeredMessage = messageRegistry.get(message);
  229. /** @type {?} */
  230. const messageElement = registeredMessage && registeredMessage.messageElement;
  231. if (messagesContainer && messageElement) {
  232. messagesContainer.removeChild(messageElement);
  233. }
  234. messageRegistry.delete(message);
  235. }
  236. /**
  237. * Creates the global container for all aria-describedby messages.
  238. * @private
  239. * @return {?}
  240. */
  241. _createMessagesContainer() {
  242. if (!messagesContainer) {
  243. /** @type {?} */
  244. const preExistingContainer = this._document.getElementById(MESSAGES_CONTAINER_ID);
  245. // When going from the server to the client, we may end up in a situation where there's
  246. // already a container on the page, but we don't have a reference to it. Clear the
  247. // old container so we don't get duplicates. Doing this, instead of emptying the previous
  248. // container, should be slightly faster.
  249. if (preExistingContainer) {
  250. (/** @type {?} */ (preExistingContainer.parentNode)).removeChild(preExistingContainer);
  251. }
  252. messagesContainer = this._document.createElement('div');
  253. messagesContainer.id = MESSAGES_CONTAINER_ID;
  254. messagesContainer.setAttribute('aria-hidden', 'true');
  255. messagesContainer.style.display = 'none';
  256. this._document.body.appendChild(messagesContainer);
  257. }
  258. }
  259. /**
  260. * Deletes the global messages container.
  261. * @private
  262. * @return {?}
  263. */
  264. _deleteMessagesContainer() {
  265. if (messagesContainer && messagesContainer.parentNode) {
  266. messagesContainer.parentNode.removeChild(messagesContainer);
  267. messagesContainer = null;
  268. }
  269. }
  270. /**
  271. * Removes all cdk-describedby messages that are hosted through the element.
  272. * @private
  273. * @param {?} element
  274. * @return {?}
  275. */
  276. _removeCdkDescribedByReferenceIds(element) {
  277. // Remove all aria-describedby reference IDs that are prefixed by CDK_DESCRIBEDBY_ID_PREFIX
  278. /** @type {?} */
  279. const originalReferenceIds = getAriaReferenceIds(element, 'aria-describedby')
  280. .filter((/**
  281. * @param {?} id
  282. * @return {?}
  283. */
  284. id => id.indexOf(CDK_DESCRIBEDBY_ID_PREFIX) != 0));
  285. element.setAttribute('aria-describedby', originalReferenceIds.join(' '));
  286. }
  287. /**
  288. * Adds a message reference to the element using aria-describedby and increments the registered
  289. * message's reference count.
  290. * @private
  291. * @param {?} element
  292. * @param {?} message
  293. * @return {?}
  294. */
  295. _addMessageReference(element, message) {
  296. /** @type {?} */
  297. const registeredMessage = (/** @type {?} */ (messageRegistry.get(message)));
  298. // Add the aria-describedby reference and set the
  299. // describedby_host attribute to mark the element.
  300. addAriaReferencedId(element, 'aria-describedby', registeredMessage.messageElement.id);
  301. element.setAttribute(CDK_DESCRIBEDBY_HOST_ATTRIBUTE, '');
  302. registeredMessage.referenceCount++;
  303. }
  304. /**
  305. * Removes a message reference from the element using aria-describedby
  306. * and decrements the registered message's reference count.
  307. * @private
  308. * @param {?} element
  309. * @param {?} message
  310. * @return {?}
  311. */
  312. _removeMessageReference(element, message) {
  313. /** @type {?} */
  314. const registeredMessage = (/** @type {?} */ (messageRegistry.get(message)));
  315. registeredMessage.referenceCount--;
  316. removeAriaReferencedId(element, 'aria-describedby', registeredMessage.messageElement.id);
  317. element.removeAttribute(CDK_DESCRIBEDBY_HOST_ATTRIBUTE);
  318. }
  319. /**
  320. * Returns true if the element has been described by the provided message ID.
  321. * @private
  322. * @param {?} element
  323. * @param {?} message
  324. * @return {?}
  325. */
  326. _isElementDescribedByMessage(element, message) {
  327. /** @type {?} */
  328. const referenceIds = getAriaReferenceIds(element, 'aria-describedby');
  329. /** @type {?} */
  330. const registeredMessage = messageRegistry.get(message);
  331. /** @type {?} */
  332. const messageId = registeredMessage && registeredMessage.messageElement.id;
  333. return !!messageId && referenceIds.indexOf(messageId) != -1;
  334. }
  335. /**
  336. * Determines whether a message can be described on a particular element.
  337. * @private
  338. * @param {?} element
  339. * @param {?} message
  340. * @return {?}
  341. */
  342. _canBeDescribed(element, message) {
  343. if (!this._isElementNode(element)) {
  344. return false;
  345. }
  346. if (message && typeof message === 'object') {
  347. // We'd have to make some assumptions about the description element's text, if the consumer
  348. // passed in an element. Assume that if an element is passed in, the consumer has verified
  349. // that it can be used as a description.
  350. return true;
  351. }
  352. /** @type {?} */
  353. const trimmedMessage = message == null ? '' : `${message}`.trim();
  354. /** @type {?} */
  355. const ariaLabel = element.getAttribute('aria-label');
  356. // We shouldn't set descriptions if they're exactly the same as the `aria-label` of the
  357. // element, because screen readers will end up reading out the same text twice in a row.
  358. return trimmedMessage ? (!ariaLabel || ariaLabel.trim() !== trimmedMessage) : false;
  359. }
  360. /**
  361. * Checks whether a node is an Element node.
  362. * @private
  363. * @param {?} element
  364. * @return {?}
  365. */
  366. _isElementNode(element) {
  367. return element.nodeType === this._document.ELEMENT_NODE;
  368. }
  369. }
  370. AriaDescriber.decorators = [
  371. { type: Injectable, args: [{ providedIn: 'root' },] },
  372. ];
  373. /** @nocollapse */
  374. AriaDescriber.ctorParameters = () => [
  375. { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
  376. ];
  377. /** @nocollapse */ AriaDescriber.ngInjectableDef = ɵɵdefineInjectable({ factory: function AriaDescriber_Factory() { return new AriaDescriber(ɵɵinject(DOCUMENT)); }, token: AriaDescriber, providedIn: "root" });
  378. /**
  379. * \@docs-private \@deprecated \@breaking-change 8.0.0
  380. * @param {?} parentDispatcher
  381. * @param {?} _document
  382. * @return {?}
  383. */
  384. function ARIA_DESCRIBER_PROVIDER_FACTORY(parentDispatcher, _document) {
  385. return parentDispatcher || new AriaDescriber(_document);
  386. }
  387. /**
  388. * \@docs-private \@deprecated \@breaking-change 8.0.0
  389. * @type {?}
  390. */
  391. const ARIA_DESCRIBER_PROVIDER = {
  392. // If there is already an AriaDescriber available, use that. Otherwise, provide a new one.
  393. provide: AriaDescriber,
  394. deps: [
  395. [new Optional(), new SkipSelf(), AriaDescriber],
  396. (/** @type {?} */ (DOCUMENT))
  397. ],
  398. useFactory: ARIA_DESCRIBER_PROVIDER_FACTORY
  399. };
  400. /**
  401. * @fileoverview added by tsickle
  402. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  403. */
  404. /**
  405. * This class manages keyboard events for selectable lists. If you pass it a query list
  406. * of items, it will set the active item correctly when arrow events occur.
  407. * @template T
  408. */
  409. class ListKeyManager {
  410. /**
  411. * @param {?} _items
  412. */
  413. constructor(_items) {
  414. this._items = _items;
  415. this._activeItemIndex = -1;
  416. this._activeItem = null;
  417. this._wrap = false;
  418. this._letterKeyStream = new Subject();
  419. this._typeaheadSubscription = Subscription.EMPTY;
  420. this._vertical = true;
  421. this._allowedModifierKeys = [];
  422. /**
  423. * Predicate function that can be used to check whether an item should be skipped
  424. * by the key manager. By default, disabled items are skipped.
  425. */
  426. this._skipPredicateFn = (/**
  427. * @param {?} item
  428. * @return {?}
  429. */
  430. (item) => item.disabled);
  431. // Buffer for the letters that the user has pressed when the typeahead option is turned on.
  432. this._pressedLetters = [];
  433. /**
  434. * Stream that emits any time the TAB key is pressed, so components can react
  435. * when focus is shifted off of the list.
  436. */
  437. this.tabOut = new Subject();
  438. /**
  439. * Stream that emits whenever the active item of the list manager changes.
  440. */
  441. this.change = new Subject();
  442. // We allow for the items to be an array because, in some cases, the consumer may
  443. // not have access to a QueryList of the items they want to manage (e.g. when the
  444. // items aren't being collected via `ViewChildren` or `ContentChildren`).
  445. if (_items instanceof QueryList) {
  446. _items.changes.subscribe((/**
  447. * @param {?} newItems
  448. * @return {?}
  449. */
  450. (newItems) => {
  451. if (this._activeItem) {
  452. /** @type {?} */
  453. const itemArray = newItems.toArray();
  454. /** @type {?} */
  455. const newIndex = itemArray.indexOf(this._activeItem);
  456. if (newIndex > -1 && newIndex !== this._activeItemIndex) {
  457. this._activeItemIndex = newIndex;
  458. }
  459. }
  460. }));
  461. }
  462. }
  463. /**
  464. * Sets the predicate function that determines which items should be skipped by the
  465. * list key manager.
  466. * @template THIS
  467. * @this {THIS}
  468. * @param {?} predicate Function that determines whether the given item should be skipped.
  469. * @return {THIS}
  470. */
  471. skipPredicate(predicate) {
  472. (/** @type {?} */ (this))._skipPredicateFn = predicate;
  473. return (/** @type {?} */ (this));
  474. }
  475. /**
  476. * Configures wrapping mode, which determines whether the active item will wrap to
  477. * the other end of list when there are no more items in the given direction.
  478. * @template THIS
  479. * @this {THIS}
  480. * @param {?=} shouldWrap Whether the list should wrap when reaching the end.
  481. * @return {THIS}
  482. */
  483. withWrap(shouldWrap = true) {
  484. (/** @type {?} */ (this))._wrap = shouldWrap;
  485. return (/** @type {?} */ (this));
  486. }
  487. /**
  488. * Configures whether the key manager should be able to move the selection vertically.
  489. * @template THIS
  490. * @this {THIS}
  491. * @param {?=} enabled Whether vertical selection should be enabled.
  492. * @return {THIS}
  493. */
  494. withVerticalOrientation(enabled = true) {
  495. (/** @type {?} */ (this))._vertical = enabled;
  496. return (/** @type {?} */ (this));
  497. }
  498. /**
  499. * Configures the key manager to move the selection horizontally.
  500. * Passing in `null` will disable horizontal movement.
  501. * @template THIS
  502. * @this {THIS}
  503. * @param {?} direction Direction in which the selection can be moved.
  504. * @return {THIS}
  505. */
  506. withHorizontalOrientation(direction) {
  507. (/** @type {?} */ (this))._horizontal = direction;
  508. return (/** @type {?} */ (this));
  509. }
  510. /**
  511. * Modifier keys which are allowed to be held down and whose default actions will be prevented
  512. * as the user is pressing the arrow keys. Defaults to not allowing any modifier keys.
  513. * @template THIS
  514. * @this {THIS}
  515. * @param {?} keys
  516. * @return {THIS}
  517. */
  518. withAllowedModifierKeys(keys) {
  519. (/** @type {?} */ (this))._allowedModifierKeys = keys;
  520. return (/** @type {?} */ (this));
  521. }
  522. /**
  523. * Turns on typeahead mode which allows users to set the active item by typing.
  524. * @template THIS
  525. * @this {THIS}
  526. * @param {?=} debounceInterval Time to wait after the last keystroke before setting the active item.
  527. * @return {THIS}
  528. */
  529. withTypeAhead(debounceInterval = 200) {
  530. if ((/** @type {?} */ (this))._items.length && (/** @type {?} */ (this))._items.some((/**
  531. * @param {?} item
  532. * @return {?}
  533. */
  534. item => typeof item.getLabel !== 'function'))) {
  535. throw Error('ListKeyManager items in typeahead mode must implement the `getLabel` method.');
  536. }
  537. (/** @type {?} */ (this))._typeaheadSubscription.unsubscribe();
  538. // Debounce the presses of non-navigational keys, collect the ones that correspond to letters
  539. // and convert those letters back into a string. Afterwards find the first item that starts
  540. // with that string and select it.
  541. (/** @type {?} */ (this))._typeaheadSubscription = (/** @type {?} */ (this))._letterKeyStream.pipe(tap((/**
  542. * @param {?} keyCode
  543. * @return {?}
  544. */
  545. keyCode => (/** @type {?} */ (this))._pressedLetters.push(keyCode))), debounceTime(debounceInterval), filter((/**
  546. * @return {?}
  547. */
  548. () => (/** @type {?} */ (this))._pressedLetters.length > 0)), map((/**
  549. * @return {?}
  550. */
  551. () => (/** @type {?} */ (this))._pressedLetters.join('')))).subscribe((/**
  552. * @param {?} inputString
  553. * @return {?}
  554. */
  555. inputString => {
  556. /** @type {?} */
  557. const items = (/** @type {?} */ (this))._getItemsArray();
  558. // Start at 1 because we want to start searching at the item immediately
  559. // following the current active item.
  560. for (let i = 1; i < items.length + 1; i++) {
  561. /** @type {?} */
  562. const index = ((/** @type {?} */ (this))._activeItemIndex + i) % items.length;
  563. /** @type {?} */
  564. const item = items[index];
  565. if (!(/** @type {?} */ (this))._skipPredicateFn(item) &&
  566. (/** @type {?} */ (item.getLabel))().toUpperCase().trim().indexOf(inputString) === 0) {
  567. (/** @type {?} */ (this)).setActiveItem(index);
  568. break;
  569. }
  570. }
  571. (/** @type {?} */ (this))._pressedLetters = [];
  572. }));
  573. return (/** @type {?} */ (this));
  574. }
  575. /**
  576. * @param {?} item
  577. * @return {?}
  578. */
  579. setActiveItem(item) {
  580. /** @type {?} */
  581. const previousIndex = this._activeItemIndex;
  582. this.updateActiveItem(item);
  583. if (this._activeItemIndex !== previousIndex) {
  584. this.change.next(this._activeItemIndex);
  585. }
  586. }
  587. /**
  588. * Sets the active item depending on the key event passed in.
  589. * @param {?} event Keyboard event to be used for determining which element should be active.
  590. * @return {?}
  591. */
  592. onKeydown(event) {
  593. /** @type {?} */
  594. const keyCode = event.keyCode;
  595. /** @type {?} */
  596. const modifiers = ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'];
  597. /** @type {?} */
  598. const isModifierAllowed = modifiers.every((/**
  599. * @param {?} modifier
  600. * @return {?}
  601. */
  602. modifier => {
  603. return !event[modifier] || this._allowedModifierKeys.indexOf(modifier) > -1;
  604. }));
  605. switch (keyCode) {
  606. case TAB:
  607. this.tabOut.next();
  608. return;
  609. case DOWN_ARROW:
  610. if (this._vertical && isModifierAllowed) {
  611. this.setNextItemActive();
  612. break;
  613. }
  614. else {
  615. return;
  616. }
  617. case UP_ARROW:
  618. if (this._vertical && isModifierAllowed) {
  619. this.setPreviousItemActive();
  620. break;
  621. }
  622. else {
  623. return;
  624. }
  625. case RIGHT_ARROW:
  626. if (this._horizontal && isModifierAllowed) {
  627. this._horizontal === 'rtl' ? this.setPreviousItemActive() : this.setNextItemActive();
  628. break;
  629. }
  630. else {
  631. return;
  632. }
  633. case LEFT_ARROW:
  634. if (this._horizontal && isModifierAllowed) {
  635. this._horizontal === 'rtl' ? this.setNextItemActive() : this.setPreviousItemActive();
  636. break;
  637. }
  638. else {
  639. return;
  640. }
  641. default:
  642. if (isModifierAllowed || hasModifierKey(event, 'shiftKey')) {
  643. // Attempt to use the `event.key` which also maps it to the user's keyboard language,
  644. // otherwise fall back to resolving alphanumeric characters via the keyCode.
  645. if (event.key && event.key.length === 1) {
  646. this._letterKeyStream.next(event.key.toLocaleUpperCase());
  647. }
  648. else if ((keyCode >= A && keyCode <= Z) || (keyCode >= ZERO && keyCode <= NINE)) {
  649. this._letterKeyStream.next(String.fromCharCode(keyCode));
  650. }
  651. }
  652. // Note that we return here, in order to avoid preventing
  653. // the default action of non-navigational keys.
  654. return;
  655. }
  656. this._pressedLetters = [];
  657. event.preventDefault();
  658. }
  659. /**
  660. * Index of the currently active item.
  661. * @return {?}
  662. */
  663. get activeItemIndex() {
  664. return this._activeItemIndex;
  665. }
  666. /**
  667. * The active item.
  668. * @return {?}
  669. */
  670. get activeItem() {
  671. return this._activeItem;
  672. }
  673. /**
  674. * Sets the active item to the first enabled item in the list.
  675. * @return {?}
  676. */
  677. setFirstItemActive() {
  678. this._setActiveItemByIndex(0, 1);
  679. }
  680. /**
  681. * Sets the active item to the last enabled item in the list.
  682. * @return {?}
  683. */
  684. setLastItemActive() {
  685. this._setActiveItemByIndex(this._items.length - 1, -1);
  686. }
  687. /**
  688. * Sets the active item to the next enabled item in the list.
  689. * @return {?}
  690. */
  691. setNextItemActive() {
  692. this._activeItemIndex < 0 ? this.setFirstItemActive() : this._setActiveItemByDelta(1);
  693. }
  694. /**
  695. * Sets the active item to a previous enabled item in the list.
  696. * @return {?}
  697. */
  698. setPreviousItemActive() {
  699. this._activeItemIndex < 0 && this._wrap ? this.setLastItemActive()
  700. : this._setActiveItemByDelta(-1);
  701. }
  702. /**
  703. * @param {?} item
  704. * @return {?}
  705. */
  706. updateActiveItem(item) {
  707. /** @type {?} */
  708. const itemArray = this._getItemsArray();
  709. /** @type {?} */
  710. const index = typeof item === 'number' ? item : itemArray.indexOf(item);
  711. /** @type {?} */
  712. const activeItem = itemArray[index];
  713. // Explicitly check for `null` and `undefined` because other falsy values are valid.
  714. this._activeItem = activeItem == null ? null : activeItem;
  715. this._activeItemIndex = index;
  716. }
  717. /**
  718. * Allows setting of the activeItemIndex without any other effects.
  719. * @deprecated Use `updateActiveItem` instead.
  720. * \@breaking-change 8.0.0
  721. * @param {?} index The new activeItemIndex.
  722. * @return {?}
  723. */
  724. updateActiveItemIndex(index) {
  725. this.updateActiveItem(index);
  726. }
  727. /**
  728. * This method sets the active item, given a list of items and the delta between the
  729. * currently active item and the new active item. It will calculate differently
  730. * depending on whether wrap mode is turned on.
  731. * @private
  732. * @param {?} delta
  733. * @return {?}
  734. */
  735. _setActiveItemByDelta(delta) {
  736. this._wrap ? this._setActiveInWrapMode(delta) : this._setActiveInDefaultMode(delta);
  737. }
  738. /**
  739. * Sets the active item properly given "wrap" mode. In other words, it will continue to move
  740. * down the list until it finds an item that is not disabled, and it will wrap if it
  741. * encounters either end of the list.
  742. * @private
  743. * @param {?} delta
  744. * @return {?}
  745. */
  746. _setActiveInWrapMode(delta) {
  747. /** @type {?} */
  748. const items = this._getItemsArray();
  749. for (let i = 1; i <= items.length; i++) {
  750. /** @type {?} */
  751. const index = (this._activeItemIndex + (delta * i) + items.length) % items.length;
  752. /** @type {?} */
  753. const item = items[index];
  754. if (!this._skipPredicateFn(item)) {
  755. this.setActiveItem(index);
  756. return;
  757. }
  758. }
  759. }
  760. /**
  761. * Sets the active item properly given the default mode. In other words, it will
  762. * continue to move down the list until it finds an item that is not disabled. If
  763. * it encounters either end of the list, it will stop and not wrap.
  764. * @private
  765. * @param {?} delta
  766. * @return {?}
  767. */
  768. _setActiveInDefaultMode(delta) {
  769. this._setActiveItemByIndex(this._activeItemIndex + delta, delta);
  770. }
  771. /**
  772. * Sets the active item to the first enabled item starting at the index specified. If the
  773. * item is disabled, it will move in the fallbackDelta direction until it either
  774. * finds an enabled item or encounters the end of the list.
  775. * @private
  776. * @param {?} index
  777. * @param {?} fallbackDelta
  778. * @return {?}
  779. */
  780. _setActiveItemByIndex(index, fallbackDelta) {
  781. /** @type {?} */
  782. const items = this._getItemsArray();
  783. if (!items[index]) {
  784. return;
  785. }
  786. while (this._skipPredicateFn(items[index])) {
  787. index += fallbackDelta;
  788. if (!items[index]) {
  789. return;
  790. }
  791. }
  792. this.setActiveItem(index);
  793. }
  794. /**
  795. * Returns the items as an array.
  796. * @private
  797. * @return {?}
  798. */
  799. _getItemsArray() {
  800. return this._items instanceof QueryList ? this._items.toArray() : this._items;
  801. }
  802. }
  803. /**
  804. * @fileoverview added by tsickle
  805. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  806. */
  807. /**
  808. * @template T
  809. */
  810. class ActiveDescendantKeyManager extends ListKeyManager {
  811. /**
  812. * @param {?} index
  813. * @return {?}
  814. */
  815. setActiveItem(index) {
  816. if (this.activeItem) {
  817. this.activeItem.setInactiveStyles();
  818. }
  819. super.setActiveItem(index);
  820. if (this.activeItem) {
  821. this.activeItem.setActiveStyles();
  822. }
  823. }
  824. }
  825. /**
  826. * @fileoverview added by tsickle
  827. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  828. */
  829. /**
  830. * @template T
  831. */
  832. class FocusKeyManager extends ListKeyManager {
  833. constructor() {
  834. super(...arguments);
  835. this._origin = 'program';
  836. }
  837. /**
  838. * Sets the focus origin that will be passed in to the items for any subsequent `focus` calls.
  839. * @template THIS
  840. * @this {THIS}
  841. * @param {?} origin Focus origin to be used when focusing items.
  842. * @return {THIS}
  843. */
  844. setFocusOrigin(origin) {
  845. (/** @type {?} */ (this))._origin = origin;
  846. return (/** @type {?} */ (this));
  847. }
  848. /**
  849. * @param {?} item
  850. * @return {?}
  851. */
  852. setActiveItem(item) {
  853. super.setActiveItem(item);
  854. if (this.activeItem) {
  855. this.activeItem.focus(this._origin);
  856. }
  857. }
  858. }
  859. /**
  860. * @fileoverview added by tsickle
  861. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  862. */
  863. // The InteractivityChecker leans heavily on the ally.js accessibility utilities.
  864. // Methods like `isTabbable` are only covering specific edge-cases for the browsers which are
  865. // supported.
  866. /**
  867. * Utility for checking the interactivity of an element, such as whether is is focusable or
  868. * tabbable.
  869. */
  870. class InteractivityChecker {
  871. /**
  872. * @param {?} _platform
  873. */
  874. constructor(_platform) {
  875. this._platform = _platform;
  876. }
  877. /**
  878. * Gets whether an element is disabled.
  879. *
  880. * @param {?} element Element to be checked.
  881. * @return {?} Whether the element is disabled.
  882. */
  883. isDisabled(element) {
  884. // This does not capture some cases, such as a non-form control with a disabled attribute or
  885. // a form control inside of a disabled form, but should capture the most common cases.
  886. return element.hasAttribute('disabled');
  887. }
  888. /**
  889. * Gets whether an element is visible for the purposes of interactivity.
  890. *
  891. * This will capture states like `display: none` and `visibility: hidden`, but not things like
  892. * being clipped by an `overflow: hidden` parent or being outside the viewport.
  893. *
  894. * @param {?} element
  895. * @return {?} Whether the element is visible.
  896. */
  897. isVisible(element) {
  898. return hasGeometry(element) && getComputedStyle(element).visibility === 'visible';
  899. }
  900. /**
  901. * Gets whether an element can be reached via Tab key.
  902. * Assumes that the element has already been checked with isFocusable.
  903. *
  904. * @param {?} element Element to be checked.
  905. * @return {?} Whether the element is tabbable.
  906. */
  907. isTabbable(element) {
  908. // Nothing is tabbable on the server 😎
  909. if (!this._platform.isBrowser) {
  910. return false;
  911. }
  912. /** @type {?} */
  913. const frameElement = getFrameElement(getWindow(element));
  914. if (frameElement) {
  915. /** @type {?} */
  916. const frameType = frameElement && frameElement.nodeName.toLowerCase();
  917. // Frame elements inherit their tabindex onto all child elements.
  918. if (getTabIndexValue(frameElement) === -1) {
  919. return false;
  920. }
  921. // Webkit and Blink consider anything inside of an <object> element as non-tabbable.
  922. if ((this._platform.BLINK || this._platform.WEBKIT) && frameType === 'object') {
  923. return false;
  924. }
  925. // Webkit and Blink disable tabbing to an element inside of an invisible frame.
  926. if ((this._platform.BLINK || this._platform.WEBKIT) && !this.isVisible(frameElement)) {
  927. return false;
  928. }
  929. }
  930. /** @type {?} */
  931. let nodeName = element.nodeName.toLowerCase();
  932. /** @type {?} */
  933. let tabIndexValue = getTabIndexValue(element);
  934. if (element.hasAttribute('contenteditable')) {
  935. return tabIndexValue !== -1;
  936. }
  937. if (nodeName === 'iframe') {
  938. // The frames may be tabbable depending on content, but it's not possibly to reliably
  939. // investigate the content of the frames.
  940. return false;
  941. }
  942. if (nodeName === 'audio') {
  943. if (!element.hasAttribute('controls')) {
  944. // By default an <audio> element without the controls enabled is not tabbable.
  945. return false;
  946. }
  947. else if (this._platform.BLINK) {
  948. // In Blink <audio controls> elements are always tabbable.
  949. return true;
  950. }
  951. }
  952. if (nodeName === 'video') {
  953. if (!element.hasAttribute('controls') && this._platform.TRIDENT) {
  954. // In Trident a <video> element without the controls enabled is not tabbable.
  955. return false;
  956. }
  957. else if (this._platform.BLINK || this._platform.FIREFOX) {
  958. // In Chrome and Firefox <video controls> elements are always tabbable.
  959. return true;
  960. }
  961. }
  962. if (nodeName === 'object' && (this._platform.BLINK || this._platform.WEBKIT)) {
  963. // In all Blink and WebKit based browsers <object> elements are never tabbable.
  964. return false;
  965. }
  966. // In iOS the browser only considers some specific elements as tabbable.
  967. if (this._platform.WEBKIT && this._platform.IOS && !isPotentiallyTabbableIOS(element)) {
  968. return false;
  969. }
  970. return element.tabIndex >= 0;
  971. }
  972. /**
  973. * Gets whether an element can be focused by the user.
  974. *
  975. * @param {?} element Element to be checked.
  976. * @return {?} Whether the element is focusable.
  977. */
  978. isFocusable(element) {
  979. // Perform checks in order of left to most expensive.
  980. // Again, naive approach that does not capture many edge cases and browser quirks.
  981. return isPotentiallyFocusable(element) && !this.isDisabled(element) && this.isVisible(element);
  982. }
  983. }
  984. InteractivityChecker.decorators = [
  985. { type: Injectable, args: [{ providedIn: 'root' },] },
  986. ];
  987. /** @nocollapse */
  988. InteractivityChecker.ctorParameters = () => [
  989. { type: Platform }
  990. ];
  991. /** @nocollapse */ InteractivityChecker.ngInjectableDef = ɵɵdefineInjectable({ factory: function InteractivityChecker_Factory() { return new InteractivityChecker(ɵɵinject(Platform)); }, token: InteractivityChecker, providedIn: "root" });
  992. /**
  993. * Returns the frame element from a window object. Since browsers like MS Edge throw errors if
  994. * the frameElement property is being accessed from a different host address, this property
  995. * should be accessed carefully.
  996. * @param {?} window
  997. * @return {?}
  998. */
  999. function getFrameElement(window) {
  1000. try {
  1001. return (/** @type {?} */ (window.frameElement));
  1002. }
  1003. catch (_a) {
  1004. return null;
  1005. }
  1006. }
  1007. /**
  1008. * Checks whether the specified element has any geometry / rectangles.
  1009. * @param {?} element
  1010. * @return {?}
  1011. */
  1012. function hasGeometry(element) {
  1013. // Use logic from jQuery to check for an invisible element.
  1014. // See https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js#L12
  1015. return !!(element.offsetWidth || element.offsetHeight ||
  1016. (typeof element.getClientRects === 'function' && element.getClientRects().length));
  1017. }
  1018. /**
  1019. * Gets whether an element's
  1020. * @param {?} element
  1021. * @return {?}
  1022. */
  1023. function isNativeFormElement(element) {
  1024. /** @type {?} */
  1025. let nodeName = element.nodeName.toLowerCase();
  1026. return nodeName === 'input' ||
  1027. nodeName === 'select' ||
  1028. nodeName === 'button' ||
  1029. nodeName === 'textarea';
  1030. }
  1031. /**
  1032. * Gets whether an element is an `<input type="hidden">`.
  1033. * @param {?} element
  1034. * @return {?}
  1035. */
  1036. function isHiddenInput(element) {
  1037. return isInputElement(element) && element.type == 'hidden';
  1038. }
  1039. /**
  1040. * Gets whether an element is an anchor that has an href attribute.
  1041. * @param {?} element
  1042. * @return {?}
  1043. */
  1044. function isAnchorWithHref(element) {
  1045. return isAnchorElement(element) && element.hasAttribute('href');
  1046. }
  1047. /**
  1048. * Gets whether an element is an input element.
  1049. * @param {?} element
  1050. * @return {?}
  1051. */
  1052. function isInputElement(element) {
  1053. return element.nodeName.toLowerCase() == 'input';
  1054. }
  1055. /**
  1056. * Gets whether an element is an anchor element.
  1057. * @param {?} element
  1058. * @return {?}
  1059. */
  1060. function isAnchorElement(element) {
  1061. return element.nodeName.toLowerCase() == 'a';
  1062. }
  1063. /**
  1064. * Gets whether an element has a valid tabindex.
  1065. * @param {?} element
  1066. * @return {?}
  1067. */
  1068. function hasValidTabIndex(element) {
  1069. if (!element.hasAttribute('tabindex') || element.tabIndex === undefined) {
  1070. return false;
  1071. }
  1072. /** @type {?} */
  1073. let tabIndex = element.getAttribute('tabindex');
  1074. // IE11 parses tabindex="" as the value "-32768"
  1075. if (tabIndex == '-32768') {
  1076. return false;
  1077. }
  1078. return !!(tabIndex && !isNaN(parseInt(tabIndex, 10)));
  1079. }
  1080. /**
  1081. * Returns the parsed tabindex from the element attributes instead of returning the
  1082. * evaluated tabindex from the browsers defaults.
  1083. * @param {?} element
  1084. * @return {?}
  1085. */
  1086. function getTabIndexValue(element) {
  1087. if (!hasValidTabIndex(element)) {
  1088. return null;
  1089. }
  1090. // See browser issue in Gecko https://bugzilla.mozilla.org/show_bug.cgi?id=1128054
  1091. /** @type {?} */
  1092. const tabIndex = parseInt(element.getAttribute('tabindex') || '', 10);
  1093. return isNaN(tabIndex) ? -1 : tabIndex;
  1094. }
  1095. /**
  1096. * Checks whether the specified element is potentially tabbable on iOS
  1097. * @param {?} element
  1098. * @return {?}
  1099. */
  1100. function isPotentiallyTabbableIOS(element) {
  1101. /** @type {?} */
  1102. let nodeName = element.nodeName.toLowerCase();
  1103. /** @type {?} */
  1104. let inputType = nodeName === 'input' && ((/** @type {?} */ (element))).type;
  1105. return inputType === 'text'
  1106. || inputType === 'password'
  1107. || nodeName === 'select'
  1108. || nodeName === 'textarea';
  1109. }
  1110. /**
  1111. * Gets whether an element is potentially focusable without taking current visible/disabled state
  1112. * into account.
  1113. * @param {?} element
  1114. * @return {?}
  1115. */
  1116. function isPotentiallyFocusable(element) {
  1117. // Inputs are potentially focusable *unless* they're type="hidden".
  1118. if (isHiddenInput(element)) {
  1119. return false;
  1120. }
  1121. return isNativeFormElement(element) ||
  1122. isAnchorWithHref(element) ||
  1123. element.hasAttribute('contenteditable') ||
  1124. hasValidTabIndex(element);
  1125. }
  1126. /**
  1127. * Gets the parent window of a DOM node with regards of being inside of an iframe.
  1128. * @param {?} node
  1129. * @return {?}
  1130. */
  1131. function getWindow(node) {
  1132. // ownerDocument is null if `node` itself *is* a document.
  1133. return node.ownerDocument && node.ownerDocument.defaultView || window;
  1134. }
  1135. /**
  1136. * @fileoverview added by tsickle
  1137. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1138. */
  1139. /**
  1140. * Class that allows for trapping focus within a DOM element.
  1141. *
  1142. * This class currently uses a relatively simple approach to focus trapping.
  1143. * It assumes that the tab order is the same as DOM order, which is not necessarily true.
  1144. * Things like `tabIndex > 0`, flex `order`, and shadow roots can cause to two to misalign.
  1145. */
  1146. class FocusTrap {
  1147. /**
  1148. * @param {?} _element
  1149. * @param {?} _checker
  1150. * @param {?} _ngZone
  1151. * @param {?} _document
  1152. * @param {?=} deferAnchors
  1153. */
  1154. constructor(_element, _checker, _ngZone, _document, deferAnchors = false) {
  1155. this._element = _element;
  1156. this._checker = _checker;
  1157. this._ngZone = _ngZone;
  1158. this._document = _document;
  1159. this._hasAttached = false;
  1160. // Event listeners for the anchors. Need to be regular functions so that we can unbind them later.
  1161. this.startAnchorListener = (/**
  1162. * @return {?}
  1163. */
  1164. () => this.focusLastTabbableElement());
  1165. this.endAnchorListener = (/**
  1166. * @return {?}
  1167. */
  1168. () => this.focusFirstTabbableElement());
  1169. this._enabled = true;
  1170. if (!deferAnchors) {
  1171. this.attachAnchors();
  1172. }
  1173. }
  1174. /**
  1175. * Whether the focus trap is active.
  1176. * @return {?}
  1177. */
  1178. get enabled() { return this._enabled; }
  1179. /**
  1180. * @param {?} value
  1181. * @return {?}
  1182. */
  1183. set enabled(value) {
  1184. this._enabled = value;
  1185. if (this._startAnchor && this._endAnchor) {
  1186. this._toggleAnchorTabIndex(value, this._startAnchor);
  1187. this._toggleAnchorTabIndex(value, this._endAnchor);
  1188. }
  1189. }
  1190. /**
  1191. * Destroys the focus trap by cleaning up the anchors.
  1192. * @return {?}
  1193. */
  1194. destroy() {
  1195. /** @type {?} */
  1196. const startAnchor = this._startAnchor;
  1197. /** @type {?} */
  1198. const endAnchor = this._endAnchor;
  1199. if (startAnchor) {
  1200. startAnchor.removeEventListener('focus', this.startAnchorListener);
  1201. if (startAnchor.parentNode) {
  1202. startAnchor.parentNode.removeChild(startAnchor);
  1203. }
  1204. }
  1205. if (endAnchor) {
  1206. endAnchor.removeEventListener('focus', this.endAnchorListener);
  1207. if (endAnchor.parentNode) {
  1208. endAnchor.parentNode.removeChild(endAnchor);
  1209. }
  1210. }
  1211. this._startAnchor = this._endAnchor = null;
  1212. }
  1213. /**
  1214. * Inserts the anchors into the DOM. This is usually done automatically
  1215. * in the constructor, but can be deferred for cases like directives with `*ngIf`.
  1216. * @return {?} Whether the focus trap managed to attach successfuly. This may not be the case
  1217. * if the target element isn't currently in the DOM.
  1218. */
  1219. attachAnchors() {
  1220. // If we're not on the browser, there can be no focus to trap.
  1221. if (this._hasAttached) {
  1222. return true;
  1223. }
  1224. this._ngZone.runOutsideAngular((/**
  1225. * @return {?}
  1226. */
  1227. () => {
  1228. if (!this._startAnchor) {
  1229. this._startAnchor = this._createAnchor();
  1230. (/** @type {?} */ (this._startAnchor)).addEventListener('focus', this.startAnchorListener);
  1231. }
  1232. if (!this._endAnchor) {
  1233. this._endAnchor = this._createAnchor();
  1234. (/** @type {?} */ (this._endAnchor)).addEventListener('focus', this.endAnchorListener);
  1235. }
  1236. }));
  1237. if (this._element.parentNode) {
  1238. this._element.parentNode.insertBefore((/** @type {?} */ (this._startAnchor)), this._element);
  1239. this._element.parentNode.insertBefore((/** @type {?} */ (this._endAnchor)), this._element.nextSibling);
  1240. this._hasAttached = true;
  1241. }
  1242. return this._hasAttached;
  1243. }
  1244. /**
  1245. * Waits for the zone to stabilize, then either focuses the first element that the
  1246. * user specified, or the first tabbable element.
  1247. * @return {?} Returns a promise that resolves with a boolean, depending
  1248. * on whether focus was moved successfuly.
  1249. */
  1250. focusInitialElementWhenReady() {
  1251. return new Promise((/**
  1252. * @param {?} resolve
  1253. * @return {?}
  1254. */
  1255. resolve => {
  1256. this._executeOnStable((/**
  1257. * @return {?}
  1258. */
  1259. () => resolve(this.focusInitialElement())));
  1260. }));
  1261. }
  1262. /**
  1263. * Waits for the zone to stabilize, then focuses
  1264. * the first tabbable element within the focus trap region.
  1265. * @return {?} Returns a promise that resolves with a boolean, depending
  1266. * on whether focus was moved successfuly.
  1267. */
  1268. focusFirstTabbableElementWhenReady() {
  1269. return new Promise((/**
  1270. * @param {?} resolve
  1271. * @return {?}
  1272. */
  1273. resolve => {
  1274. this._executeOnStable((/**
  1275. * @return {?}
  1276. */
  1277. () => resolve(this.focusFirstTabbableElement())));
  1278. }));
  1279. }
  1280. /**
  1281. * Waits for the zone to stabilize, then focuses
  1282. * the last tabbable element within the focus trap region.
  1283. * @return {?} Returns a promise that resolves with a boolean, depending
  1284. * on whether focus was moved successfuly.
  1285. */
  1286. focusLastTabbableElementWhenReady() {
  1287. return new Promise((/**
  1288. * @param {?} resolve
  1289. * @return {?}
  1290. */
  1291. resolve => {
  1292. this._executeOnStable((/**
  1293. * @return {?}
  1294. */
  1295. () => resolve(this.focusLastTabbableElement())));
  1296. }));
  1297. }
  1298. /**
  1299. * Get the specified boundary element of the trapped region.
  1300. * @private
  1301. * @param {?} bound The boundary to get (start or end of trapped region).
  1302. * @return {?} The boundary element.
  1303. */
  1304. _getRegionBoundary(bound) {
  1305. // Contains the deprecated version of selector, for temporary backwards comparability.
  1306. /** @type {?} */
  1307. let markers = (/** @type {?} */ (this._element.querySelectorAll(`[cdk-focus-region-${bound}], ` +
  1308. `[cdkFocusRegion${bound}], ` +
  1309. `[cdk-focus-${bound}]`)));
  1310. for (let i = 0; i < markers.length; i++) {
  1311. // @breaking-change 8.0.0
  1312. if (markers[i].hasAttribute(`cdk-focus-${bound}`)) {
  1313. console.warn(`Found use of deprecated attribute 'cdk-focus-${bound}', ` +
  1314. `use 'cdkFocusRegion${bound}' instead. The deprecated ` +
  1315. `attribute will be removed in 8.0.0.`, markers[i]);
  1316. }
  1317. else if (markers[i].hasAttribute(`cdk-focus-region-${bound}`)) {
  1318. console.warn(`Found use of deprecated attribute 'cdk-focus-region-${bound}', ` +
  1319. `use 'cdkFocusRegion${bound}' instead. The deprecated attribute ` +
  1320. `will be removed in 8.0.0.`, markers[i]);
  1321. }
  1322. }
  1323. if (bound == 'start') {
  1324. return markers.length ? markers[0] : this._getFirstTabbableElement(this._element);
  1325. }
  1326. return markers.length ?
  1327. markers[markers.length - 1] : this._getLastTabbableElement(this._element);
  1328. }
  1329. /**
  1330. * Focuses the element that should be focused when the focus trap is initialized.
  1331. * @return {?} Whether focus was moved successfuly.
  1332. */
  1333. focusInitialElement() {
  1334. // Contains the deprecated version of selector, for temporary backwards comparability.
  1335. /** @type {?} */
  1336. const redirectToElement = (/** @type {?} */ (this._element.querySelector(`[cdk-focus-initial], ` +
  1337. `[cdkFocusInitial]`)));
  1338. if (redirectToElement) {
  1339. // @breaking-change 8.0.0
  1340. if (redirectToElement.hasAttribute(`cdk-focus-initial`)) {
  1341. console.warn(`Found use of deprecated attribute 'cdk-focus-initial', ` +
  1342. `use 'cdkFocusInitial' instead. The deprecated attribute ` +
  1343. `will be removed in 8.0.0`, redirectToElement);
  1344. }
  1345. // Warn the consumer if the element they've pointed to
  1346. // isn't focusable, when not in production mode.
  1347. if (isDevMode() && !this._checker.isFocusable(redirectToElement)) {
  1348. console.warn(`Element matching '[cdkFocusInitial]' is not focusable.`, redirectToElement);
  1349. }
  1350. redirectToElement.focus();
  1351. return true;
  1352. }
  1353. return this.focusFirstTabbableElement();
  1354. }
  1355. /**
  1356. * Focuses the first tabbable element within the focus trap region.
  1357. * @return {?} Whether focus was moved successfuly.
  1358. */
  1359. focusFirstTabbableElement() {
  1360. /** @type {?} */
  1361. const redirectToElement = this._getRegionBoundary('start');
  1362. if (redirectToElement) {
  1363. redirectToElement.focus();
  1364. }
  1365. return !!redirectToElement;
  1366. }
  1367. /**
  1368. * Focuses the last tabbable element within the focus trap region.
  1369. * @return {?} Whether focus was moved successfuly.
  1370. */
  1371. focusLastTabbableElement() {
  1372. /** @type {?} */
  1373. const redirectToElement = this._getRegionBoundary('end');
  1374. if (redirectToElement) {
  1375. redirectToElement.focus();
  1376. }
  1377. return !!redirectToElement;
  1378. }
  1379. /**
  1380. * Checks whether the focus trap has successfuly been attached.
  1381. * @return {?}
  1382. */
  1383. hasAttached() {
  1384. return this._hasAttached;
  1385. }
  1386. /**
  1387. * Get the first tabbable element from a DOM subtree (inclusive).
  1388. * @private
  1389. * @param {?} root
  1390. * @return {?}
  1391. */
  1392. _getFirstTabbableElement(root) {
  1393. if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) {
  1394. return root;
  1395. }
  1396. // Iterate in DOM order. Note that IE doesn't have `children` for SVG so we fall
  1397. // back to `childNodes` which includes text nodes, comments etc.
  1398. /** @type {?} */
  1399. let children = root.children || root.childNodes;
  1400. for (let i = 0; i < children.length; i++) {
  1401. /** @type {?} */
  1402. let tabbableChild = children[i].nodeType === this._document.ELEMENT_NODE ?
  1403. this._getFirstTabbableElement((/** @type {?} */ (children[i]))) :
  1404. null;
  1405. if (tabbableChild) {
  1406. return tabbableChild;
  1407. }
  1408. }
  1409. return null;
  1410. }
  1411. /**
  1412. * Get the last tabbable element from a DOM subtree (inclusive).
  1413. * @private
  1414. * @param {?} root
  1415. * @return {?}
  1416. */
  1417. _getLastTabbableElement(root) {
  1418. if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) {
  1419. return root;
  1420. }
  1421. // Iterate in reverse DOM order.
  1422. /** @type {?} */
  1423. let children = root.children || root.childNodes;
  1424. for (let i = children.length - 1; i >= 0; i--) {
  1425. /** @type {?} */
  1426. let tabbableChild = children[i].nodeType === this._document.ELEMENT_NODE ?
  1427. this._getLastTabbableElement((/** @type {?} */ (children[i]))) :
  1428. null;
  1429. if (tabbableChild) {
  1430. return tabbableChild;
  1431. }
  1432. }
  1433. return null;
  1434. }
  1435. /**
  1436. * Creates an anchor element.
  1437. * @private
  1438. * @return {?}
  1439. */
  1440. _createAnchor() {
  1441. /** @type {?} */
  1442. const anchor = this._document.createElement('div');
  1443. this._toggleAnchorTabIndex(this._enabled, anchor);
  1444. anchor.classList.add('cdk-visually-hidden');
  1445. anchor.classList.add('cdk-focus-trap-anchor');
  1446. anchor.setAttribute('aria-hidden', 'true');
  1447. return anchor;
  1448. }
  1449. /**
  1450. * Toggles the `tabindex` of an anchor, based on the enabled state of the focus trap.
  1451. * @private
  1452. * @param {?} isEnabled Whether the focus trap is enabled.
  1453. * @param {?} anchor Anchor on which to toggle the tabindex.
  1454. * @return {?}
  1455. */
  1456. _toggleAnchorTabIndex(isEnabled, anchor) {
  1457. // Remove the tabindex completely, rather than setting it to -1, because if the
  1458. // element has a tabindex, the user might still hit it when navigating with the arrow keys.
  1459. isEnabled ? anchor.setAttribute('tabindex', '0') : anchor.removeAttribute('tabindex');
  1460. }
  1461. /**
  1462. * Executes a function when the zone is stable.
  1463. * @private
  1464. * @param {?} fn
  1465. * @return {?}
  1466. */
  1467. _executeOnStable(fn) {
  1468. if (this._ngZone.isStable) {
  1469. fn();
  1470. }
  1471. else {
  1472. this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(fn);
  1473. }
  1474. }
  1475. }
  1476. /**
  1477. * Factory that allows easy instantiation of focus traps.
  1478. */
  1479. class FocusTrapFactory {
  1480. /**
  1481. * @param {?} _checker
  1482. * @param {?} _ngZone
  1483. * @param {?} _document
  1484. */
  1485. constructor(_checker, _ngZone, _document) {
  1486. this._checker = _checker;
  1487. this._ngZone = _ngZone;
  1488. this._document = _document;
  1489. }
  1490. /**
  1491. * Creates a focus-trapped region around the given element.
  1492. * @param {?} element The element around which focus will be trapped.
  1493. * @param {?=} deferCaptureElements Defers the creation of focus-capturing elements to be done
  1494. * manually by the user.
  1495. * @return {?} The created focus trap instance.
  1496. */
  1497. create(element, deferCaptureElements = false) {
  1498. return new FocusTrap(element, this._checker, this._ngZone, this._document, deferCaptureElements);
  1499. }
  1500. }
  1501. FocusTrapFactory.decorators = [
  1502. { type: Injectable, args: [{ providedIn: 'root' },] },
  1503. ];
  1504. /** @nocollapse */
  1505. FocusTrapFactory.ctorParameters = () => [
  1506. { type: InteractivityChecker },
  1507. { type: NgZone },
  1508. { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
  1509. ];
  1510. /** @nocollapse */ FocusTrapFactory.ngInjectableDef = ɵɵdefineInjectable({ factory: function FocusTrapFactory_Factory() { return new FocusTrapFactory(ɵɵinject(InteractivityChecker), ɵɵinject(NgZone), ɵɵinject(DOCUMENT)); }, token: FocusTrapFactory, providedIn: "root" });
  1511. /**
  1512. * Directive for trapping focus within a region.
  1513. */
  1514. class CdkTrapFocus {
  1515. /**
  1516. * @param {?} _elementRef
  1517. * @param {?} _focusTrapFactory
  1518. * @param {?} _document
  1519. */
  1520. constructor(_elementRef, _focusTrapFactory, _document) {
  1521. this._elementRef = _elementRef;
  1522. this._focusTrapFactory = _focusTrapFactory;
  1523. /**
  1524. * Previously focused element to restore focus to upon destroy when using autoCapture.
  1525. */
  1526. this._previouslyFocusedElement = null;
  1527. this._document = _document;
  1528. this.focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement, true);
  1529. }
  1530. /**
  1531. * Whether the focus trap is active.
  1532. * @return {?}
  1533. */
  1534. get enabled() { return this.focusTrap.enabled; }
  1535. /**
  1536. * @param {?} value
  1537. * @return {?}
  1538. */
  1539. set enabled(value) { this.focusTrap.enabled = coerceBooleanProperty(value); }
  1540. /**
  1541. * Whether the directive should automatially move focus into the trapped region upon
  1542. * initialization and return focus to the previous activeElement upon destruction.
  1543. * @return {?}
  1544. */
  1545. get autoCapture() { return this._autoCapture; }
  1546. /**
  1547. * @param {?} value
  1548. * @return {?}
  1549. */
  1550. set autoCapture(value) { this._autoCapture = coerceBooleanProperty(value); }
  1551. /**
  1552. * @return {?}
  1553. */
  1554. ngOnDestroy() {
  1555. this.focusTrap.destroy();
  1556. // If we stored a previously focused element when using autoCapture, return focus to that
  1557. // element now that the trapped region is being destroyed.
  1558. if (this._previouslyFocusedElement) {
  1559. this._previouslyFocusedElement.focus();
  1560. this._previouslyFocusedElement = null;
  1561. }
  1562. }
  1563. /**
  1564. * @return {?}
  1565. */
  1566. ngAfterContentInit() {
  1567. this.focusTrap.attachAnchors();
  1568. if (this.autoCapture) {
  1569. this._previouslyFocusedElement = (/** @type {?} */ (this._document.activeElement));
  1570. this.focusTrap.focusInitialElementWhenReady();
  1571. }
  1572. }
  1573. /**
  1574. * @return {?}
  1575. */
  1576. ngDoCheck() {
  1577. if (!this.focusTrap.hasAttached()) {
  1578. this.focusTrap.attachAnchors();
  1579. }
  1580. }
  1581. }
  1582. CdkTrapFocus.decorators = [
  1583. { type: Directive, args: [{
  1584. selector: '[cdkTrapFocus]',
  1585. exportAs: 'cdkTrapFocus',
  1586. },] },
  1587. ];
  1588. /** @nocollapse */
  1589. CdkTrapFocus.ctorParameters = () => [
  1590. { type: ElementRef },
  1591. { type: FocusTrapFactory },
  1592. { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
  1593. ];
  1594. CdkTrapFocus.propDecorators = {
  1595. enabled: [{ type: Input, args: ['cdkTrapFocus',] }],
  1596. autoCapture: [{ type: Input, args: ['cdkTrapFocusAutoCapture',] }]
  1597. };
  1598. /**
  1599. * @fileoverview added by tsickle
  1600. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1601. */
  1602. /** @type {?} */
  1603. const LIVE_ANNOUNCER_ELEMENT_TOKEN = new InjectionToken('liveAnnouncerElement', {
  1604. providedIn: 'root',
  1605. factory: LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY,
  1606. });
  1607. /**
  1608. * \@docs-private
  1609. * @return {?}
  1610. */
  1611. function LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY() {
  1612. return null;
  1613. }
  1614. /**
  1615. * Injection token that can be used to configure the default options for the LiveAnnouncer.
  1616. * @type {?}
  1617. */
  1618. const LIVE_ANNOUNCER_DEFAULT_OPTIONS = new InjectionToken('LIVE_ANNOUNCER_DEFAULT_OPTIONS');
  1619. /**
  1620. * @fileoverview added by tsickle
  1621. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1622. */
  1623. class LiveAnnouncer {
  1624. /**
  1625. * @param {?} elementToken
  1626. * @param {?} _ngZone
  1627. * @param {?} _document
  1628. * @param {?=} _defaultOptions
  1629. */
  1630. constructor(elementToken, _ngZone, _document, _defaultOptions) {
  1631. this._ngZone = _ngZone;
  1632. this._defaultOptions = _defaultOptions;
  1633. // We inject the live element and document as `any` because the constructor signature cannot
  1634. // reference browser globals (HTMLElement, Document) on non-browser environments, since having
  1635. // a class decorator causes TypeScript to preserve the constructor signature types.
  1636. this._document = _document;
  1637. this._liveElement = elementToken || this._createLiveElement();
  1638. }
  1639. /**
  1640. * @param {?} message
  1641. * @param {...?} args
  1642. * @return {?}
  1643. */
  1644. announce(message, ...args) {
  1645. /** @type {?} */
  1646. const defaultOptions = this._defaultOptions;
  1647. /** @type {?} */
  1648. let politeness;
  1649. /** @type {?} */
  1650. let duration;
  1651. if (args.length === 1 && typeof args[0] === 'number') {
  1652. duration = args[0];
  1653. }
  1654. else {
  1655. [politeness, duration] = args;
  1656. }
  1657. this.clear();
  1658. clearTimeout(this._previousTimeout);
  1659. if (!politeness) {
  1660. politeness =
  1661. (defaultOptions && defaultOptions.politeness) ? defaultOptions.politeness : 'polite';
  1662. }
  1663. if (duration == null && defaultOptions) {
  1664. duration = defaultOptions.duration;
  1665. }
  1666. // TODO: ensure changing the politeness works on all environments we support.
  1667. this._liveElement.setAttribute('aria-live', politeness);
  1668. // This 100ms timeout is necessary for some browser + screen-reader combinations:
  1669. // - Both JAWS and NVDA over IE11 will not announce anything without a non-zero timeout.
  1670. // - With Chrome and IE11 with NVDA or JAWS, a repeated (identical) message won't be read a
  1671. // second time without clearing and then using a non-zero delay.
  1672. // (using JAWS 17 at time of this writing).
  1673. return this._ngZone.runOutsideAngular((/**
  1674. * @return {?}
  1675. */
  1676. () => {
  1677. return new Promise((/**
  1678. * @param {?} resolve
  1679. * @return {?}
  1680. */
  1681. resolve => {
  1682. clearTimeout(this._previousTimeout);
  1683. this._previousTimeout = setTimeout((/**
  1684. * @return {?}
  1685. */
  1686. () => {
  1687. this._liveElement.textContent = message;
  1688. resolve();
  1689. if (typeof duration === 'number') {
  1690. this._previousTimeout = setTimeout((/**
  1691. * @return {?}
  1692. */
  1693. () => this.clear()), duration);
  1694. }
  1695. }), 100);
  1696. }));
  1697. }));
  1698. }
  1699. /**
  1700. * Clears the current text from the announcer element. Can be used to prevent
  1701. * screen readers from reading the text out again while the user is going
  1702. * through the page landmarks.
  1703. * @return {?}
  1704. */
  1705. clear() {
  1706. if (this._liveElement) {
  1707. this._liveElement.textContent = '';
  1708. }
  1709. }
  1710. /**
  1711. * @return {?}
  1712. */
  1713. ngOnDestroy() {
  1714. clearTimeout(this._previousTimeout);
  1715. if (this._liveElement && this._liveElement.parentNode) {
  1716. this._liveElement.parentNode.removeChild(this._liveElement);
  1717. this._liveElement = (/** @type {?} */ (null));
  1718. }
  1719. }
  1720. /**
  1721. * @private
  1722. * @return {?}
  1723. */
  1724. _createLiveElement() {
  1725. /** @type {?} */
  1726. const elementClass = 'cdk-live-announcer-element';
  1727. /** @type {?} */
  1728. const previousElements = this._document.getElementsByClassName(elementClass);
  1729. /** @type {?} */
  1730. const liveEl = this._document.createElement('div');
  1731. // Remove any old containers. This can happen when coming in from a server-side-rendered page.
  1732. for (let i = 0; i < previousElements.length; i++) {
  1733. (/** @type {?} */ (previousElements[i].parentNode)).removeChild(previousElements[i]);
  1734. }
  1735. liveEl.classList.add(elementClass);
  1736. liveEl.classList.add('cdk-visually-hidden');
  1737. liveEl.setAttribute('aria-atomic', 'true');
  1738. liveEl.setAttribute('aria-live', 'polite');
  1739. this._document.body.appendChild(liveEl);
  1740. return liveEl;
  1741. }
  1742. }
  1743. LiveAnnouncer.decorators = [
  1744. { type: Injectable, args: [{ providedIn: 'root' },] },
  1745. ];
  1746. /** @nocollapse */
  1747. LiveAnnouncer.ctorParameters = () => [
  1748. { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [LIVE_ANNOUNCER_ELEMENT_TOKEN,] }] },
  1749. { type: NgZone },
  1750. { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
  1751. { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [LIVE_ANNOUNCER_DEFAULT_OPTIONS,] }] }
  1752. ];
  1753. /** @nocollapse */ LiveAnnouncer.ngInjectableDef = ɵɵdefineInjectable({ factory: function LiveAnnouncer_Factory() { return new LiveAnnouncer(ɵɵinject(LIVE_ANNOUNCER_ELEMENT_TOKEN, 8), ɵɵinject(NgZone), ɵɵinject(DOCUMENT), ɵɵinject(LIVE_ANNOUNCER_DEFAULT_OPTIONS, 8)); }, token: LiveAnnouncer, providedIn: "root" });
  1754. /**
  1755. * A directive that works similarly to aria-live, but uses the LiveAnnouncer to ensure compatibility
  1756. * with a wider range of browsers and screen readers.
  1757. */
  1758. class CdkAriaLive {
  1759. /**
  1760. * @param {?} _elementRef
  1761. * @param {?} _liveAnnouncer
  1762. * @param {?} _contentObserver
  1763. * @param {?} _ngZone
  1764. */
  1765. constructor(_elementRef, _liveAnnouncer, _contentObserver, _ngZone) {
  1766. this._elementRef = _elementRef;
  1767. this._liveAnnouncer = _liveAnnouncer;
  1768. this._contentObserver = _contentObserver;
  1769. this._ngZone = _ngZone;
  1770. this._politeness = 'off';
  1771. }
  1772. /**
  1773. * The aria-live politeness level to use when announcing messages.
  1774. * @return {?}
  1775. */
  1776. get politeness() { return this._politeness; }
  1777. /**
  1778. * @param {?} value
  1779. * @return {?}
  1780. */
  1781. set politeness(value) {
  1782. this._politeness = value === 'polite' || value === 'assertive' ? value : 'off';
  1783. if (this._politeness === 'off') {
  1784. if (this._subscription) {
  1785. this._subscription.unsubscribe();
  1786. this._subscription = null;
  1787. }
  1788. }
  1789. else if (!this._subscription) {
  1790. this._subscription = this._ngZone.runOutsideAngular((/**
  1791. * @return {?}
  1792. */
  1793. () => {
  1794. return this._contentObserver
  1795. .observe(this._elementRef)
  1796. .subscribe((/**
  1797. * @return {?}
  1798. */
  1799. () => {
  1800. // Note that we use textContent here, rather than innerText, in order to avoid a reflow.
  1801. /** @type {?} */
  1802. const elementText = this._elementRef.nativeElement.textContent;
  1803. // The `MutationObserver` fires also for attribute
  1804. // changes which we don't want to announce.
  1805. if (elementText !== this._previousAnnouncedText) {
  1806. this._liveAnnouncer.announce(elementText, this._politeness);
  1807. this._previousAnnouncedText = elementText;
  1808. }
  1809. }));
  1810. }));
  1811. }
  1812. }
  1813. /**
  1814. * @return {?}
  1815. */
  1816. ngOnDestroy() {
  1817. if (this._subscription) {
  1818. this._subscription.unsubscribe();
  1819. }
  1820. }
  1821. }
  1822. CdkAriaLive.decorators = [
  1823. { type: Directive, args: [{
  1824. selector: '[cdkAriaLive]',
  1825. exportAs: 'cdkAriaLive',
  1826. },] },
  1827. ];
  1828. /** @nocollapse */
  1829. CdkAriaLive.ctorParameters = () => [
  1830. { type: ElementRef },
  1831. { type: LiveAnnouncer },
  1832. { type: ContentObserver },
  1833. { type: NgZone }
  1834. ];
  1835. CdkAriaLive.propDecorators = {
  1836. politeness: [{ type: Input, args: ['cdkAriaLive',] }]
  1837. };
  1838. /**
  1839. * \@docs-private \@deprecated \@breaking-change 8.0.0
  1840. * @param {?} parentAnnouncer
  1841. * @param {?} liveElement
  1842. * @param {?} _document
  1843. * @param {?} ngZone
  1844. * @return {?}
  1845. */
  1846. function LIVE_ANNOUNCER_PROVIDER_FACTORY(parentAnnouncer, liveElement, _document, ngZone) {
  1847. return parentAnnouncer || new LiveAnnouncer(liveElement, ngZone, _document);
  1848. }
  1849. /**
  1850. * \@docs-private \@deprecated \@breaking-change 8.0.0
  1851. * @type {?}
  1852. */
  1853. const LIVE_ANNOUNCER_PROVIDER = {
  1854. // If there is already a LiveAnnouncer available, use that. Otherwise, provide a new one.
  1855. provide: LiveAnnouncer,
  1856. deps: [
  1857. [new Optional(), new SkipSelf(), LiveAnnouncer],
  1858. [new Optional(), new Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN)],
  1859. DOCUMENT,
  1860. NgZone,
  1861. ],
  1862. useFactory: LIVE_ANNOUNCER_PROVIDER_FACTORY
  1863. };
  1864. /**
  1865. * @fileoverview added by tsickle
  1866. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1867. */
  1868. // This is the value used by AngularJS Material. Through trial and error (on iPhone 6S) they found
  1869. // that a value of around 650ms seems appropriate.
  1870. /** @type {?} */
  1871. const TOUCH_BUFFER_MS = 650;
  1872. /**
  1873. * Event listener options that enable capturing and also
  1874. * mark the listener as passive if the browser supports it.
  1875. * @type {?}
  1876. */
  1877. const captureEventListenerOptions = normalizePassiveListenerOptions({
  1878. passive: true,
  1879. capture: true
  1880. });
  1881. /**
  1882. * Monitors mouse and keyboard events to determine the cause of focus events.
  1883. */
  1884. class FocusMonitor {
  1885. /**
  1886. * @param {?} _ngZone
  1887. * @param {?} _platform
  1888. */
  1889. constructor(_ngZone, _platform) {
  1890. this._ngZone = _ngZone;
  1891. this._platform = _platform;
  1892. /**
  1893. * The focus origin that the next focus event is a result of.
  1894. */
  1895. this._origin = null;
  1896. /**
  1897. * Whether the window has just been focused.
  1898. */
  1899. this._windowFocused = false;
  1900. /**
  1901. * Map of elements being monitored to their info.
  1902. */
  1903. this._elementInfo = new Map();
  1904. /**
  1905. * The number of elements currently being monitored.
  1906. */
  1907. this._monitoredElementCount = 0;
  1908. /**
  1909. * Event listener for `keydown` events on the document.
  1910. * Needs to be an arrow function in order to preserve the context when it gets bound.
  1911. */
  1912. this._documentKeydownListener = (/**
  1913. * @return {?}
  1914. */
  1915. () => {
  1916. // On keydown record the origin and clear any touch event that may be in progress.
  1917. this._lastTouchTarget = null;
  1918. this._setOriginForCurrentEventQueue('keyboard');
  1919. });
  1920. /**
  1921. * Event listener for `mousedown` events on the document.
  1922. * Needs to be an arrow function in order to preserve the context when it gets bound.
  1923. */
  1924. this._documentMousedownListener = (/**
  1925. * @return {?}
  1926. */
  1927. () => {
  1928. // On mousedown record the origin only if there is not touch
  1929. // target, since a mousedown can happen as a result of a touch event.
  1930. if (!this._lastTouchTarget) {
  1931. this._setOriginForCurrentEventQueue('mouse');
  1932. }
  1933. });
  1934. /**
  1935. * Event listener for `touchstart` events on the document.
  1936. * Needs to be an arrow function in order to preserve the context when it gets bound.
  1937. */
  1938. this._documentTouchstartListener = (/**
  1939. * @param {?} event
  1940. * @return {?}
  1941. */
  1942. (event) => {
  1943. // When the touchstart event fires the focus event is not yet in the event queue. This means
  1944. // we can't rely on the trick used above (setting timeout of 1ms). Instead we wait 650ms to
  1945. // see if a focus happens.
  1946. if (this._touchTimeoutId != null) {
  1947. clearTimeout(this._touchTimeoutId);
  1948. }
  1949. // Since this listener is bound on the `document` level, any events coming from the shadow DOM
  1950. // will have their `target` set to the shadow root. If available, use `composedPath` to
  1951. // figure out the event target.
  1952. this._lastTouchTarget = event.composedPath ? event.composedPath()[0] : event.target;
  1953. this._touchTimeoutId = setTimeout((/**
  1954. * @return {?}
  1955. */
  1956. () => this._lastTouchTarget = null), TOUCH_BUFFER_MS);
  1957. });
  1958. /**
  1959. * Event listener for `focus` events on the window.
  1960. * Needs to be an arrow function in order to preserve the context when it gets bound.
  1961. */
  1962. this._windowFocusListener = (/**
  1963. * @return {?}
  1964. */
  1965. () => {
  1966. // Make a note of when the window regains focus, so we can
  1967. // restore the origin info for the focused element.
  1968. this._windowFocused = true;
  1969. this._windowFocusTimeoutId = setTimeout((/**
  1970. * @return {?}
  1971. */
  1972. () => this._windowFocused = false));
  1973. });
  1974. }
  1975. /**
  1976. * @param {?} element
  1977. * @param {?=} checkChildren
  1978. * @return {?}
  1979. */
  1980. monitor(element, checkChildren = false) {
  1981. // Do nothing if we're not on the browser platform.
  1982. if (!this._platform.isBrowser) {
  1983. return of(null);
  1984. }
  1985. /** @type {?} */
  1986. const nativeElement = coerceElement(element);
  1987. // Check if we're already monitoring this element.
  1988. if (this._elementInfo.has(nativeElement)) {
  1989. /** @type {?} */
  1990. let cachedInfo = this._elementInfo.get(nativeElement);
  1991. (/** @type {?} */ (cachedInfo)).checkChildren = checkChildren;
  1992. return (/** @type {?} */ (cachedInfo)).subject.asObservable();
  1993. }
  1994. // Create monitored element info.
  1995. /** @type {?} */
  1996. let info = {
  1997. unlisten: (/**
  1998. * @return {?}
  1999. */
  2000. () => { }),
  2001. checkChildren: checkChildren,
  2002. subject: new Subject()
  2003. };
  2004. this._elementInfo.set(nativeElement, info);
  2005. this._incrementMonitoredElementCount();
  2006. // Start listening. We need to listen in capture phase since focus events don't bubble.
  2007. /** @type {?} */
  2008. let focusListener = (/**
  2009. * @param {?} event
  2010. * @return {?}
  2011. */
  2012. (event) => this._onFocus(event, nativeElement));
  2013. /** @type {?} */
  2014. let blurListener = (/**
  2015. * @param {?} event
  2016. * @return {?}
  2017. */
  2018. (event) => this._onBlur(event, nativeElement));
  2019. this._ngZone.runOutsideAngular((/**
  2020. * @return {?}
  2021. */
  2022. () => {
  2023. nativeElement.addEventListener('focus', focusListener, true);
  2024. nativeElement.addEventListener('blur', blurListener, true);
  2025. }));
  2026. // Create an unlisten function for later.
  2027. info.unlisten = (/**
  2028. * @return {?}
  2029. */
  2030. () => {
  2031. nativeElement.removeEventListener('focus', focusListener, true);
  2032. nativeElement.removeEventListener('blur', blurListener, true);
  2033. });
  2034. return info.subject.asObservable();
  2035. }
  2036. /**
  2037. * @param {?} element
  2038. * @return {?}
  2039. */
  2040. stopMonitoring(element) {
  2041. /** @type {?} */
  2042. const nativeElement = coerceElement(element);
  2043. /** @type {?} */
  2044. const elementInfo = this._elementInfo.get(nativeElement);
  2045. if (elementInfo) {
  2046. elementInfo.unlisten();
  2047. elementInfo.subject.complete();
  2048. this._setClasses(nativeElement);
  2049. this._elementInfo.delete(nativeElement);
  2050. this._decrementMonitoredElementCount();
  2051. }
  2052. }
  2053. /**
  2054. * @param {?} element
  2055. * @param {?} origin
  2056. * @param {?=} options
  2057. * @return {?}
  2058. */
  2059. focusVia(element, origin, options) {
  2060. /** @type {?} */
  2061. const nativeElement = coerceElement(element);
  2062. this._setOriginForCurrentEventQueue(origin);
  2063. // `focus` isn't available on the server
  2064. if (typeof nativeElement.focus === 'function') {
  2065. // Cast the element to `any`, because the TS typings don't have the `options` parameter yet.
  2066. ((/** @type {?} */ (nativeElement))).focus(options);
  2067. }
  2068. }
  2069. /**
  2070. * @return {?}
  2071. */
  2072. ngOnDestroy() {
  2073. this._elementInfo.forEach((/**
  2074. * @param {?} _info
  2075. * @param {?} element
  2076. * @return {?}
  2077. */
  2078. (_info, element) => this.stopMonitoring(element)));
  2079. }
  2080. /**
  2081. * @private
  2082. * @param {?} element
  2083. * @param {?} className
  2084. * @param {?} shouldSet
  2085. * @return {?}
  2086. */
  2087. _toggleClass(element, className, shouldSet) {
  2088. if (shouldSet) {
  2089. element.classList.add(className);
  2090. }
  2091. else {
  2092. element.classList.remove(className);
  2093. }
  2094. }
  2095. /**
  2096. * Sets the focus classes on the element based on the given focus origin.
  2097. * @private
  2098. * @param {?} element The element to update the classes on.
  2099. * @param {?=} origin The focus origin.
  2100. * @return {?}
  2101. */
  2102. _setClasses(element, origin) {
  2103. /** @type {?} */
  2104. const elementInfo = this._elementInfo.get(element);
  2105. if (elementInfo) {
  2106. this._toggleClass(element, 'cdk-focused', !!origin);
  2107. this._toggleClass(element, 'cdk-touch-focused', origin === 'touch');
  2108. this._toggleClass(element, 'cdk-keyboard-focused', origin === 'keyboard');
  2109. this._toggleClass(element, 'cdk-mouse-focused', origin === 'mouse');
  2110. this._toggleClass(element, 'cdk-program-focused', origin === 'program');
  2111. }
  2112. }
  2113. /**
  2114. * Sets the origin and schedules an async function to clear it at the end of the event queue.
  2115. * @private
  2116. * @param {?} origin The origin to set.
  2117. * @return {?}
  2118. */
  2119. _setOriginForCurrentEventQueue(origin) {
  2120. this._ngZone.runOutsideAngular((/**
  2121. * @return {?}
  2122. */
  2123. () => {
  2124. this._origin = origin;
  2125. // Sometimes the focus origin won't be valid in Firefox because Firefox seems to focus *one*
  2126. // tick after the interaction event fired. To ensure the focus origin is always correct,
  2127. // the focus origin will be determined at the beginning of the next tick.
  2128. this._originTimeoutId = setTimeout((/**
  2129. * @return {?}
  2130. */
  2131. () => this._origin = null), 1);
  2132. }));
  2133. }
  2134. /**
  2135. * Checks whether the given focus event was caused by a touchstart event.
  2136. * @private
  2137. * @param {?} event The focus event to check.
  2138. * @return {?} Whether the event was caused by a touch.
  2139. */
  2140. _wasCausedByTouch(event) {
  2141. // Note(mmalerba): This implementation is not quite perfect, there is a small edge case.
  2142. // Consider the following dom structure:
  2143. //
  2144. // <div #parent tabindex="0" cdkFocusClasses>
  2145. // <div #child (click)="#parent.focus()"></div>
  2146. // </div>
  2147. //
  2148. // If the user touches the #child element and the #parent is programmatically focused as a
  2149. // result, this code will still consider it to have been caused by the touch event and will
  2150. // apply the cdk-touch-focused class rather than the cdk-program-focused class. This is a
  2151. // relatively small edge-case that can be worked around by using
  2152. // focusVia(parentEl, 'program') to focus the parent element.
  2153. //
  2154. // If we decide that we absolutely must handle this case correctly, we can do so by listening
  2155. // for the first focus event after the touchstart, and then the first blur event after that
  2156. // focus event. When that blur event fires we know that whatever follows is not a result of the
  2157. // touchstart.
  2158. /** @type {?} */
  2159. let focusTarget = event.target;
  2160. return this._lastTouchTarget instanceof Node && focusTarget instanceof Node &&
  2161. (focusTarget === this._lastTouchTarget || focusTarget.contains(this._lastTouchTarget));
  2162. }
  2163. /**
  2164. * Handles focus events on a registered element.
  2165. * @private
  2166. * @param {?} event The focus event.
  2167. * @param {?} element The monitored element.
  2168. * @return {?}
  2169. */
  2170. _onFocus(event, element) {
  2171. // NOTE(mmalerba): We currently set the classes based on the focus origin of the most recent
  2172. // focus event affecting the monitored element. If we want to use the origin of the first event
  2173. // instead we should check for the cdk-focused class here and return if the element already has
  2174. // it. (This only matters for elements that have includesChildren = true).
  2175. // NOTE(mmalerba): We currently set the classes based on the focus origin of the most recent
  2176. // focus event affecting the monitored element. If we want to use the origin of the first event
  2177. // instead we should check for the cdk-focused class here and return if the element already has
  2178. // it. (This only matters for elements that have includesChildren = true).
  2179. // If we are not counting child-element-focus as focused, make sure that the event target is the
  2180. // monitored element itself.
  2181. /** @type {?} */
  2182. const elementInfo = this._elementInfo.get(element);
  2183. if (!elementInfo || (!elementInfo.checkChildren && element !== event.target)) {
  2184. return;
  2185. }
  2186. // If we couldn't detect a cause for the focus event, it's due to one of three reasons:
  2187. // 1) The window has just regained focus, in which case we want to restore the focused state of
  2188. // the element from before the window blurred.
  2189. // 2) It was caused by a touch event, in which case we mark the origin as 'touch'.
  2190. // 3) The element was programmatically focused, in which case we should mark the origin as
  2191. // 'program'.
  2192. /** @type {?} */
  2193. let origin = this._origin;
  2194. if (!origin) {
  2195. if (this._windowFocused && this._lastFocusOrigin) {
  2196. origin = this._lastFocusOrigin;
  2197. }
  2198. else if (this._wasCausedByTouch(event)) {
  2199. origin = 'touch';
  2200. }
  2201. else {
  2202. origin = 'program';
  2203. }
  2204. }
  2205. this._setClasses(element, origin);
  2206. this._emitOrigin(elementInfo.subject, origin);
  2207. this._lastFocusOrigin = origin;
  2208. }
  2209. /**
  2210. * Handles blur events on a registered element.
  2211. * @param {?} event The blur event.
  2212. * @param {?} element The monitored element.
  2213. * @return {?}
  2214. */
  2215. _onBlur(event, element) {
  2216. // If we are counting child-element-focus as focused, make sure that we aren't just blurring in
  2217. // order to focus another child of the monitored element.
  2218. /** @type {?} */
  2219. const elementInfo = this._elementInfo.get(element);
  2220. if (!elementInfo || (elementInfo.checkChildren && event.relatedTarget instanceof Node &&
  2221. element.contains(event.relatedTarget))) {
  2222. return;
  2223. }
  2224. this._setClasses(element);
  2225. this._emitOrigin(elementInfo.subject, null);
  2226. }
  2227. /**
  2228. * @private
  2229. * @param {?} subject
  2230. * @param {?} origin
  2231. * @return {?}
  2232. */
  2233. _emitOrigin(subject, origin) {
  2234. this._ngZone.run((/**
  2235. * @return {?}
  2236. */
  2237. () => subject.next(origin)));
  2238. }
  2239. /**
  2240. * @private
  2241. * @return {?}
  2242. */
  2243. _incrementMonitoredElementCount() {
  2244. // Register global listeners when first element is monitored.
  2245. if (++this._monitoredElementCount == 1 && this._platform.isBrowser) {
  2246. // Note: we listen to events in the capture phase so we
  2247. // can detect them even if the user stops propagation.
  2248. this._ngZone.runOutsideAngular((/**
  2249. * @return {?}
  2250. */
  2251. () => {
  2252. document.addEventListener('keydown', this._documentKeydownListener, captureEventListenerOptions);
  2253. document.addEventListener('mousedown', this._documentMousedownListener, captureEventListenerOptions);
  2254. document.addEventListener('touchstart', this._documentTouchstartListener, captureEventListenerOptions);
  2255. window.addEventListener('focus', this._windowFocusListener);
  2256. }));
  2257. }
  2258. }
  2259. /**
  2260. * @private
  2261. * @return {?}
  2262. */
  2263. _decrementMonitoredElementCount() {
  2264. // Unregister global listeners when last element is unmonitored.
  2265. if (!--this._monitoredElementCount) {
  2266. document.removeEventListener('keydown', this._documentKeydownListener, captureEventListenerOptions);
  2267. document.removeEventListener('mousedown', this._documentMousedownListener, captureEventListenerOptions);
  2268. document.removeEventListener('touchstart', this._documentTouchstartListener, captureEventListenerOptions);
  2269. window.removeEventListener('focus', this._windowFocusListener);
  2270. // Clear timeouts for all potentially pending timeouts to prevent the leaks.
  2271. clearTimeout(this._windowFocusTimeoutId);
  2272. clearTimeout(this._touchTimeoutId);
  2273. clearTimeout(this._originTimeoutId);
  2274. }
  2275. }
  2276. }
  2277. FocusMonitor.decorators = [
  2278. { type: Injectable, args: [{ providedIn: 'root' },] },
  2279. ];
  2280. /** @nocollapse */
  2281. FocusMonitor.ctorParameters = () => [
  2282. { type: NgZone },
  2283. { type: Platform }
  2284. ];
  2285. /** @nocollapse */ FocusMonitor.ngInjectableDef = ɵɵdefineInjectable({ factory: function FocusMonitor_Factory() { return new FocusMonitor(ɵɵinject(NgZone), ɵɵinject(Platform)); }, token: FocusMonitor, providedIn: "root" });
  2286. /**
  2287. * Directive that determines how a particular element was focused (via keyboard, mouse, touch, or
  2288. * programmatically) and adds corresponding classes to the element.
  2289. *
  2290. * There are two variants of this directive:
  2291. * 1) cdkMonitorElementFocus: does not consider an element to be focused if one of its children is
  2292. * focused.
  2293. * 2) cdkMonitorSubtreeFocus: considers an element focused if it or any of its children are focused.
  2294. */
  2295. class CdkMonitorFocus {
  2296. /**
  2297. * @param {?} _elementRef
  2298. * @param {?} _focusMonitor
  2299. */
  2300. constructor(_elementRef, _focusMonitor) {
  2301. this._elementRef = _elementRef;
  2302. this._focusMonitor = _focusMonitor;
  2303. this.cdkFocusChange = new EventEmitter();
  2304. this._monitorSubscription = this._focusMonitor.monitor(this._elementRef, this._elementRef.nativeElement.hasAttribute('cdkMonitorSubtreeFocus'))
  2305. .subscribe((/**
  2306. * @param {?} origin
  2307. * @return {?}
  2308. */
  2309. origin => this.cdkFocusChange.emit(origin)));
  2310. }
  2311. /**
  2312. * @return {?}
  2313. */
  2314. ngOnDestroy() {
  2315. this._focusMonitor.stopMonitoring(this._elementRef);
  2316. this._monitorSubscription.unsubscribe();
  2317. }
  2318. }
  2319. CdkMonitorFocus.decorators = [
  2320. { type: Directive, args: [{
  2321. selector: '[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]',
  2322. },] },
  2323. ];
  2324. /** @nocollapse */
  2325. CdkMonitorFocus.ctorParameters = () => [
  2326. { type: ElementRef },
  2327. { type: FocusMonitor }
  2328. ];
  2329. CdkMonitorFocus.propDecorators = {
  2330. cdkFocusChange: [{ type: Output }]
  2331. };
  2332. /**
  2333. * \@docs-private \@deprecated \@breaking-change 8.0.0
  2334. * @param {?} parentDispatcher
  2335. * @param {?} ngZone
  2336. * @param {?} platform
  2337. * @return {?}
  2338. */
  2339. function FOCUS_MONITOR_PROVIDER_FACTORY(parentDispatcher, ngZone, platform) {
  2340. return parentDispatcher || new FocusMonitor(ngZone, platform);
  2341. }
  2342. /**
  2343. * \@docs-private \@deprecated \@breaking-change 8.0.0
  2344. * @type {?}
  2345. */
  2346. const FOCUS_MONITOR_PROVIDER = {
  2347. // If there is already a FocusMonitor available, use that. Otherwise, provide a new one.
  2348. provide: FocusMonitor,
  2349. deps: [[new Optional(), new SkipSelf(), FocusMonitor], NgZone, Platform],
  2350. useFactory: FOCUS_MONITOR_PROVIDER_FACTORY
  2351. };
  2352. /**
  2353. * @fileoverview added by tsickle
  2354. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  2355. */
  2356. /**
  2357. * Screenreaders will often fire fake mousedown events when a focusable element
  2358. * is activated using the keyboard. We can typically distinguish between these faked
  2359. * mousedown events and real mousedown events using the "buttons" property. While
  2360. * real mousedowns will indicate the mouse button that was pressed (e.g. "1" for
  2361. * the left mouse button), faked mousedowns will usually set the property value to 0.
  2362. * @param {?} event
  2363. * @return {?}
  2364. */
  2365. function isFakeMousedownFromScreenReader(event) {
  2366. return event.buttons === 0;
  2367. }
  2368. /**
  2369. * @fileoverview added by tsickle
  2370. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  2371. */
  2372. class A11yModule {
  2373. }
  2374. A11yModule.decorators = [
  2375. { type: NgModule, args: [{
  2376. imports: [CommonModule, PlatformModule, ObserversModule],
  2377. declarations: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus],
  2378. exports: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus],
  2379. },] },
  2380. ];
  2381. /**
  2382. * @fileoverview added by tsickle
  2383. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  2384. */
  2385. /**
  2386. * @fileoverview added by tsickle
  2387. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  2388. */
  2389. export { ARIA_DESCRIBER_PROVIDER_FACTORY, MESSAGES_CONTAINER_ID, CDK_DESCRIBEDBY_ID_PREFIX, CDK_DESCRIBEDBY_HOST_ATTRIBUTE, AriaDescriber, ARIA_DESCRIBER_PROVIDER, ActiveDescendantKeyManager, FocusKeyManager, ListKeyManager, FocusTrap, FocusTrapFactory, CdkTrapFocus, InteractivityChecker, LIVE_ANNOUNCER_PROVIDER_FACTORY, LiveAnnouncer, CdkAriaLive, LIVE_ANNOUNCER_PROVIDER, LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY, LIVE_ANNOUNCER_ELEMENT_TOKEN, LIVE_ANNOUNCER_DEFAULT_OPTIONS, FOCUS_MONITOR_PROVIDER_FACTORY, TOUCH_BUFFER_MS, FocusMonitor, CdkMonitorFocus, FOCUS_MONITOR_PROVIDER, isFakeMousedownFromScreenReader, A11yModule };
  2390. //# sourceMappingURL=a11y.js.map