tooltip.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  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 { animate, keyframes, state, style, transition, trigger } from '@angular/animations';
  9. import { AriaDescriber, FocusMonitor, A11yModule } from '@angular/cdk/a11y';
  10. import { Directionality } from '@angular/cdk/bidi';
  11. import { coerceBooleanProperty } from '@angular/cdk/coercion';
  12. import { ESCAPE, hasModifierKey } from '@angular/cdk/keycodes';
  13. import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
  14. import { Overlay, OverlayModule } from '@angular/cdk/overlay';
  15. import { Platform } from '@angular/cdk/platform';
  16. import { ComponentPortal } from '@angular/cdk/portal';
  17. import { ScrollDispatcher } from '@angular/cdk/scrolling';
  18. import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Directive, ElementRef, Inject, InjectionToken, Input, NgZone, Optional, ViewContainerRef, ViewEncapsulation, NgModule } from '@angular/core';
  19. import { HAMMER_LOADER, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
  20. import { Subject } from 'rxjs';
  21. import { take, takeUntil } from 'rxjs/operators';
  22. import { CommonModule } from '@angular/common';
  23. import { GestureConfig, MatCommonModule } from '@angular/material/core';
  24. /**
  25. * @fileoverview added by tsickle
  26. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  27. */
  28. /**
  29. * Animations used by MatTooltip.
  30. * \@docs-private
  31. * @type {?}
  32. */
  33. const matTooltipAnimations = {
  34. /**
  35. * Animation that transitions a tooltip in and out.
  36. */
  37. tooltipState: trigger('state', [
  38. state('initial, void, hidden', style({ opacity: 0, transform: 'scale(0)' })),
  39. state('visible', style({ transform: 'scale(1)' })),
  40. transition('* => visible', animate('200ms cubic-bezier(0, 0, 0.2, 1)', keyframes([
  41. style({ opacity: 0, transform: 'scale(0)', offset: 0 }),
  42. style({ opacity: 0.5, transform: 'scale(0.99)', offset: 0.5 }),
  43. style({ opacity: 1, transform: 'scale(1)', offset: 1 })
  44. ]))),
  45. transition('* => hidden', animate('100ms cubic-bezier(0, 0, 0.2, 1)', style({ opacity: 0 }))),
  46. ])
  47. };
  48. /**
  49. * @fileoverview added by tsickle
  50. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  51. */
  52. /**
  53. * Time in ms to throttle repositioning after scroll events.
  54. * @type {?}
  55. */
  56. const SCROLL_THROTTLE_MS = 20;
  57. /**
  58. * CSS class that will be attached to the overlay panel.
  59. * @type {?}
  60. */
  61. const TOOLTIP_PANEL_CLASS = 'mat-tooltip-panel';
  62. /**
  63. * Creates an error to be thrown if the user supplied an invalid tooltip position.
  64. * \@docs-private
  65. * @param {?} position
  66. * @return {?}
  67. */
  68. function getMatTooltipInvalidPositionError(position) {
  69. return Error(`Tooltip position "${position}" is invalid.`);
  70. }
  71. /**
  72. * Injection token that determines the scroll handling while a tooltip is visible.
  73. * @type {?}
  74. */
  75. const MAT_TOOLTIP_SCROLL_STRATEGY = new InjectionToken('mat-tooltip-scroll-strategy');
  76. /**
  77. * \@docs-private
  78. * @param {?} overlay
  79. * @return {?}
  80. */
  81. function MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY(overlay) {
  82. return (/**
  83. * @return {?}
  84. */
  85. () => overlay.scrollStrategies.reposition({ scrollThrottle: SCROLL_THROTTLE_MS }));
  86. }
  87. /**
  88. * \@docs-private
  89. * @type {?}
  90. */
  91. const MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY_PROVIDER = {
  92. provide: MAT_TOOLTIP_SCROLL_STRATEGY,
  93. deps: [Overlay],
  94. useFactory: MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY,
  95. };
  96. /**
  97. * Injection token to be used to override the default options for `matTooltip`.
  98. * @type {?}
  99. */
  100. const MAT_TOOLTIP_DEFAULT_OPTIONS = new InjectionToken('mat-tooltip-default-options', {
  101. providedIn: 'root',
  102. factory: MAT_TOOLTIP_DEFAULT_OPTIONS_FACTORY
  103. });
  104. /**
  105. * \@docs-private
  106. * @return {?}
  107. */
  108. function MAT_TOOLTIP_DEFAULT_OPTIONS_FACTORY() {
  109. return {
  110. showDelay: 0,
  111. hideDelay: 0,
  112. touchendHideDelay: 1500,
  113. };
  114. }
  115. /**
  116. * Directive that attaches a material design tooltip to the host element. Animates the showing and
  117. * hiding of a tooltip provided position (defaults to below the element).
  118. *
  119. * https://material.io/design/components/tooltips.html
  120. */
  121. class MatTooltip {
  122. /**
  123. * @param {?} _overlay
  124. * @param {?} _elementRef
  125. * @param {?} _scrollDispatcher
  126. * @param {?} _viewContainerRef
  127. * @param {?} _ngZone
  128. * @param {?} platform
  129. * @param {?} _ariaDescriber
  130. * @param {?} _focusMonitor
  131. * @param {?} scrollStrategy
  132. * @param {?} _dir
  133. * @param {?} _defaultOptions
  134. * @param {?=} hammerLoader
  135. */
  136. constructor(_overlay, _elementRef, _scrollDispatcher, _viewContainerRef, _ngZone, platform, _ariaDescriber, _focusMonitor, scrollStrategy, _dir, _defaultOptions, hammerLoader) {
  137. this._overlay = _overlay;
  138. this._elementRef = _elementRef;
  139. this._scrollDispatcher = _scrollDispatcher;
  140. this._viewContainerRef = _viewContainerRef;
  141. this._ngZone = _ngZone;
  142. this._ariaDescriber = _ariaDescriber;
  143. this._focusMonitor = _focusMonitor;
  144. this._dir = _dir;
  145. this._defaultOptions = _defaultOptions;
  146. this._position = 'below';
  147. this._disabled = false;
  148. /**
  149. * The default delay in ms before showing the tooltip after show is called
  150. */
  151. this.showDelay = this._defaultOptions.showDelay;
  152. /**
  153. * The default delay in ms before hiding the tooltip after hide is called
  154. */
  155. this.hideDelay = this._defaultOptions.hideDelay;
  156. this._message = '';
  157. this._manualListeners = new Map();
  158. /**
  159. * Emits when the component is destroyed.
  160. */
  161. this._destroyed = new Subject();
  162. this._scrollStrategy = scrollStrategy;
  163. /** @type {?} */
  164. const element = _elementRef.nativeElement;
  165. /** @type {?} */
  166. const hasGestures = typeof window === 'undefined' || ((/** @type {?} */ (window))).Hammer || hammerLoader;
  167. // The mouse events shouldn't be bound on mobile devices, because they can prevent the
  168. // first tap from firing its click event or can cause the tooltip to open for clicks.
  169. if (!platform.IOS && !platform.ANDROID) {
  170. this._manualListeners
  171. .set('mouseenter', (/**
  172. * @return {?}
  173. */
  174. () => this.show()))
  175. .set('mouseleave', (/**
  176. * @return {?}
  177. */
  178. () => this.hide()));
  179. }
  180. else if (!hasGestures) {
  181. // If Hammerjs isn't loaded, fall back to showing on `touchstart`, otherwise
  182. // there's no way for the user to trigger the tooltip on a touch device.
  183. this._manualListeners.set('touchstart', (/**
  184. * @return {?}
  185. */
  186. () => this.show()));
  187. }
  188. this._manualListeners.forEach((/**
  189. * @param {?} listener
  190. * @param {?} event
  191. * @return {?}
  192. */
  193. (listener, event) => element.addEventListener(event, listener)));
  194. _focusMonitor.monitor(_elementRef).pipe(takeUntil(this._destroyed)).subscribe((/**
  195. * @param {?} origin
  196. * @return {?}
  197. */
  198. origin => {
  199. // Note that the focus monitor runs outside the Angular zone.
  200. if (!origin) {
  201. _ngZone.run((/**
  202. * @return {?}
  203. */
  204. () => this.hide(0)));
  205. }
  206. else if (origin === 'keyboard') {
  207. _ngZone.run((/**
  208. * @return {?}
  209. */
  210. () => this.show()));
  211. }
  212. }));
  213. if (_defaultOptions && _defaultOptions.position) {
  214. this.position = _defaultOptions.position;
  215. }
  216. }
  217. /**
  218. * Allows the user to define the position of the tooltip relative to the parent element
  219. * @return {?}
  220. */
  221. get position() { return this._position; }
  222. /**
  223. * @param {?} value
  224. * @return {?}
  225. */
  226. set position(value) {
  227. if (value !== this._position) {
  228. this._position = value;
  229. if (this._overlayRef) {
  230. this._updatePosition();
  231. if (this._tooltipInstance) {
  232. (/** @type {?} */ (this._tooltipInstance)).show(0);
  233. }
  234. this._overlayRef.updatePosition();
  235. }
  236. }
  237. }
  238. /**
  239. * Disables the display of the tooltip.
  240. * @return {?}
  241. */
  242. get disabled() { return this._disabled; }
  243. /**
  244. * @param {?} value
  245. * @return {?}
  246. */
  247. set disabled(value) {
  248. this._disabled = coerceBooleanProperty(value);
  249. // If tooltip is disabled, hide immediately.
  250. if (this._disabled) {
  251. this.hide(0);
  252. }
  253. }
  254. /**
  255. * The message to be displayed in the tooltip
  256. * @return {?}
  257. */
  258. get message() { return this._message; }
  259. /**
  260. * @param {?} value
  261. * @return {?}
  262. */
  263. set message(value) {
  264. this._ariaDescriber.removeDescription(this._elementRef.nativeElement, this._message);
  265. // If the message is not a string (e.g. number), convert it to a string and trim it.
  266. this._message = value != null ? `${value}`.trim() : '';
  267. if (!this._message && this._isTooltipVisible()) {
  268. this.hide(0);
  269. }
  270. else {
  271. this._updateTooltipMessage();
  272. this._ariaDescriber.describe(this._elementRef.nativeElement, this.message);
  273. }
  274. }
  275. /**
  276. * Classes to be passed to the tooltip. Supports the same syntax as `ngClass`.
  277. * @return {?}
  278. */
  279. get tooltipClass() { return this._tooltipClass; }
  280. /**
  281. * @param {?} value
  282. * @return {?}
  283. */
  284. set tooltipClass(value) {
  285. this._tooltipClass = value;
  286. if (this._tooltipInstance) {
  287. this._setTooltipClass(this._tooltipClass);
  288. }
  289. }
  290. /**
  291. * Setup styling-specific things
  292. * @return {?}
  293. */
  294. ngOnInit() {
  295. /** @type {?} */
  296. const element = this._elementRef.nativeElement;
  297. /** @type {?} */
  298. const elementStyle = (/** @type {?} */ (element.style));
  299. if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
  300. // When we bind a gesture event on an element (in this case `longpress`), HammerJS
  301. // will add some inline styles by default, including `user-select: none`. This is
  302. // problematic on iOS and in Safari, because it will prevent users from typing in inputs.
  303. // Since `user-select: none` is not needed for the `longpress` event and can cause unexpected
  304. // behavior for text fields, we always clear the `user-select` to avoid such issues.
  305. elementStyle.webkitUserSelect = elementStyle.userSelect = elementStyle.msUserSelect = '';
  306. }
  307. // Hammer applies `-webkit-user-drag: none` on all elements by default,
  308. // which breaks the native drag&drop. If the consumer explicitly made
  309. // the element draggable, clear the `-webkit-user-drag`.
  310. if (element.draggable && elementStyle.webkitUserDrag === 'none') {
  311. elementStyle.webkitUserDrag = '';
  312. }
  313. }
  314. /**
  315. * Dispose the tooltip when destroyed.
  316. * @return {?}
  317. */
  318. ngOnDestroy() {
  319. if (this._overlayRef) {
  320. this._overlayRef.dispose();
  321. this._tooltipInstance = null;
  322. }
  323. // Clean up the event listeners set in the constructor
  324. this._manualListeners.forEach((/**
  325. * @param {?} listener
  326. * @param {?} event
  327. * @return {?}
  328. */
  329. (listener, event) => {
  330. this._elementRef.nativeElement.removeEventListener(event, listener);
  331. }));
  332. this._manualListeners.clear();
  333. this._destroyed.next();
  334. this._destroyed.complete();
  335. this._ariaDescriber.removeDescription(this._elementRef.nativeElement, this.message);
  336. this._focusMonitor.stopMonitoring(this._elementRef);
  337. }
  338. /**
  339. * Shows the tooltip after the delay in ms, defaults to tooltip-delay-show or 0ms if no input
  340. * @param {?=} delay
  341. * @return {?}
  342. */
  343. show(delay = this.showDelay) {
  344. if (this.disabled || !this.message || (this._isTooltipVisible() &&
  345. !(/** @type {?} */ (this._tooltipInstance))._showTimeoutId && !(/** @type {?} */ (this._tooltipInstance))._hideTimeoutId)) {
  346. return;
  347. }
  348. /** @type {?} */
  349. const overlayRef = this._createOverlay();
  350. this._detach();
  351. this._portal = this._portal || new ComponentPortal(TooltipComponent, this._viewContainerRef);
  352. this._tooltipInstance = overlayRef.attach(this._portal).instance;
  353. this._tooltipInstance.afterHidden()
  354. .pipe(takeUntil(this._destroyed))
  355. .subscribe((/**
  356. * @return {?}
  357. */
  358. () => this._detach()));
  359. this._setTooltipClass(this._tooltipClass);
  360. this._updateTooltipMessage();
  361. (/** @type {?} */ (this._tooltipInstance)).show(delay);
  362. }
  363. /**
  364. * Hides the tooltip after the delay in ms, defaults to tooltip-delay-hide or 0ms if no input
  365. * @param {?=} delay
  366. * @return {?}
  367. */
  368. hide(delay = this.hideDelay) {
  369. if (this._tooltipInstance) {
  370. this._tooltipInstance.hide(delay);
  371. }
  372. }
  373. /**
  374. * Shows/hides the tooltip
  375. * @return {?}
  376. */
  377. toggle() {
  378. this._isTooltipVisible() ? this.hide() : this.show();
  379. }
  380. /**
  381. * Returns true if the tooltip is currently visible to the user
  382. * @return {?}
  383. */
  384. _isTooltipVisible() {
  385. return !!this._tooltipInstance && this._tooltipInstance.isVisible();
  386. }
  387. /**
  388. * Handles the keydown events on the host element.
  389. * @param {?} e
  390. * @return {?}
  391. */
  392. _handleKeydown(e) {
  393. if (this._isTooltipVisible() && e.keyCode === ESCAPE && !hasModifierKey(e)) {
  394. e.preventDefault();
  395. e.stopPropagation();
  396. this.hide(0);
  397. }
  398. }
  399. /**
  400. * Handles the touchend events on the host element.
  401. * @return {?}
  402. */
  403. _handleTouchend() {
  404. this.hide(this._defaultOptions.touchendHideDelay);
  405. }
  406. /**
  407. * Create the overlay config and position strategy
  408. * @private
  409. * @return {?}
  410. */
  411. _createOverlay() {
  412. if (this._overlayRef) {
  413. return this._overlayRef;
  414. }
  415. /** @type {?} */
  416. const scrollableAncestors = this._scrollDispatcher.getAncestorScrollContainers(this._elementRef);
  417. // Create connected position strategy that listens for scroll events to reposition.
  418. /** @type {?} */
  419. const strategy = this._overlay.position()
  420. .flexibleConnectedTo(this._elementRef)
  421. .withTransformOriginOn('.mat-tooltip')
  422. .withFlexibleDimensions(false)
  423. .withViewportMargin(8)
  424. .withScrollableContainers(scrollableAncestors);
  425. strategy.positionChanges.pipe(takeUntil(this._destroyed)).subscribe((/**
  426. * @param {?} change
  427. * @return {?}
  428. */
  429. change => {
  430. if (this._tooltipInstance) {
  431. if (change.scrollableViewProperties.isOverlayClipped && this._tooltipInstance.isVisible()) {
  432. // After position changes occur and the overlay is clipped by
  433. // a parent scrollable then close the tooltip.
  434. this._ngZone.run((/**
  435. * @return {?}
  436. */
  437. () => this.hide(0)));
  438. }
  439. }
  440. }));
  441. this._overlayRef = this._overlay.create({
  442. direction: this._dir,
  443. positionStrategy: strategy,
  444. panelClass: TOOLTIP_PANEL_CLASS,
  445. scrollStrategy: this._scrollStrategy()
  446. });
  447. this._updatePosition();
  448. this._overlayRef.detachments()
  449. .pipe(takeUntil(this._destroyed))
  450. .subscribe((/**
  451. * @return {?}
  452. */
  453. () => this._detach()));
  454. return this._overlayRef;
  455. }
  456. /**
  457. * Detaches the currently-attached tooltip.
  458. * @private
  459. * @return {?}
  460. */
  461. _detach() {
  462. if (this._overlayRef && this._overlayRef.hasAttached()) {
  463. this._overlayRef.detach();
  464. }
  465. this._tooltipInstance = null;
  466. }
  467. /**
  468. * Updates the position of the current tooltip.
  469. * @private
  470. * @return {?}
  471. */
  472. _updatePosition() {
  473. /** @type {?} */
  474. const position = (/** @type {?} */ ((/** @type {?} */ (this._overlayRef)).getConfig().positionStrategy));
  475. /** @type {?} */
  476. const origin = this._getOrigin();
  477. /** @type {?} */
  478. const overlay = this._getOverlayPosition();
  479. position.withPositions([
  480. Object.assign({}, origin.main, overlay.main),
  481. Object.assign({}, origin.fallback, overlay.fallback)
  482. ]);
  483. }
  484. /**
  485. * Returns the origin position and a fallback position based on the user's position preference.
  486. * The fallback position is the inverse of the origin (e.g. `'below' -> 'above'`).
  487. * @return {?}
  488. */
  489. _getOrigin() {
  490. /** @type {?} */
  491. const isLtr = !this._dir || this._dir.value == 'ltr';
  492. /** @type {?} */
  493. const position = this.position;
  494. /** @type {?} */
  495. let originPosition;
  496. if (position == 'above' || position == 'below') {
  497. originPosition = { originX: 'center', originY: position == 'above' ? 'top' : 'bottom' };
  498. }
  499. else if (position == 'before' ||
  500. (position == 'left' && isLtr) ||
  501. (position == 'right' && !isLtr)) {
  502. originPosition = { originX: 'start', originY: 'center' };
  503. }
  504. else if (position == 'after' ||
  505. (position == 'right' && isLtr) ||
  506. (position == 'left' && !isLtr)) {
  507. originPosition = { originX: 'end', originY: 'center' };
  508. }
  509. else {
  510. throw getMatTooltipInvalidPositionError(position);
  511. }
  512. const { x, y } = this._invertPosition(originPosition.originX, originPosition.originY);
  513. return {
  514. main: originPosition,
  515. fallback: { originX: x, originY: y }
  516. };
  517. }
  518. /**
  519. * Returns the overlay position and a fallback position based on the user's preference
  520. * @return {?}
  521. */
  522. _getOverlayPosition() {
  523. /** @type {?} */
  524. const isLtr = !this._dir || this._dir.value == 'ltr';
  525. /** @type {?} */
  526. const position = this.position;
  527. /** @type {?} */
  528. let overlayPosition;
  529. if (position == 'above') {
  530. overlayPosition = { overlayX: 'center', overlayY: 'bottom' };
  531. }
  532. else if (position == 'below') {
  533. overlayPosition = { overlayX: 'center', overlayY: 'top' };
  534. }
  535. else if (position == 'before' ||
  536. (position == 'left' && isLtr) ||
  537. (position == 'right' && !isLtr)) {
  538. overlayPosition = { overlayX: 'end', overlayY: 'center' };
  539. }
  540. else if (position == 'after' ||
  541. (position == 'right' && isLtr) ||
  542. (position == 'left' && !isLtr)) {
  543. overlayPosition = { overlayX: 'start', overlayY: 'center' };
  544. }
  545. else {
  546. throw getMatTooltipInvalidPositionError(position);
  547. }
  548. const { x, y } = this._invertPosition(overlayPosition.overlayX, overlayPosition.overlayY);
  549. return {
  550. main: overlayPosition,
  551. fallback: { overlayX: x, overlayY: y }
  552. };
  553. }
  554. /**
  555. * Updates the tooltip message and repositions the overlay according to the new message length
  556. * @private
  557. * @return {?}
  558. */
  559. _updateTooltipMessage() {
  560. // Must wait for the message to be painted to the tooltip so that the overlay can properly
  561. // calculate the correct positioning based on the size of the text.
  562. if (this._tooltipInstance) {
  563. this._tooltipInstance.message = this.message;
  564. this._tooltipInstance._markForCheck();
  565. this._ngZone.onMicrotaskEmpty.asObservable().pipe(take(1), takeUntil(this._destroyed)).subscribe((/**
  566. * @return {?}
  567. */
  568. () => {
  569. if (this._tooltipInstance) {
  570. (/** @type {?} */ (this._overlayRef)).updatePosition();
  571. }
  572. }));
  573. }
  574. }
  575. /**
  576. * Updates the tooltip class
  577. * @private
  578. * @param {?} tooltipClass
  579. * @return {?}
  580. */
  581. _setTooltipClass(tooltipClass) {
  582. if (this._tooltipInstance) {
  583. this._tooltipInstance.tooltipClass = tooltipClass;
  584. this._tooltipInstance._markForCheck();
  585. }
  586. }
  587. /**
  588. * Inverts an overlay position.
  589. * @private
  590. * @param {?} x
  591. * @param {?} y
  592. * @return {?}
  593. */
  594. _invertPosition(x, y) {
  595. if (this.position === 'above' || this.position === 'below') {
  596. if (y === 'top') {
  597. y = 'bottom';
  598. }
  599. else if (y === 'bottom') {
  600. y = 'top';
  601. }
  602. }
  603. else {
  604. if (x === 'end') {
  605. x = 'start';
  606. }
  607. else if (x === 'start') {
  608. x = 'end';
  609. }
  610. }
  611. return { x, y };
  612. }
  613. }
  614. MatTooltip.decorators = [
  615. { type: Directive, args: [{
  616. selector: '[matTooltip]',
  617. exportAs: 'matTooltip',
  618. host: {
  619. '(longpress)': 'show()',
  620. '(keydown)': '_handleKeydown($event)',
  621. '(touchend)': '_handleTouchend()',
  622. },
  623. },] },
  624. ];
  625. /** @nocollapse */
  626. MatTooltip.ctorParameters = () => [
  627. { type: Overlay },
  628. { type: ElementRef },
  629. { type: ScrollDispatcher },
  630. { type: ViewContainerRef },
  631. { type: NgZone },
  632. { type: Platform },
  633. { type: AriaDescriber },
  634. { type: FocusMonitor },
  635. { type: undefined, decorators: [{ type: Inject, args: [MAT_TOOLTIP_SCROLL_STRATEGY,] }] },
  636. { type: Directionality, decorators: [{ type: Optional }] },
  637. { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_TOOLTIP_DEFAULT_OPTIONS,] }] },
  638. { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [HAMMER_LOADER,] }] }
  639. ];
  640. MatTooltip.propDecorators = {
  641. position: [{ type: Input, args: ['matTooltipPosition',] }],
  642. disabled: [{ type: Input, args: ['matTooltipDisabled',] }],
  643. showDelay: [{ type: Input, args: ['matTooltipShowDelay',] }],
  644. hideDelay: [{ type: Input, args: ['matTooltipHideDelay',] }],
  645. message: [{ type: Input, args: ['matTooltip',] }],
  646. tooltipClass: [{ type: Input, args: ['matTooltipClass',] }]
  647. };
  648. /**
  649. * Internal component that wraps the tooltip's content.
  650. * \@docs-private
  651. */
  652. class TooltipComponent {
  653. /**
  654. * @param {?} _changeDetectorRef
  655. * @param {?} _breakpointObserver
  656. */
  657. constructor(_changeDetectorRef, _breakpointObserver) {
  658. this._changeDetectorRef = _changeDetectorRef;
  659. this._breakpointObserver = _breakpointObserver;
  660. /**
  661. * Property watched by the animation framework to show or hide the tooltip
  662. */
  663. this._visibility = 'initial';
  664. /**
  665. * Whether interactions on the page should close the tooltip
  666. */
  667. this._closeOnInteraction = false;
  668. /**
  669. * Subject for notifying that the tooltip has been hidden from the view
  670. */
  671. this._onHide = new Subject();
  672. /**
  673. * Stream that emits whether the user has a handset-sized display.
  674. */
  675. this._isHandset = this._breakpointObserver.observe(Breakpoints.Handset);
  676. }
  677. /**
  678. * Shows the tooltip with an animation originating from the provided origin
  679. * @param {?} delay Amount of milliseconds to the delay showing the tooltip.
  680. * @return {?}
  681. */
  682. show(delay) {
  683. // Cancel the delayed hide if it is scheduled
  684. if (this._hideTimeoutId) {
  685. clearTimeout(this._hideTimeoutId);
  686. this._hideTimeoutId = null;
  687. }
  688. // Body interactions should cancel the tooltip if there is a delay in showing.
  689. this._closeOnInteraction = true;
  690. this._showTimeoutId = setTimeout((/**
  691. * @return {?}
  692. */
  693. () => {
  694. this._visibility = 'visible';
  695. this._showTimeoutId = null;
  696. // Mark for check so if any parent component has set the
  697. // ChangeDetectionStrategy to OnPush it will be checked anyways
  698. this._markForCheck();
  699. }), delay);
  700. }
  701. /**
  702. * Begins the animation to hide the tooltip after the provided delay in ms.
  703. * @param {?} delay Amount of milliseconds to delay showing the tooltip.
  704. * @return {?}
  705. */
  706. hide(delay) {
  707. // Cancel the delayed show if it is scheduled
  708. if (this._showTimeoutId) {
  709. clearTimeout(this._showTimeoutId);
  710. this._showTimeoutId = null;
  711. }
  712. this._hideTimeoutId = setTimeout((/**
  713. * @return {?}
  714. */
  715. () => {
  716. this._visibility = 'hidden';
  717. this._hideTimeoutId = null;
  718. // Mark for check so if any parent component has set the
  719. // ChangeDetectionStrategy to OnPush it will be checked anyways
  720. this._markForCheck();
  721. }), delay);
  722. }
  723. /**
  724. * Returns an observable that notifies when the tooltip has been hidden from view.
  725. * @return {?}
  726. */
  727. afterHidden() {
  728. return this._onHide.asObservable();
  729. }
  730. /**
  731. * Whether the tooltip is being displayed.
  732. * @return {?}
  733. */
  734. isVisible() {
  735. return this._visibility === 'visible';
  736. }
  737. /**
  738. * @return {?}
  739. */
  740. ngOnDestroy() {
  741. this._onHide.complete();
  742. }
  743. /**
  744. * @return {?}
  745. */
  746. _animationStart() {
  747. this._closeOnInteraction = false;
  748. }
  749. /**
  750. * @param {?} event
  751. * @return {?}
  752. */
  753. _animationDone(event) {
  754. /** @type {?} */
  755. const toState = (/** @type {?} */ (event.toState));
  756. if (toState === 'hidden' && !this.isVisible()) {
  757. this._onHide.next();
  758. }
  759. if (toState === 'visible' || toState === 'hidden') {
  760. this._closeOnInteraction = true;
  761. }
  762. }
  763. /**
  764. * Interactions on the HTML body should close the tooltip immediately as defined in the
  765. * material design spec.
  766. * https://material.io/design/components/tooltips.html#behavior
  767. * @return {?}
  768. */
  769. _handleBodyInteraction() {
  770. if (this._closeOnInteraction) {
  771. this.hide(0);
  772. }
  773. }
  774. /**
  775. * Marks that the tooltip needs to be checked in the next change detection run.
  776. * Mainly used for rendering the initial text before positioning a tooltip, which
  777. * can be problematic in components with OnPush change detection.
  778. * @return {?}
  779. */
  780. _markForCheck() {
  781. this._changeDetectorRef.markForCheck();
  782. }
  783. }
  784. TooltipComponent.decorators = [
  785. { type: Component, args: [{selector: 'mat-tooltip-component',
  786. template: "<div class=\"mat-tooltip\" [ngClass]=\"tooltipClass\" [class.mat-tooltip-handset]=\"(_isHandset | async)?.matches\" [@state]=\"_visibility\" (@state.start)=\"_animationStart()\" (@state.done)=\"_animationDone($event)\">{{message}}</div>",
  787. styles: [".mat-tooltip-panel{pointer-events:none!important}.mat-tooltip{color:#fff;border-radius:4px;margin:14px;max-width:250px;padding-left:8px;padding-right:8px;overflow:hidden;text-overflow:ellipsis}@media (-ms-high-contrast:active){.mat-tooltip{outline:solid 1px}}.mat-tooltip-handset{margin:24px;padding-left:16px;padding-right:16px}"],
  788. encapsulation: ViewEncapsulation.None,
  789. changeDetection: ChangeDetectionStrategy.OnPush,
  790. animations: [matTooltipAnimations.tooltipState],
  791. host: {
  792. // Forces the element to have a layout in IE and Edge. This fixes issues where the element
  793. // won't be rendered if the animations are disabled or there is no web animations polyfill.
  794. '[style.zoom]': '_visibility === "visible" ? 1 : null',
  795. '(body:click)': 'this._handleBodyInteraction()',
  796. 'aria-hidden': 'true',
  797. }
  798. },] },
  799. ];
  800. /** @nocollapse */
  801. TooltipComponent.ctorParameters = () => [
  802. { type: ChangeDetectorRef },
  803. { type: BreakpointObserver }
  804. ];
  805. /**
  806. * @fileoverview added by tsickle
  807. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  808. */
  809. class MatTooltipModule {
  810. }
  811. MatTooltipModule.decorators = [
  812. { type: NgModule, args: [{
  813. imports: [
  814. A11yModule,
  815. CommonModule,
  816. OverlayModule,
  817. MatCommonModule,
  818. ],
  819. exports: [MatTooltip, TooltipComponent, MatCommonModule],
  820. declarations: [MatTooltip, TooltipComponent],
  821. entryComponents: [TooltipComponent],
  822. providers: [
  823. MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY_PROVIDER,
  824. { provide: HAMMER_GESTURE_CONFIG, useClass: GestureConfig },
  825. ]
  826. },] },
  827. ];
  828. /**
  829. * @fileoverview added by tsickle
  830. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  831. */
  832. /**
  833. * @fileoverview added by tsickle
  834. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  835. */
  836. export { MatTooltipModule, getMatTooltipInvalidPositionError, MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY, MAT_TOOLTIP_DEFAULT_OPTIONS_FACTORY, SCROLL_THROTTLE_MS, TOOLTIP_PANEL_CLASS, MAT_TOOLTIP_SCROLL_STRATEGY, MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY_PROVIDER, MAT_TOOLTIP_DEFAULT_OPTIONS, MatTooltip, TooltipComponent, matTooltipAnimations };
  837. //# sourceMappingURL=tooltip.js.map