import { AnimationEvent } from '@angular/animations' ; import { AriaDescriber , ConfigurableFocusTrapFactory , FocusMonitor , FocusOrigin , FocusTrap , } from '@angular/cdk/a11y' ; import { coerceBooleanProperty , coerceNumberProperty , NumberInput } from '@angular/cdk/coercion' ; import { ESCAPE , hasModifierKey } from '@angular/cdk/keycodes' ; import { BreakpointObserver , BreakpointState } from '@angular/cdk/layout' ; import { ConnectionPositionPair , Overlay , OverlayRef , ScrollStrategy } from '@angular/cdk/overlay' ; import { normalizePassiveListenerOptions , Platform , _getFocusedElementPierceShadowDom , } from '@angular/cdk/platform' ; import { ComponentPortal , ComponentType } from '@angular/cdk/portal' ; import { ScrollDispatcher } from '@angular/cdk/scrolling' ; import { DOCUMENT } from '@angular/common' ; import { AfterViewInit , ChangeDetectionStrategy , ChangeDetectorRef , Component , Directive , ElementRef , EventEmitter , Inject , InjectionToken , Input , NgZone , OnDestroy , Optional , Output , TemplateRef , ViewContainerRef , ViewEncapsulation , } from '@angular/core' ; import { Breakpoints } from '@sbb-esta/angular/core' ; import { Observable , Subject } from 'rxjs' ; import { filter , take , takeUntil } from 'rxjs/operators' ; import { sbbTooltipAnimations } from './tooltip-animations' ; /** Possible positions for a tooltip. */ export type TooltipPosition = 'left' | 'right' | 'above' | 'below' ; /**
 * Options for how the tooltip trigger should handle touch gestures.
 * See `SbbTooltip.touchGestures` for more information.
 */ export type TooltipTouchGestures = 'auto' | 'on' | 'off' ; /** Possible visibility states of a tooltip. */ export type TooltipVisibility = 'initial' | 'visible' | 'hidden' ; /** Time in ms to throttle repositioning after scroll events. */ export const SCROLL_THROTTLE_MS = 20 ; const PANEL_CLASS = '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 ; docs-private export function getSbbTooltipInvalidPositionError ( position ) { return Error ( `Tooltip position "${ position }" is invalid.` ) ; } /** Injection token that determines the scroll handling while a tooltip is visible. */ export const SBB_TOOLTIP_SCROLL_STRATEGY = new InjectionToken < ( ) => ScrollStrategy > ( 'sbb-tooltip-scroll-strategy' ) ; docs-private export function SBB_TOOLTIP_SCROLL_STRATEGY_FACTORY ( overlay ) { return ( ) => overlay . scrollStrategies . reposition ( { scrollThrottle : SCROLL_THROTTLE_MS } ) ; } docs-private export const SBB_TOOLTIP_SCROLL_STRATEGY_FACTORY_PROVIDER = { provide : SBB_TOOLTIP_SCROLL_STRATEGY , deps : [ Overlay ] , useFactory : SBB_TOOLTIP_SCROLL_STRATEGY_FACTORY , } ; /** Default `sbbTooltip` options that can be overridden. */ export interface SbbTooltipDefaultOptions { showDelay : number ; hideDelay : number ; touchendHideDelay : number ; touchGestures ? : TooltipTouchGestures ; /** Whether the tooltip should focus the first focusable element on open. */ autoFocus ? : boolean ; /**
     * Whether the tooltip should restore focus to the
     * previously-focused element, after it's closed.
     */ restoreFocus ? : boolean ; } /** Injection token to be used to override the default options for `sbbTooltip`. */ export const SBB_TOOLTIP_DEFAULT_OPTIONS = new InjectionToken < SbbTooltipDefaultOptions > ( 'sbb-tooltip-default-options' , { providedIn : 'root' , factory : SBB_TOOLTIP_DEFAULT_OPTIONS_FACTORY , } ) ; docs-private export function SBB_TOOLTIP_DEFAULT_OPTIONS_FACTORY ( ) { return { showDelay : 0 , hideDelay : 0 , touchendHideDelay : 1500 , autoFocus : true , restoreFocus : true , } ; } /** Tooltip event, which is emitted for certain events. */ export class SbbTooltipChangeEvent { constructor ( /** Instance of tooltip component. */ public instance ) { } } @ Directive ( ) export abstract class _SbbTooltipBase < T extends _TooltipComponentBase > implements OnDestroy , AfterViewInit { _overlayRef : OverlayRef | null ; _tooltipInstance : T | null ; private _portal : ComponentPortal < T > ; private _disabled : boolean = false ; private _tooltipClass : string | string [ ] | Set < string > | { [ key ] : any ; } ; private _scrollStrategy : ( ) => ScrollStrategy ; private _viewInitialized = false ; private _pointerExitEventsInitialized = false ; protected abstract readonly _tooltipComponent : ComponentType < T > ; protected _viewportMargin : number = 8 ; private _currentPosition : TooltipPosition ; protected readonly _cssClassPrefix : string = 'sbb' ; /**
     * The trigger event, on which the tooltip opens.
     * This is primarily used for sbb-tooltip and should be used with care.
     */ @ Input ( 'sbbTooltipTrigger' ) trigger : 'click' | 'hover' = 'hover' ; /** Disables the display of the tooltip. */ @ Input ( 'sbbTooltipDisabled' ) get disabled ( ) : boolean { return this . _disabled ; } set disabled ( value ) { this . _disabled = coerceBooleanProperty ( value ) ; if ( this . _disabled ) { this . hide ( 0 ) ; } else { this . _setupPointerEnterEventsIfNeeded ( ) ; } } /** The default delay in ms before showing the tooltip after show is called */ @ Input ( 'sbbTooltipShowDelay' ) get showDelay ( ) : number { return this . _showDelay ; } set showDelay ( value ) { this . _showDelay = coerceNumberProperty ( value ) ; } private _showDelay = this . _defaultOptions . showDelay ; /** The default delay in ms before hiding the tooltip after hide is called */ @ Input ( 'sbbTooltipHideDelay' ) get hideDelay ( ) : number { return this . _hideDelay ; } set hideDelay ( value ) { this . _hideDelay = coerceNumberProperty ( value ) ; } private _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, sbb-angular 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.
     */ @ Input ( 'sbbTooltipTouchGestures' ) touchGestures : TooltipTouchGestures = 'auto' ; /** The message to be displayed in the tooltip */ @ Input ( 'sbbTooltip' ) get message ( ) { return this . _message ; } set message ( value ) { if ( typeof this . _message === 'string' ) { this . _ariaDescriber . removeDescription ( this . _elementRef . nativeElement , this . _message , 'tooltip' ) ; } this . _message = value instanceof TemplateRef ? value : value != null ? String ( value ) . trim ( ) : '' ; if ( ! this . _message && this . _isTooltipVisible ( ) ) { this . hide ( 0 ) ; } else { this . _setupPointerEnterEventsIfNeeded ( ) ; this . _updateTooltipMessage ( ) ; if ( typeof this . _message === 'string' ) { this . _ngZone . runOutsideAngular ( ( ) => { Promise . resolve ( ) . then ( ( ) => { this . _ariaDescriber . describe ( this . _elementRef . nativeElement , this . _message as string , 'tooltip' ) ; } ) ; } ) ; } } } private _message : string | TemplateRef < any > = '' ; /** Classes to be passed to the tooltip. Supports the same syntax as `ngClass`. */ @ Input ( 'sbbTooltipClass' ) get tooltipClass ( ) { return this . _tooltipClass ; } set tooltipClass ( value ) { this . _tooltipClass = value ; if ( this . _tooltipInstance ) { this . _setTooltipClass ( this . _tooltipClass ) ; } } /** Manually-bound passive event listeners. */ private readonly _passiveListeners : ( readonly [ string , EventListenerOrEventListenerObject ] ) [ ] = [ ] ; /** Reference to the current document. */ private _document : Document ; /** Timer started at the last `touchstart` event. */ private _touchstartTimeout : any ; /** Emits when the component is destroyed. */ private readonly _destroyed = new Subject < void > ( ) ; /** Event emitted when the tooltip is opened. */ @ Output ( ) readonly opened : EventEmitter < SbbTooltipChangeEvent > = new EventEmitter < SbbTooltipChangeEvent > ( ) ; constructor ( private _overlay , private _elementRef , private _scrollDispatcher , private _viewContainerRef , private _ngZone , private _platform , private _ariaDescriber , private _focusMonitor , scrollStrategy , private _defaultOptions , @ Inject ( DOCUMENT ) document ) { this . _scrollStrategy = scrollStrategy ; this . _document = document ; if ( _defaultOptions ?. touchGestures ) { this . touchGestures = _defaultOptions . touchGestures ; } _ngZone . runOutsideAngular ( ( ) => { _elementRef . nativeElement . addEventListener ( 'keydown' , this . _handleKeydown ) ; } ) ; } ngAfterViewInit ( ) { this . _viewInitialized = true ; this . _setupPointerEnterEventsIfNeeded ( ) ; this . _focusMonitor . monitor ( this . _elementRef ) . pipe ( filter ( ( ) => this . trigger === 'hover' ) , takeUntil ( this . _destroyed ) ) . subscribe ( ( origin ) => { 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 ; } 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 ( ) ; if ( typeof this . _message === 'string' ) { 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 ( this . _tooltipComponent , this . _viewContainerRef ) ; this . _tooltipInstance = overlayRef . attach ( this . _portal ) . instance ; if ( this . message instanceof TemplateRef ) { this . _ariaDescriber . describe ( this . _elementRef . nativeElement , this . _tooltipInstance . _elementRef . nativeElement ) ; } this . _tooltipInstance . afterHidden ( ) . pipe ( takeUntil ( this . _destroyed ) ) . subscribe ( ( ) => this . _detach ( ) ) ; this . _setTooltipClass ( this . _tooltipClass ) ; this . _updateTooltipMessage ( ) ; if ( this . trigger === 'click' ) { this . _tooltipInstance . _config = { autoFocus : this . _defaultOptions . autoFocus ?? true , restoreFocus : this . _defaultOptions . restoreFocus ?? true , } ; this . _tooltipInstance . _initializeWithAttachedContent ( ) ; } else { this . _tooltipInstance . _config = undefined ; } this . opened . emit ( new SbbTooltipChangeEvent ( this ) ) ; 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 ( ) ; } /**
     * Handles the keydown events on the host element.
     * Needs to be an arrow function so that we can use it in addEventListener.
     */ private _handleKeydown = ( event ) => { if ( this . _isTooltipVisible ( ) && event . keyCode === ESCAPE && ! hasModifierKey ( event ) ) { event . preventDefault ( ) ; event . stopPropagation ( ) ; this . _ngZone . run ( ( ) => this . hide ( 0 ) ) ; } } ; /** Create the overlay config and position strategy */ private _createOverlay ( ) { if ( this . _overlayRef ) { return this . _overlayRef ; } const scrollableAncestors = this . _scrollDispatcher . getAncestorScrollContainers ( this . _elementRef ) ; const classPrefix = `${ this . _cssClassPrefix }-${ PANEL_CLASS }-` ; const strategy = this . _overlay . position ( ) . flexibleConnectedTo ( this . _elementRef ) . withTransformOriginOn ( `.${ this . _cssClassPrefix }-tooltip` ) . withFlexibleDimensions ( false ) . withViewportMargin ( this . _viewportMargin ) . withScrollableContainers ( scrollableAncestors ) . withPositions ( [ { originX : 'center' , originY : 'bottom' , overlayX : 'center' , overlayY : 'top' , offsetY : 2 , } , { originX : 'end' , originY : 'bottom' , overlayX : 'end' , overlayY : 'top' , offsetX : 5 , offsetY : 2 , panelClass : `${ classPrefix }left` , } , { originX : 'start' , originY : 'bottom' , overlayX : 'start' , overlayY : 'top' , offsetX : - 5 , offsetY : 2 , panelClass : `${ classPrefix }right` , } , { originX : 'center' , originY : 'top' , overlayX : 'center' , overlayY : 'bottom' , offsetY : - 2 , } , { originX : 'end' , originY : 'top' , overlayX : 'end' , overlayY : 'bottom' , offsetX : 5 , offsetY : - 2 , panelClass : `${ classPrefix }left` , } , { originX : 'start' , originY : 'top' , overlayX : 'start' , overlayY : 'bottom' , offsetX : - 5 , offsetY : - 2 , panelClass : `${ classPrefix }right` , } , ] ) ; strategy . positionChanges . pipe ( takeUntil ( this . _destroyed ) ) . subscribe ( ( change ) => { this . _updateCurrentPositionClass ( change . connectionPair ) ; if ( this . _tooltipInstance && change . scrollableViewProperties . isOriginOutsideView && this . _tooltipInstance . isVisible ( ) ) { this . _ngZone . run ( ( ) => this . hide ( 0 ) ) ; } } ) ; this . _overlayRef = this . _overlay . create ( { positionStrategy : strategy , panelClass : `${ this . _cssClassPrefix }-${ PANEL_CLASS }` , scrollStrategy : this . _scrollStrategy ( ) , } ) ; this . _overlayRef . detachments ( ) . pipe ( takeUntil ( this . _destroyed ) ) . subscribe ( ( ) => this . _detach ( ) ) ; this . _overlayRef . keydownEvents ( ) . pipe ( filter ( ( e ) => e . keyCode === ESCAPE ) , takeUntil ( this . _destroyed ) ) . subscribe ( ( ) => this . _tooltipInstance ?. _handleBodyInteraction ( ) ) ; this . _overlayRef . outsidePointerEvents ( ) . pipe ( takeUntil ( this . _destroyed ) ) . subscribe ( ( ) => this . _tooltipInstance ?. _handleBodyInteraction ( ) ) ; return this . _overlayRef ; } /** Detaches the currently-attached tooltip. */ private _detach ( ) { if ( this . _overlayRef && this . _overlayRef . hasAttached ( ) ) { if ( this . message instanceof TemplateRef && this . _tooltipInstance ) { this . _ariaDescriber . removeDescription ( this . _elementRef . nativeElement , this . _tooltipInstance . _elementRef . nativeElement ) ; } this . _overlayRef . detach ( ) ; } this . _tooltipInstance = null ; } /** Updates the tooltip message and repositions the overlay according to the new message length */ private _updateTooltipMessage ( ) { 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 */ private _setTooltipClass ( tooltipClass ) { if ( this . _tooltipInstance ) { this . _tooltipInstance . tooltipClass = tooltipClass ; this . _tooltipInstance . _markForCheck ( ) ; } } /** Updates the class on the overlay panel based on the current position of the tooltip. */ private _updateCurrentPositionClass ( connectionPair ) { const { overlayY , originX , originY } = connectionPair ; let newPosition ; if ( overlayY === 'center' ) { newPosition = originX === 'start' ? 'left' : 'right' ; } else { newPosition = overlayY === 'bottom' && originY === 'top' ? 'above' : 'below' ; } if ( newPosition !== this . _currentPosition ) { const overlayRef = this . _overlayRef ; if ( overlayRef ) { const classPrefix = `${ this . _cssClassPrefix }-${ PANEL_CLASS }-` ; overlayRef . removePanelClass ( classPrefix + this . _currentPosition ) ; overlayRef . addPanelClass ( classPrefix + newPosition ) ; } this . _currentPosition = newPosition ; } } /** Binds the pointer events to the tooltip trigger. */ private _setupPointerEnterEventsIfNeeded ( ) { if ( this . _disabled || ! this . message || ! this . _viewInitialized || this . _passiveListeners . length ) { return ; } if ( this . trigger === 'click' ) { this . _passiveListeners . push ( [ 'click' , ( ) => this . show ( 0 ) ] ) ; } else if ( this . _platformSupportsMouseEvents ( ) ) { this . _passiveListeners . push ( [ 'mouseenter' , ( ) => { this . _setupPointerExitEventsIfNeeded ( ) ; this . show ( ) ; } , ] ) ; } else if ( this . touchGestures !== 'off' ) { this . _disableNativeGesturesIfNecessary ( ) ; this . _passiveListeners . push ( [ 'touchstart' , ( ) => { this . _setupPointerExitEventsIfNeeded ( ) ; clearTimeout ( this . _touchstartTimeout ) ; this . _touchstartTimeout = setTimeout ( ( ) => this . show ( ) , LONGPRESS_DELAY ) ; } , ] ) ; } this . _addListeners ( this . _passiveListeners ) ; } private _setupPointerExitEventsIfNeeded ( ) { if ( this . _pointerExitEventsInitialized ) { return ; } this . _pointerExitEventsInitialized = true ; const exitListeners = [ ] ; if ( this . _platformSupportsMouseEvents ( ) ) { exitListeners . push ( [ 'mouseleave' , ( ) => this . hide ( ) ] , [ 'wheel' , ( event ) => this . _wheelListener ( event as WheelEvent ) ] ) ; } 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 ) ; } private _addListeners ( listeners ) { listeners . forEach ( ( [ event , listener ] ) => { this . _elementRef . nativeElement . addEventListener ( event , listener , passiveListenerOptions ) ; } ) ; } private _platformSupportsMouseEvents ( ) { return ! this . _platform . IOS && ! this . _platform . ANDROID ; } /** Listener for the `wheel` event on the element. */ private _wheelListener ( event ) { if ( this . _isTooltipVisible ( ) ) { const elementUnderPointer = this . _document . elementFromPoint ( event . clientX , event . clientY ) ; const element = this . _elementRef . nativeElement ; if ( elementUnderPointer !== element && ! element . contains ( elementUnderPointer ) ) { this . hide ( ) ; } } } /** Disables the native browser gestures, based on how the tooltip has been configured. */ private _disableNativeGesturesIfNecessary ( ) { const gestures = this . touchGestures ; if ( gestures !== 'off' ) { const element = this . _elementRef . nativeElement ; const style = element . style ; if ( gestures === 'on' || ( element . nodeName !== 'INPUT' && element . nodeName !== 'TEXTAREA' ) ) { style . userSelect = ( style as any ) . msUserSelect = style . webkitUserSelect = ( style as any ) . MozUserSelect = 'none' ; } if ( gestures === 'on' || ! element . draggable ) { ( style as any ) . webkitUserDrag = 'none' ; } style . touchAction = 'none' ; ( style as any ) . webkitTapHighlightColor = 'transparent' ; } } } /**
 * Directive that attaches a sbb design tooltip to the host element. Animates the showing and
 * hiding of a tooltip provided position (defaults to below the element).
 */ @ Directive ( { selector : '[sbbTooltip]' , exportAs : 'sbbTooltip' , host : { '[class.sbb-tooltip-trigger]' : 'trigger === "click"' , '[attr.aria-expanded]' : 'trigger === "click" ? _isTooltipVisible() : null' , } , } ) export class SbbTooltip extends _SbbTooltipBase < TooltipComponent > { protected readonly _tooltipComponent : ComponentType < TooltipComponent > = TooltipComponent ; constructor ( overlay , elementRef , scrollDispatcher , viewContainerRef , ngZone , platform , ariaDescriber , focusMonitor , @ Inject ( SBB_TOOLTIP_SCROLL_STRATEGY ) scrollStrategy , @ Optional ( ) @ Inject ( SBB_TOOLTIP_DEFAULT_OPTIONS ) defaultOptions , @ Inject ( DOCUMENT ) document ) { super ( overlay , elementRef , scrollDispatcher , viewContainerRef , ngZone , platform , ariaDescriber , focusMonitor , scrollStrategy , defaultOptions , document ) ; } } @ Directive ( ) export abstract class _TooltipComponentBase implements OnDestroy { /** Message to display in the tooltip */ message : string | TemplateRef < any > ; /** Classes to be added to the tooltip. Supports the same syntax as `ngClass`. */ tooltipClass : string | string [ ] | Set < string > | { [ key ] : any ; } ; /** The timeout ID of any current timer set to show the tooltip */ _showTimeoutId : any ; /** The timeout ID of any current timer set to hide the tooltip */ _hideTimeoutId : any ; /** Property watched by the animation framework to show or hide the tooltip */ _visibility : TooltipVisibility = 'initial' ; /**
     * Type of interaction that led to the tooltip being closed. This is used to determine
     * whether the focus style will be applied when returning focus to its original location
     * after the dialog is closed.
     */ _closeInteractionType : FocusOrigin | null = null ; _config ? : Pick < SbbTooltipDefaultOptions , 'autoFocus' | 'restoreFocus' > = { } ; protected _document : Document ; /** Whether interactions on the page should close the tooltip */ private _closeOnInteraction : boolean = false ; /** The class that traps and manages focus within the tooltip. */ private _focusTrap : FocusTrap ; /** Element that was focused before the tooltip was opened. Save this to restore upon close. */ private _elementFocusedBeforeDialogWasOpened : HTMLElement | null = null ; /** Subject for notifying that the tooltip has been hidden from the view */ private readonly _onHide : Subject < void > = new Subject < void > ( ) ; constructor ( public _elementRef , protected _focusTrapFactory , private _changeDetectorRef , private _focusMonitor , @ Optional ( ) @ Inject ( DOCUMENT ) document ) { this . _document = document ; } param delay show ( delay ) { clearTimeout ( this . _hideTimeoutId ) ; this . _closeOnInteraction = true ; this . _showTimeoutId = setTimeout ( ( ) => { this . _visibility = 'visible' ; this . _showTimeoutId = undefined ; this . _markForCheck ( ) ; } , delay ) ; } param delay hide ( delay ) { clearTimeout ( this . _showTimeoutId ) ; this . _hideTimeoutId = setTimeout ( ( ) => { this . _visibility = 'hidden' ; this . _hideTimeoutId = undefined ; 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 ( ) { clearTimeout ( this . _showTimeoutId ) ; clearTimeout ( this . _hideTimeoutId ) ; this . _onHide . complete ( ) ; } _animationStart ( ) { this . _closeOnInteraction = false ; } _animationDone ( event ) { const toState = event . toState as TooltipVisibility ; if ( toState === 'visible' && this . isVisible ( ) && this . _config ) { this . _trapFocus ( ) ; } if ( toState === 'hidden' && ! this . isVisible ( ) ) { this . _restoreFocus ( ) ; this . _onHide . next ( ) ; } if ( toState === 'visible' || toState === 'hidden' ) { this . _closeOnInteraction = true ; } } /**
     * Interactions on the HTML body should close the tooltip immediately.
     */ _handleBodyInteraction ( ) { if ( this . _closeOnInteraction ) { this . hide ( 0 ) ; } } /** Initializes the dialog container with the attached content. */ _initializeWithAttachedContent ( ) { this . _setupFocusTrap ( ) ; this . _capturePreviouslyFocusedElement ( ) ; this . _focusDialogContainer ( ) ; } /**
     * 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 ( ) ; } /** Moves the focus inside the focus trap. */ protected _trapFocus ( ) { if ( this . _config ?. autoFocus ) { this . _focusTrap . focusInitialElementWhenReady ( ) ; } else if ( ! this . _containsFocus ( ) ) { this . _elementRef . nativeElement . focus ( ) ; } } /** Restores focus to the element that was focused before the dialog opened. */ protected _restoreFocus ( ) { const previousElement = this . _elementFocusedBeforeDialogWasOpened ; if ( this . _config ?. restoreFocus && previousElement && typeof previousElement . focus === 'function' ) { const activeElement = _getFocusedElementPierceShadowDom ( ) ; const element = this . _elementRef . nativeElement ; if ( ! activeElement || activeElement === this . _document . body || activeElement === element || element . contains ( activeElement ) ) { this . _focusMonitor . focusVia ( previousElement , this . _closeInteractionType ) ; this . _closeInteractionType = null ; } } if ( this . _focusTrap ) { this . _focusTrap . destroy ( ) ; } } /** Sets up the focus trap. */ private _setupFocusTrap ( ) { this . _focusTrap = this . _focusTrapFactory . create ( this . _elementRef . nativeElement ) ; } /** Captures the element that was focused before the dialog was opened. */ private _capturePreviouslyFocusedElement ( ) { if ( this . _document ) { this . _elementFocusedBeforeDialogWasOpened = _getFocusedElementPierceShadowDom ( ) ; } } /** Focuses the dialog container. */ private _focusDialogContainer ( ) { if ( this . _elementRef . nativeElement . focus ) { this . _elementRef . nativeElement . focus ( ) ; } } /** Returns whether focus is inside the dialog. */ private _containsFocus ( ) { const element = this . _elementRef . nativeElement ; const activeElement = _getFocusedElementPierceShadowDom ( ) ; return element === activeElement || element . contains ( activeElement ) ; } } docs-private @ Component ( { selector : 'sbb-tooltip-component' , templateUrl : 'tooltip.html' , styleUrls : [ 'tooltip.css' ] , encapsulation : ViewEncapsulation . None , changeDetection : ChangeDetectionStrategy . OnPush , animations : [ sbbTooltipAnimations . tooltipState ] , host : { class : 'sbb-tooltip-component' , '[style.zoom]' : '_visibility === "visible" ? 1 : null' , 'aria-hidden' : 'true' , } , } ) export class TooltipComponent extends _TooltipComponentBase { /** Stream that emits whether the user has a handset-sized display.  */ _isHandset : Observable < BreakpointState > = this . _breakpointObserver . observe ( Breakpoints . MobileDevice ) ; get _templateRef ( ) : TemplateRef < any > | null { return this . message instanceof TemplateRef ? this . message : null ; } constructor ( elementRef , focusTrapFactory , changeDetectorRef , focusMonitor , @ Optional ( ) @ Inject ( DOCUMENT ) document , private _breakpointObserver ) { super ( elementRef , focusTrapFactory , changeDetectorRef , focusMonitor , document ) ; } }