| import { Overlay, OverlayModule } from '@angular/cdk/overlay'; |
| import { AriaDescriber, FocusMonitor, A11yModule } from '@angular/cdk/a11y'; |
| import { DOCUMENT, CommonModule } from '@angular/common'; |
| import { InjectionToken, Directive, ElementRef, ViewContainerRef, NgZone, Inject, Optional, Input, Component, ViewEncapsulation, ChangeDetectionStrategy, ChangeDetectorRef, NgModule } from '@angular/core'; |
| import { MatCommonModule } from '@angular/material/core'; |
| import { ScrollDispatcher, CdkScrollableModule } from '@angular/cdk/scrolling'; |
| import { Directionality } from '@angular/cdk/bidi'; |
| import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
| import { ESCAPE, hasModifierKey } from '@angular/cdk/keycodes'; |
| import { Breakpoints, BreakpointObserver } from '@angular/cdk/layout'; |
| import { normalizePassiveListenerOptions, Platform } from '@angular/cdk/platform'; |
| import { ComponentPortal } from '@angular/cdk/portal'; |
| import { Subject } from 'rxjs'; |
| import { takeUntil, take } from 'rxjs/operators'; |
| import { trigger, state, style, transition, animate, keyframes } from '@angular/animations'; |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * Animations used by MatTooltip. |
| * @docs-private |
| */ |
| const matTooltipAnimations = { |
| /** Animation that transitions a tooltip in and out. */ |
| tooltipState: trigger('state', [ |
| state('initial, void, hidden', style({ opacity: 0, transform: 'scale(0)' })), |
| state('visible', style({ transform: 'scale(1)' })), |
| transition('* => visible', animate('200ms cubic-bezier(0, 0, 0.2, 1)', keyframes([ |
| style({ opacity: 0, transform: 'scale(0)', offset: 0 }), |
| style({ opacity: 0.5, transform: 'scale(0.99)', offset: 0.5 }), |
| style({ opacity: 1, transform: 'scale(1)', offset: 1 }) |
| ]))), |
| transition('* => hidden', animate('100ms cubic-bezier(0, 0, 0.2, 1)', style({ opacity: 0 }))), |
| ]) |
| }; |
| |
| /** Time in ms to throttle repositioning after scroll events. */ |
| const SCROLL_THROTTLE_MS = 20; |
| /** CSS class that will be attached to the overlay panel. */ |
| const TOOLTIP_PANEL_CLASS = 'mat-tooltip-panel'; |
| /** Options used to bind passive event listeners. */ |
| const passiveListenerOptions = normalizePassiveListenerOptions({ passive: true }); |
| /** |
| * Time between the user putting the pointer on a tooltip |
| * trigger and the long press event being fired. |
| */ |
| const LONGPRESS_DELAY = 500; |
| /** |
| * Creates an error to be thrown if the user supplied an invalid tooltip position. |
| * @docs-private |
| */ |
| function getMatTooltipInvalidPositionError(position) { |
| return Error(`Tooltip position "${position}" is invalid.`); |
| } |
| /** Injection token that determines the scroll handling while a tooltip is visible. */ |
| const MAT_TOOLTIP_SCROLL_STRATEGY = new InjectionToken('mat-tooltip-scroll-strategy'); |
| /** @docs-private */ |
| function MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY(overlay) { |
| return () => overlay.scrollStrategies.reposition({ scrollThrottle: SCROLL_THROTTLE_MS }); |
| } |
| /** @docs-private */ |
| const MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY_PROVIDER = { |
| provide: MAT_TOOLTIP_SCROLL_STRATEGY, |
| deps: [Overlay], |
| useFactory: MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY, |
| }; |
| /** Injection token to be used to override the default options for `matTooltip`. */ |
| const MAT_TOOLTIP_DEFAULT_OPTIONS = new InjectionToken('mat-tooltip-default-options', { |
| providedIn: 'root', |
| factory: MAT_TOOLTIP_DEFAULT_OPTIONS_FACTORY |
| }); |
| /** @docs-private */ |
| function MAT_TOOLTIP_DEFAULT_OPTIONS_FACTORY() { |
| return { |
| showDelay: 0, |
| hideDelay: 0, |
| touchendHideDelay: 1500, |
| }; |
| } |
| /** |
| * Directive that attaches a material design tooltip to the host element. Animates the showing and |
| * hiding of a tooltip provided position (defaults to below the element). |
| * |
| * https://material.io/design/components/tooltips.html |
| */ |
| class MatTooltip { |
| constructor(_overlay, _elementRef, _scrollDispatcher, _viewContainerRef, _ngZone, _platform, _ariaDescriber, _focusMonitor, scrollStrategy, _dir, _defaultOptions, |
| /** @breaking-change 11.0.0 _document argument to become required. */ |
| _document) { |
| this._overlay = _overlay; |
| this._elementRef = _elementRef; |
| this._scrollDispatcher = _scrollDispatcher; |
| this._viewContainerRef = _viewContainerRef; |
| this._ngZone = _ngZone; |
| this._platform = _platform; |
| this._ariaDescriber = _ariaDescriber; |
| this._focusMonitor = _focusMonitor; |
| this._dir = _dir; |
| this._defaultOptions = _defaultOptions; |
| this._position = 'below'; |
| this._disabled = false; |
| this._viewInitialized = false; |
| this._pointerExitEventsInitialized = false; |
| /** The default delay in ms before showing the tooltip after show is called */ |
| this.showDelay = this._defaultOptions.showDelay; |
| /** The default delay in ms before hiding the tooltip after hide is called */ |
| this.hideDelay = this._defaultOptions.hideDelay; |
| /** |
| * How touch gestures should be handled by the tooltip. On touch devices the tooltip directive |
| * uses a long press gesture to show and hide, however it can conflict with the native browser |
| * gestures. To work around the conflict, Angular Material disables native gestures on the |
| * trigger, but that might not be desirable on particular elements (e.g. inputs and draggable |
| * elements). The different values for this option configure the touch event handling as follows: |
| * - `auto` - Enables touch gestures for all elements, but tries to avoid conflicts with native |
| * browser gestures on particular elements. In particular, it allows text selection on inputs |
| * and textareas, and preserves the native browser dragging on elements marked as `draggable`. |
| * - `on` - Enables touch gestures for all elements and disables native |
| * browser gestures with no exceptions. |
| * - `off` - Disables touch gestures. Note that this will prevent the tooltip from |
| * showing on touch devices. |
| */ |
| this.touchGestures = 'auto'; |
| this._message = ''; |
| /** Manually-bound passive event listeners. */ |
| this._passiveListeners = []; |
| /** Emits when the component is destroyed. */ |
| this._destroyed = new Subject(); |
| /** |
| * Handles the keydown events on the host element. |
| * Needs to be an arrow function so that we can use it in addEventListener. |
| */ |
| this._handleKeydown = (event) => { |
| if (this._isTooltipVisible() && event.keyCode === ESCAPE && !hasModifierKey(event)) { |
| event.preventDefault(); |
| event.stopPropagation(); |
| this._ngZone.run(() => this.hide(0)); |
| } |
| }; |
| this._scrollStrategy = scrollStrategy; |
| if (_defaultOptions) { |
| if (_defaultOptions.position) { |
| this.position = _defaultOptions.position; |
| } |
| if (_defaultOptions.touchGestures) { |
| this.touchGestures = _defaultOptions.touchGestures; |
| } |
| } |
| _ngZone.runOutsideAngular(() => { |
| _elementRef.nativeElement.addEventListener('keydown', this._handleKeydown); |
| }); |
| } |
| /** Allows the user to define the position of the tooltip relative to the parent element */ |
| get position() { return this._position; } |
| set position(value) { |
| if (value !== this._position) { |
| this._position = value; |
| if (this._overlayRef) { |
| this._updatePosition(); |
| if (this._tooltipInstance) { |
| this._tooltipInstance.show(0); |
| } |
| this._overlayRef.updatePosition(); |
| } |
| } |
| } |
| /** Disables the display of the tooltip. */ |
| get disabled() { return this._disabled; } |
| set disabled(value) { |
| this._disabled = coerceBooleanProperty(value); |
| // If tooltip is disabled, hide immediately. |
| if (this._disabled) { |
| this.hide(0); |
| } |
| else { |
| this._setupPointerEnterEventsIfNeeded(); |
| } |
| } |
| /** The message to be displayed in the tooltip */ |
| get message() { return this._message; } |
| set message(value) { |
| this._ariaDescriber.removeDescription(this._elementRef.nativeElement, this._message, 'tooltip'); |
| // If the message is not a string (e.g. number), convert it to a string and trim it. |
| // Must convert with `String(value)`, not `${value}`, otherwise Closure Compiler optimises |
| // away the string-conversion: https://github.com/angular/components/issues/20684 |
| this._message = value != null ? String(value).trim() : ''; |
| if (!this._message && this._isTooltipVisible()) { |
| this.hide(0); |
| } |
| else { |
| this._setupPointerEnterEventsIfNeeded(); |
| this._updateTooltipMessage(); |
| this._ngZone.runOutsideAngular(() => { |
| // The `AriaDescriber` has some functionality that avoids adding a description if it's the |
| // same as the `aria-label` of an element, however we can't know whether the tooltip trigger |
| // has a data-bound `aria-label` or when it'll be set for the first time. We can avoid the |
| // issue by deferring the description by a tick so Angular has time to set the `aria-label`. |
| Promise.resolve().then(() => { |
| this._ariaDescriber.describe(this._elementRef.nativeElement, this.message, 'tooltip'); |
| }); |
| }); |
| } |
| } |
| /** Classes to be passed to the tooltip. Supports the same syntax as `ngClass`. */ |
| get tooltipClass() { return this._tooltipClass; } |
| set tooltipClass(value) { |
| this._tooltipClass = value; |
| if (this._tooltipInstance) { |
| this._setTooltipClass(this._tooltipClass); |
| } |
| } |
| ngAfterViewInit() { |
| // This needs to happen after view init so the initial values for all inputs have been set. |
| this._viewInitialized = true; |
| this._setupPointerEnterEventsIfNeeded(); |
| this._focusMonitor.monitor(this._elementRef) |
| .pipe(takeUntil(this._destroyed)) |
| .subscribe(origin => { |
| // Note that the focus monitor runs outside the Angular zone. |
| if (!origin) { |
| this._ngZone.run(() => this.hide(0)); |
| } |
| else if (origin === 'keyboard') { |
| this._ngZone.run(() => this.show()); |
| } |
| }); |
| } |
| /** |
| * Dispose the tooltip when destroyed. |
| */ |
| ngOnDestroy() { |
| const nativeElement = this._elementRef.nativeElement; |
| clearTimeout(this._touchstartTimeout); |
| if (this._overlayRef) { |
| this._overlayRef.dispose(); |
| this._tooltipInstance = null; |
| } |
| // Clean up the event listeners set in the constructor |
| nativeElement.removeEventListener('keydown', this._handleKeydown); |
| this._passiveListeners.forEach(([event, listener]) => { |
| nativeElement.removeEventListener(event, listener, passiveListenerOptions); |
| }); |
| this._passiveListeners.length = 0; |
| this._destroyed.next(); |
| this._destroyed.complete(); |
| this._ariaDescriber.removeDescription(nativeElement, this.message, 'tooltip'); |
| this._focusMonitor.stopMonitoring(nativeElement); |
| } |
| /** Shows the tooltip after the delay in ms, defaults to tooltip-delay-show or 0ms if no input */ |
| show(delay = this.showDelay) { |
| if (this.disabled || !this.message || (this._isTooltipVisible() && |
| !this._tooltipInstance._showTimeoutId && !this._tooltipInstance._hideTimeoutId)) { |
| return; |
| } |
| const overlayRef = this._createOverlay(); |
| this._detach(); |
| this._portal = this._portal || new ComponentPortal(TooltipComponent, this._viewContainerRef); |
| this._tooltipInstance = overlayRef.attach(this._portal).instance; |
| this._tooltipInstance.afterHidden() |
| .pipe(takeUntil(this._destroyed)) |
| .subscribe(() => this._detach()); |
| this._setTooltipClass(this._tooltipClass); |
| this._updateTooltipMessage(); |
| this._tooltipInstance.show(delay); |
| } |
| /** Hides the tooltip after the delay in ms, defaults to tooltip-delay-hide or 0ms if no input */ |
| hide(delay = this.hideDelay) { |
| if (this._tooltipInstance) { |
| this._tooltipInstance.hide(delay); |
| } |
| } |
| /** Shows/hides the tooltip */ |
| toggle() { |
| this._isTooltipVisible() ? this.hide() : this.show(); |
| } |
| /** Returns true if the tooltip is currently visible to the user */ |
| _isTooltipVisible() { |
| return !!this._tooltipInstance && this._tooltipInstance.isVisible(); |
| } |
| /** Create the overlay config and position strategy */ |
| _createOverlay() { |
| if (this._overlayRef) { |
| return this._overlayRef; |
| } |
| const scrollableAncestors = this._scrollDispatcher.getAncestorScrollContainers(this._elementRef); |
| // Create connected position strategy that listens for scroll events to reposition. |
| const strategy = this._overlay.position() |
| .flexibleConnectedTo(this._elementRef) |
| .withTransformOriginOn('.mat-tooltip') |
| .withFlexibleDimensions(false) |
| .withViewportMargin(8) |
| .withScrollableContainers(scrollableAncestors); |
| strategy.positionChanges.pipe(takeUntil(this._destroyed)).subscribe(change => { |
| if (this._tooltipInstance) { |
| if (change.scrollableViewProperties.isOverlayClipped && this._tooltipInstance.isVisible()) { |
| // After position changes occur and the overlay is clipped by |
| // a parent scrollable then close the tooltip. |
| this._ngZone.run(() => this.hide(0)); |
| } |
| } |
| }); |
| this._overlayRef = this._overlay.create({ |
| direction: this._dir, |
| positionStrategy: strategy, |
| panelClass: TOOLTIP_PANEL_CLASS, |
| scrollStrategy: this._scrollStrategy() |
| }); |
| this._updatePosition(); |
| this._overlayRef.detachments() |
| .pipe(takeUntil(this._destroyed)) |
| .subscribe(() => this._detach()); |
| return this._overlayRef; |
| } |
| /** Detaches the currently-attached tooltip. */ |
| _detach() { |
| if (this._overlayRef && this._overlayRef.hasAttached()) { |
| this._overlayRef.detach(); |
| } |
| this._tooltipInstance = null; |
| } |
| /** Updates the position of the current tooltip. */ |
| _updatePosition() { |
| const position = this._overlayRef.getConfig().positionStrategy; |
| const origin = this._getOrigin(); |
| const overlay = this._getOverlayPosition(); |
| position.withPositions([ |
| Object.assign(Object.assign({}, origin.main), overlay.main), |
| Object.assign(Object.assign({}, origin.fallback), overlay.fallback) |
| ]); |
| } |
| /** |
| * Returns the origin position and a fallback position based on the user's position preference. |
| * The fallback position is the inverse of the origin (e.g. `'below' -> 'above'`). |
| */ |
| _getOrigin() { |
| const isLtr = !this._dir || this._dir.value == 'ltr'; |
| const position = this.position; |
| let originPosition; |
| if (position == 'above' || position == 'below') { |
| originPosition = { originX: 'center', originY: position == 'above' ? 'top' : 'bottom' }; |
| } |
| else if (position == 'before' || |
| (position == 'left' && isLtr) || |
| (position == 'right' && !isLtr)) { |
| originPosition = { originX: 'start', originY: 'center' }; |
| } |
| else if (position == 'after' || |
| (position == 'right' && isLtr) || |
| (position == 'left' && !isLtr)) { |
| originPosition = { originX: 'end', originY: 'center' }; |
| } |
| else if (typeof ngDevMode === 'undefined' || ngDevMode) { |
| throw getMatTooltipInvalidPositionError(position); |
| } |
| const { x, y } = this._invertPosition(originPosition.originX, originPosition.originY); |
| return { |
| main: originPosition, |
| fallback: { originX: x, originY: y } |
| }; |
| } |
| /** Returns the overlay position and a fallback position based on the user's preference */ |
| _getOverlayPosition() { |
| const isLtr = !this._dir || this._dir.value == 'ltr'; |
| const position = this.position; |
| let overlayPosition; |
| if (position == 'above') { |
| overlayPosition = { overlayX: 'center', overlayY: 'bottom' }; |
| } |
| else if (position == 'below') { |
| overlayPosition = { overlayX: 'center', overlayY: 'top' }; |
| } |
| else if (position == 'before' || |
| (position == 'left' && isLtr) || |
| (position == 'right' && !isLtr)) { |
| overlayPosition = { overlayX: 'end', overlayY: 'center' }; |
| } |
| else if (position == 'after' || |
| (position == 'right' && isLtr) || |
| (position == 'left' && !isLtr)) { |
| overlayPosition = { overlayX: 'start', overlayY: 'center' }; |
| } |
| else if (typeof ngDevMode === 'undefined' || ngDevMode) { |
| throw getMatTooltipInvalidPositionError(position); |
| } |
| const { x, y } = this._invertPosition(overlayPosition.overlayX, overlayPosition.overlayY); |
| return { |
| main: overlayPosition, |
| fallback: { overlayX: x, overlayY: y } |
| }; |
| } |
| /** Updates the tooltip message and repositions the overlay according to the new message length */ |
| _updateTooltipMessage() { |
| // Must wait for the message to be painted to the tooltip so that the overlay can properly |
| // calculate the correct positioning based on the size of the text. |
| if (this._tooltipInstance) { |
| this._tooltipInstance.message = this.message; |
| this._tooltipInstance._markForCheck(); |
| this._ngZone.onMicrotaskEmpty.pipe(take(1), takeUntil(this._destroyed)).subscribe(() => { |
| if (this._tooltipInstance) { |
| this._overlayRef.updatePosition(); |
| } |
| }); |
| } |
| } |
| /** Updates the tooltip class */ |
| _setTooltipClass(tooltipClass) { |
| if (this._tooltipInstance) { |
| this._tooltipInstance.tooltipClass = tooltipClass; |
| this._tooltipInstance._markForCheck(); |
| } |
| } |
| /** Inverts an overlay position. */ |
| _invertPosition(x, y) { |
| if (this.position === 'above' || this.position === 'below') { |
| if (y === 'top') { |
| y = 'bottom'; |
| } |
| else if (y === 'bottom') { |
| y = 'top'; |
| } |
| } |
| else { |
| if (x === 'end') { |
| x = 'start'; |
| } |
| else if (x === 'start') { |
| x = 'end'; |
| } |
| } |
| return { x, y }; |
| } |
| /** Binds the pointer events to the tooltip trigger. */ |
| _setupPointerEnterEventsIfNeeded() { |
| // Optimization: Defer hooking up events if there's no message or the tooltip is disabled. |
| if (this._disabled || !this.message || !this._viewInitialized || |
| this._passiveListeners.length) { |
| return; |
| } |
| // The mouse events shouldn't be bound on mobile devices, because they can prevent the |
| // first tap from firing its click event or can cause the tooltip to open for clicks. |
| if (this._platformSupportsMouseEvents()) { |
| this._passiveListeners |
| .push(['mouseenter', () => { |
| this._setupPointerExitEventsIfNeeded(); |
| this.show(); |
| }]); |
| } |
| else if (this.touchGestures !== 'off') { |
| this._disableNativeGesturesIfNecessary(); |
| this._passiveListeners |
| .push(['touchstart', () => { |
| // Note that it's important that we don't `preventDefault` here, |
| // because it can prevent click events from firing on the element. |
| this._setupPointerExitEventsIfNeeded(); |
| clearTimeout(this._touchstartTimeout); |
| this._touchstartTimeout = setTimeout(() => this.show(), LONGPRESS_DELAY); |
| }]); |
| } |
| this._addListeners(this._passiveListeners); |
| } |
| _setupPointerExitEventsIfNeeded() { |
| if (this._pointerExitEventsInitialized) { |
| return; |
| } |
| this._pointerExitEventsInitialized = true; |
| const exitListeners = []; |
| if (this._platformSupportsMouseEvents()) { |
| exitListeners.push(['mouseleave', () => this.hide()], ['wheel', event => this._wheelListener(event)]); |
| } |
| else if (this.touchGestures !== 'off') { |
| this._disableNativeGesturesIfNecessary(); |
| const touchendListener = () => { |
| clearTimeout(this._touchstartTimeout); |
| this.hide(this._defaultOptions.touchendHideDelay); |
| }; |
| exitListeners.push(['touchend', touchendListener], ['touchcancel', touchendListener]); |
| } |
| this._addListeners(exitListeners); |
| this._passiveListeners.push(...exitListeners); |
| } |
| _addListeners(listeners) { |
| listeners.forEach(([event, listener]) => { |
| this._elementRef.nativeElement.addEventListener(event, listener, passiveListenerOptions); |
| }); |
| } |
| _platformSupportsMouseEvents() { |
| return !this._platform.IOS && !this._platform.ANDROID; |
| } |
| /** Listener for the `wheel` event on the element. */ |
| _wheelListener(event) { |
| if (this._isTooltipVisible()) { |
| // @breaking-change 11.0.0 Remove `|| document` once the document is a required param. |
| const doc = this._document || document; |
| const elementUnderPointer = doc.elementFromPoint(event.clientX, event.clientY); |
| const element = this._elementRef.nativeElement; |
| // On non-touch devices we depend on the `mouseleave` event to close the tooltip, but it |
| // won't fire if the user scrolls away using the wheel without moving their cursor. We |
| // work around it by finding the element under the user's cursor and closing the tooltip |
| // if it's not the trigger. |
| if (elementUnderPointer !== element && !element.contains(elementUnderPointer)) { |
| this.hide(); |
| } |
| } |
| } |
| /** Disables the native browser gestures, based on how the tooltip has been configured. */ |
| _disableNativeGesturesIfNecessary() { |
| const gestures = this.touchGestures; |
| if (gestures !== 'off') { |
| const element = this._elementRef.nativeElement; |
| const style = element.style; |
| // If gestures are set to `auto`, we don't disable text selection on inputs and |
| // textareas, because it prevents the user from typing into them on iOS Safari. |
| if (gestures === 'on' || (element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA')) { |
| style.userSelect = style.msUserSelect = style.webkitUserSelect = |
| style.MozUserSelect = 'none'; |
| } |
| // If we have `auto` gestures and the element uses native HTML dragging, |
| // we don't set `-webkit-user-drag` because it prevents the native behavior. |
| if (gestures === 'on' || !element.draggable) { |
| style.webkitUserDrag = 'none'; |
| } |
| style.touchAction = 'none'; |
| style.webkitTapHighlightColor = 'transparent'; |
| } |
| } |
| } |
| MatTooltip.decorators = [ |
| { type: Directive, args: [{ |
| selector: '[matTooltip]', |
| exportAs: 'matTooltip', |
| host: { |
| 'class': 'mat-tooltip-trigger' |
| } |
| },] } |
| ]; |
| MatTooltip.ctorParameters = () => [ |
| { type: Overlay }, |
| { type: ElementRef }, |
| { type: ScrollDispatcher }, |
| { type: ViewContainerRef }, |
| { type: NgZone }, |
| { type: Platform }, |
| { type: AriaDescriber }, |
| { type: FocusMonitor }, |
| { type: undefined, decorators: [{ type: Inject, args: [MAT_TOOLTIP_SCROLL_STRATEGY,] }] }, |
| { type: Directionality, decorators: [{ type: Optional }] }, |
| { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_TOOLTIP_DEFAULT_OPTIONS,] }] }, |
| { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] } |
| ]; |
| MatTooltip.propDecorators = { |
| position: [{ type: Input, args: ['matTooltipPosition',] }], |
| disabled: [{ type: Input, args: ['matTooltipDisabled',] }], |
| showDelay: [{ type: Input, args: ['matTooltipShowDelay',] }], |
| hideDelay: [{ type: Input, args: ['matTooltipHideDelay',] }], |
| touchGestures: [{ type: Input, args: ['matTooltipTouchGestures',] }], |
| message: [{ type: Input, args: ['matTooltip',] }], |
| tooltipClass: [{ type: Input, args: ['matTooltipClass',] }] |
| }; |
| /** |
| * Internal component that wraps the tooltip's content. |
| * @docs-private |
| */ |
| class TooltipComponent { |
| constructor(_changeDetectorRef, _breakpointObserver) { |
| this._changeDetectorRef = _changeDetectorRef; |
| this._breakpointObserver = _breakpointObserver; |
| /** Property watched by the animation framework to show or hide the tooltip */ |
| this._visibility = 'initial'; |
| /** Whether interactions on the page should close the tooltip */ |
| this._closeOnInteraction = false; |
| /** Subject for notifying that the tooltip has been hidden from the view */ |
| this._onHide = new Subject(); |
| /** Stream that emits whether the user has a handset-sized display. */ |
| this._isHandset = this._breakpointObserver.observe(Breakpoints.Handset); |
| } |
| /** |
| * Shows the tooltip with an animation originating from the provided origin |
| * @param delay Amount of milliseconds to the delay showing the tooltip. |
| */ |
| show(delay) { |
| // Cancel the delayed hide if it is scheduled |
| if (this._hideTimeoutId) { |
| clearTimeout(this._hideTimeoutId); |
| this._hideTimeoutId = null; |
| } |
| // Body interactions should cancel the tooltip if there is a delay in showing. |
| this._closeOnInteraction = true; |
| this._showTimeoutId = setTimeout(() => { |
| this._visibility = 'visible'; |
| this._showTimeoutId = null; |
| // Mark for check so if any parent component has set the |
| // ChangeDetectionStrategy to OnPush it will be checked anyways |
| this._markForCheck(); |
| }, delay); |
| } |
| /** |
| * Begins the animation to hide the tooltip after the provided delay in ms. |
| * @param delay Amount of milliseconds to delay showing the tooltip. |
| */ |
| hide(delay) { |
| // Cancel the delayed show if it is scheduled |
| if (this._showTimeoutId) { |
| clearTimeout(this._showTimeoutId); |
| this._showTimeoutId = null; |
| } |
| this._hideTimeoutId = setTimeout(() => { |
| this._visibility = 'hidden'; |
| this._hideTimeoutId = null; |
| // Mark for check so if any parent component has set the |
| // ChangeDetectionStrategy to OnPush it will be checked anyways |
| this._markForCheck(); |
| }, delay); |
| } |
| /** Returns an observable that notifies when the tooltip has been hidden from view. */ |
| afterHidden() { |
| return this._onHide; |
| } |
| /** Whether the tooltip is being displayed. */ |
| isVisible() { |
| return this._visibility === 'visible'; |
| } |
| ngOnDestroy() { |
| this._onHide.complete(); |
| } |
| _animationStart() { |
| this._closeOnInteraction = false; |
| } |
| _animationDone(event) { |
| const toState = event.toState; |
| if (toState === 'hidden' && !this.isVisible()) { |
| this._onHide.next(); |
| } |
| if (toState === 'visible' || toState === 'hidden') { |
| this._closeOnInteraction = true; |
| } |
| } |
| /** |
| * Interactions on the HTML body should close the tooltip immediately as defined in the |
| * material design spec. |
| * https://material.io/design/components/tooltips.html#behavior |
| */ |
| _handleBodyInteraction() { |
| if (this._closeOnInteraction) { |
| this.hide(0); |
| } |
| } |
| /** |
| * Marks that the tooltip needs to be checked in the next change detection run. |
| * Mainly used for rendering the initial text before positioning a tooltip, which |
| * can be problematic in components with OnPush change detection. |
| */ |
| _markForCheck() { |
| this._changeDetectorRef.markForCheck(); |
| } |
| } |
| TooltipComponent.decorators = [ |
| { type: Component, args: [{ |
| selector: 'mat-tooltip-component', |
| template: "<div class=\"mat-tooltip\"\n [ngClass]=\"tooltipClass\"\n [class.mat-tooltip-handset]=\"(_isHandset | async)?.matches\"\n [@state]=\"_visibility\"\n (@state.start)=\"_animationStart()\"\n (@state.done)=\"_animationDone($event)\">{{message}}</div>\n", |
| encapsulation: ViewEncapsulation.None, |
| changeDetection: ChangeDetectionStrategy.OnPush, |
| animations: [matTooltipAnimations.tooltipState], |
| host: { |
| // Forces the element to have a layout in IE and Edge. This fixes issues where the element |
| // won't be rendered if the animations are disabled or there is no web animations polyfill. |
| '[style.zoom]': '_visibility === "visible" ? 1 : null', |
| '(body:click)': 'this._handleBodyInteraction()', |
| '(body:auxclick)': 'this._handleBodyInteraction()', |
| 'aria-hidden': 'true', |
| }, |
| 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}.cdk-high-contrast-active .mat-tooltip{outline:solid 1px}.mat-tooltip-handset{margin:24px;padding-left:16px;padding-right:16px}\n"] |
| },] } |
| ]; |
| TooltipComponent.ctorParameters = () => [ |
| { type: ChangeDetectorRef }, |
| { type: BreakpointObserver } |
| ]; |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| class MatTooltipModule { |
| } |
| MatTooltipModule.decorators = [ |
| { type: NgModule, args: [{ |
| imports: [ |
| A11yModule, |
| CommonModule, |
| OverlayModule, |
| MatCommonModule, |
| ], |
| exports: [MatTooltip, TooltipComponent, MatCommonModule, CdkScrollableModule], |
| declarations: [MatTooltip, TooltipComponent], |
| entryComponents: [TooltipComponent], |
| providers: [MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY_PROVIDER] |
| },] } |
| ]; |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| |
| /** |
| * Generated bundle index. Do not edit. |
| */ |
| |
| export { MAT_TOOLTIP_DEFAULT_OPTIONS, MAT_TOOLTIP_DEFAULT_OPTIONS_FACTORY, MAT_TOOLTIP_SCROLL_STRATEGY, MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY, MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY_PROVIDER, MatTooltip, MatTooltipModule, SCROLL_THROTTLE_MS, TOOLTIP_PANEL_CLASS, TooltipComponent, getMatTooltipInvalidPositionError, matTooltipAnimations }; |
| //# sourceMappingURL=tooltip.js.map |