UNPKG

@ngneat/helipopper

Version:

A Powerful Tooltip and Popover for Angular Applications

769 lines (759 loc) 35.2 kB
import * as i0 from '@angular/core'; import { ElementRef, inject, NgZone, Injectable, booleanAttribute, InjectionToken, input, output, model, EventEmitter, computed, DestroyRef, PLATFORM_ID, Injector, effect, untracked, Directive, Inject, Output } from '@angular/core'; import { isPlatformServer } from '@angular/common'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { Observable, defer, from, map as map$1, of, shareReplay, Subject, fromEvent, merge } from 'rxjs'; import { auditTime, map, switchMap, filter, takeUntil } from 'rxjs/operators'; import * as i1 from '@ngneat/overview'; import { isString, isComponent, isTemplateRef } from '@ngneat/overview'; import { TIPPY_LOADER, TIPPY_CONFIG } from '@ngneat/helipopper/config'; // Let's retrieve the native `IntersectionObserver` implementation hidden by // `__zone_symbol__IntersectionObserver`. This would be the unpatched version of // the observer present in zone.js environments. // Otherwise, if the user is using zoneless change detection (and zone.js is not included), // we fall back to the native implementation. Accessing the native implementation // allows us to remove `runOutsideAngular` calls and reduce indentation, // making the code a bit more readable. const IntersectionObserver = globalThis['__zone_symbol__IntersectionObserver'] || globalThis.IntersectionObserver; let supportsIntersectionObserver = false; let supportsResizeObserver = false; if (typeof window !== 'undefined') { supportsIntersectionObserver = 'IntersectionObserver' in window; supportsResizeObserver = 'ResizeObserver' in window; } function inView(host, options = { root: null, threshold: 0.3, }) { const element = coerceElement(host); return new Observable((subscriber) => { if (!supportsIntersectionObserver) { subscriber.next(); subscriber.complete(); return; } const observer = new IntersectionObserver((entries) => { // Several changes may occur in the same tick, we want to check the latest entry state. const entry = entries[entries.length - 1]; if (entry.isIntersecting) { subscriber.next(); subscriber.complete(); } }, options); observer.observe(element); return () => observer.disconnect(); }); } function isElementOverflow(host) { // Don't access the `offsetWidth` multiple times since it triggers layout updates. const hostOffsetWidth = host.offsetWidth; return (hostOffsetWidth > host.parentElement.offsetWidth || hostOffsetWidth < host.scrollWidth); } function overflowChanges(host) { const element = coerceElement(host); return dimensionsChanges(element).pipe(auditTime(150), map(() => isElementOverflow(element))); } function dimensionsChanges(target) { return new Observable((subscriber) => { if (!supportsResizeObserver) { subscriber.next(); subscriber.complete(); return; } const observer = new ResizeObserver(() => subscriber.next(true)); observer.observe(target); return () => observer.disconnect(); }); } function onlyTippyProps(allProps) { const tippyProps = {}; const ownProps = [ 'useTextContent', 'variations', 'useHostWidth', 'defaultVariation', 'beforeRender', 'isLazy', 'variation', 'isEnabled', 'className', 'onlyTextOverflow', 'data', 'content', 'context', 'hideOnEscape', 'customHost', 'injector', 'preserveView', 'vcr', 'popperWidth', 'zIndexGetter', 'staticWidthHost', ]; const overriddenMethods = ['onShow', 'onHidden', 'onCreate']; Object.keys(allProps).forEach((prop) => { if (!ownProps.includes(prop) && !overriddenMethods.includes(prop)) { tippyProps[prop] = allProps[prop]; } }); return tippyProps; } function normalizeClassName(className) { const classes = typeof className === 'string' ? className.split(' ') : className; return classes.map((klass) => klass?.trim()).filter(Boolean); } function coerceCssPixelValue(value) { if (value == null) { return ''; } return typeof value === 'string' ? value : `${value}px`; } function coerceElement(element) { return element instanceof ElementRef ? element.nativeElement : element; } let observer; const elementHiddenHandlers = new WeakMap(); function observeVisibility(host, hiddenHandler) { observer ??= new IntersectionObserver((entries) => { entries.forEach((entry) => { if (!entry.isIntersecting) { elementHiddenHandlers.get(entry.target)(); } }); }); elementHiddenHandlers.set(host, hiddenHandler); observer.observe(host); return () => { elementHiddenHandlers.delete(host); observer.unobserve(host); }; } // We need to use `isPromise` instead of checking whether // `value instanceof Promise`. In zone.js patched environments, `global.Promise` // is the `ZoneAwarePromise`. // `import(...)` returns a native promise (not a `ZoneAwarePromise`), causing // `instanceof` check to be falsy. function isPromise(value) { return typeof value?.then === 'function'; } class TippyFactory { constructor() { this._ngZone = inject(NgZone); this._loader = inject(TIPPY_LOADER); this._tippyImpl$ = null; } /** * This returns an observable because the user should provide a `loader` * function, which may return a promise if the tippy.js library is to be * loaded asynchronously. */ create(target, props) { // We use `shareReplay` to ensure that subsequent emissions are // synchronous and to avoid triggering the `defer` callback repeatedly // when new subscribers arrive. this._tippyImpl$ ||= defer(() => { const maybeTippy = this._ngZone.runOutsideAngular(() => this._loader()); return isPromise(maybeTippy) ? from(maybeTippy).pipe(map$1((tippy) => tippy.default)) : of(maybeTippy); }).pipe(shareReplay()); return this._tippyImpl$.pipe(map$1((tippy) => { return this._ngZone.runOutsideAngular(() => tippy(target, props)); })); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: TippyFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: TippyFactory, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: TippyFactory, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); /** * Transforms a value (typically a string) to a boolean. * Intended to be used as a transform function of an input. * * @see https://material.angular.io/cdk/coercion/overview */ const coerceBooleanAttribute = booleanAttribute; const TIPPY_REF = /* @__PURE__ */ new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'TIPPY_REF' : ''); function injectTippyRef() { const instance = inject(TIPPY_REF, { optional: true }); if (!instance) { if (typeof ngDevMode !== 'undefined' && ngDevMode) { throw new Error('tp is not provided in the current context or on one of its ancestors'); } else { throw new Error(`[tp]: ${1 /* TippyErrorCode.TippyNotProvided */}`); } } return instance; } // These are the default values used by `tippy.js`. // We are providing them as default input values. // The `tippy.js` repository has been archived and is unlikely to // change in the future, so it is safe to use these values as defaults. const defaultAppendTo = () => document.body; const defaultDelay = 0; const defaultDuration = [300, 250]; const defaultInteractiveBorder = 2; const defaultMaxWidth = 350; const defaultOffset = [0, 10]; const defaultPlacement = 'top'; const defaultTrigger = 'mouseenter focus'; const defaultTriggerTarget = null; const defaultZIndex = 9999; const defaultAnimation = 'fade'; class TippyDirective { // It should be a getter because computations are cached until // any of the producers change. get hostWidth() { return this.host().getBoundingClientRect().width; } constructor(globalConfig, injector, viewService, vcr, ngZone, hostRef) { this.globalConfig = globalConfig; this.injector = injector; this.viewService = viewService; this.vcr = vcr; this.ngZone = ngZone; this.hostRef = hostRef; this.appendTo = input(defaultAppendTo, { alias: 'tpAppendTo', }); this.content = input('', { alias: 'tp' }); this.delay = input(defaultDelay, { alias: 'tpDelay', }); this.duration = input(defaultDuration, { alias: 'tpDuration', }); this.hideOnClick = input(true, { alias: 'tpHideOnClick', }); this.interactive = input(false, { alias: 'tpInteractive', }); this.interactiveBorder = input(defaultInteractiveBorder, { alias: 'tpInteractiveBorder', }); this.maxWidth = input(defaultMaxWidth, { alias: 'tpMaxWidth', }); // Note that some of the input signal types are declared explicitly because the compiler // also uses types from `@popperjs/core` and requires a type annotation. this.offset = input(defaultOffset, { alias: 'tpOffset', }); this.placement = input(defaultPlacement, { alias: 'tpPlacement', }); this.popperOptions = input({}, { alias: 'tpPopperOptions', }); this.showOnCreate = input(false, { alias: 'tpShowOnCreate', }); this.trigger = input(defaultTrigger, { alias: 'tpTrigger', }); this.triggerTarget = input(defaultTriggerTarget, { alias: 'tpTriggerTarget', }); this.zIndex = input(defaultZIndex, { alias: 'tpZIndex', }); this.animation = input(defaultAnimation, { alias: 'tpAnimation', }); this.useTextContent = input(false, { transform: coerceBooleanAttribute, alias: 'tpUseTextContent', }); this.isLazy = input(false, { transform: coerceBooleanAttribute, alias: 'tpIsLazy', }); this.variation = input(undefined, { alias: 'tpVariation' }); this.isEnabled = input(true, { alias: 'tpIsEnabled' }); this.className = input('', { alias: 'tpClassName' }); this.onlyTextOverflow = input(false, { transform: coerceBooleanAttribute, alias: 'tpOnlyTextOverflow', }); this.staticWidthHost = input(false, { transform: coerceBooleanAttribute, alias: 'tpStaticWidthHost', }); this.data = input(undefined, { alias: 'tpData' }); this.useHostWidth = input(false, { transform: coerceBooleanAttribute, alias: 'tpUseHostWidth', }); this.hideOnEscape = input(false, { transform: coerceBooleanAttribute, alias: 'tpHideOnEscape', }); this.popperWidth = input(undefined, { alias: 'tpPopperWidth', }); this.customHost = input(undefined, { alias: 'tpHost' }); this.onShow = output({ alias: 'tpOnShow' }); this.onHide = output({ alias: 'tpOnHide' }); this.isVisible = model(false, { alias: 'tpIsVisible' }); this.visible = new EventEmitter(); this.viewRef = null; this.variationDefined = false; this.viewOptions$ = null; /** * We had use `visible` event emitter previously as a `takeUntil` subscriber in multiple places * within the directive. * This is for internal use only; thus we don't have to deal with the `visible` event emitter * and trigger change detections only when the `visible` event is being listened outside * in the template (`<button [tippy]="..." (visible)="..."></button>`). */ this.visibleInternal = new Subject(); this.visibilityObserverCleanup = null; this.contentChanged = new Subject(); this.host = computed(() => this.customHost() || this.hostRef.nativeElement); this.destroyRef = inject(DestroyRef); this.isServer = isPlatformServer(inject(PLATFORM_ID)); this.tippyFactory = inject(TippyFactory); this.destroyed = false; if (this.isServer) return; this.setupListeners(); this.destroyRef.onDestroy(() => { this.destroyed = true; this.instance?.destroy(); this.destroyView(); this.visibilityObserverCleanup?.(); }); } ngOnChanges(changes) { if (this.isServer) return; const variation = this.variation() || this.globalConfig.defaultVariation || ''; const props = Object.keys(changes) // `isVisible` is not required as a prop since we update it manually // in an effect-like manner. .filter((key) => key !== 'isVisible') .reduce((accumulator, key) => ({ ...accumulator, [key]: changes[key].currentValue }), { ...this.globalConfig.variations?.[variation] }); this.updateProps(props); } ngOnInit() { if (this.useHostWidth()) { this.props.maxWidth = this.hostWidth; } } ngAfterViewInit() { if (this.isServer) return; if (this.isLazy()) { const hostInView$ = inView(this.host()); if (this.onlyTextOverflow()) { hostInView$ .pipe(switchMap(() => this.isOverflowing$()), takeUntilDestroyed(this.destroyRef)) .subscribe((isElementOverflow) => { this.checkOverflow(isElementOverflow); }); } else { hostInView$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => { this.createInstance(); }); } } else if (this.onlyTextOverflow()) { this.isOverflowing$() .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((isElementOverflow) => { this.checkOverflow(isElementOverflow); }); } else { this.createInstance(); } } destroyView() { this.viewOptions$ = null; this.viewRef?.destroy(); this.viewRef = null; } /** * This method is useful when you append to an element that you might remove from the DOM. * In such cases we want to hide the tooltip and let it go through the destroy lifecycle. * For example, if you have a grid row with an element that you toggle using the display CSS property on hover. */ observeHostVisibility() { if (this.isServer) return; // We don't want to observe the host visibility if we are appending to the body. if (this.props.appendTo && this.props.appendTo !== document.body) { this.visibilityObserverCleanup?.(); return this.visibleInternal .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((isVisible) => { if (isVisible) { this.visibilityObserverCleanup = observeVisibility(this.instance.reference, () => { this.hide(); // Because we have animation on the popper it doesn't close immediately doesn't trigger the `tpVisible` event. // Tippy is relying on the transitionend event to trigger the `onHidden` callback. // https://github.com/atomiks/tippyjs/blob/master/src/dom-utils.ts#L117 // This event never fires because the popper is removed from the DOM before the transition ends. if (this.props.animation) { this.onHidden(); } }); } else { this.visibilityObserverCleanup?.(); } }); } } show() { this.instance?.show(); } hide() { this.instance?.hide(); } enable() { this.instance?.enable(); } disable() { this.instance?.disable(); } updateProps(props) { this.setProps({ ...this.props, ...props }); } setProps(props) { this.props = props; this.instance?.setProps(onlyTippyProps(props)); } setStatus(isEnabled) { isEnabled ? this.instance?.enable() : this.instance?.disable(); } createInstance() { if (!this.content() && !this.useTextContent()) { return; } this.tippyFactory .create(this.host(), { allowHTML: true, appendTo: document.body, ...(this.globalConfig.zIndexGetter ? { zIndex: this.globalConfig.zIndexGetter() } : {}), ...onlyTippyProps(this.globalConfig), ...onlyTippyProps(this.props), onMount: (instance) => { const isVisible = true; this.isVisible.set(isVisible); this.visibleInternal.next(isVisible); if (this.visible.observed) { this.ngZone.run(() => this.visible.next(isVisible)); } this.useHostWidth() && this.listenToHostResize(); this.globalConfig.onMount?.(instance); }, onCreate: (instance) => { instance.popper.classList.add(`tippy-variation-${this.variation() || this.globalConfig.defaultVariation}`); if (this.className()) { for (const klass of normalizeClassName(this.className())) { instance.popper.classList.add(klass); } } this.globalConfig.onCreate?.(instance); if (this.isVisible() === true) { instance.show(); } }, onShow: (instance) => { instance.reference.setAttribute('data-tippy-open', ''); // We're re-entering because we might create an Angular component, // which should be done within the zone. const content = this.ngZone.run(() => this.resolveContent(instance)); if (isString(content)) { instance.setProps({ allowHTML: false }); if (!content?.trim()) { this.disable(); } else { this.enable(); } } instance.setContent(content); this.hideOnEscape() && this.handleEscapeButton(); if (this.useHostWidth()) { this.setInstanceWidth(instance, this.hostWidth); } else if (this.popperWidth()) { this.setInstanceWidth(instance, this.popperWidth()); } this.globalConfig.onShow?.(instance); this.onShow.emit(); }, onHide(instance) { instance.reference.removeAttribute('data-tippy-open'); }, onHidden: (instance) => { this.onHidden(instance); }, }) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((instance) => { this.instance = instance; this.setStatus(this.isEnabled()); this.setProps(this.props); this.variation() === 'contextMenu' && this.handleContextMenu(); }); } resolveContent(instance) { const content = this.content(); if (!this.viewOptions$ && !isString(content)) { const injector = Injector.create({ providers: [ { provide: TIPPY_REF, useValue: this.instance, }, ], parent: this.injector, }); const data = this.data(); if (isComponent(content)) { this.instance.data = data; this.viewOptions$ = { injector, }; } else if (isTemplateRef(content)) { this.viewOptions$ = { injector, context: { data, $implicit: this.hide.bind(this), }, }; } } this.viewRef = this.viewService.createView(content, { vcr: this.vcr, ...this.viewOptions$, }); // We need to call `detectChanges` for OnPush components to update their content. if (isComponent(content)) { // `ɵcmp` is a component defition set for any component. // Checking the `onPush` property of the component definition is a // smarter way to determine whether we need to call `detectChanges()`, // as users may be unaware of setting the binding. const isOnPush = content.ɵcmp?.onPush; if (isOnPush) { this.viewRef.detectChanges(); } } let newContent = this.viewRef.getElement(); if (this.useTextContent()) { newContent = instance.reference.textContent; } if (isString(newContent) && this.globalConfig.beforeRender) { newContent = this.globalConfig.beforeRender(newContent); } return newContent; } handleContextMenu() { fromEvent(this.host(), 'contextmenu') .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((event) => { event.preventDefault(); this.instance.setProps({ getReferenceClientRect: () => ({ width: 0, height: 0, top: event.clientY, bottom: event.clientY, left: event.clientX, right: event.clientX, }), }); this.instance.show(); }); } handleEscapeButton() { fromEvent(document.body, 'keydown') .pipe(filter(({ code }) => code === 'Escape'), takeUntil(this.visibleInternal.pipe(filter((v) => !v))), takeUntilDestroyed(this.destroyRef)) .subscribe(() => this.hide()); } checkOverflow(isElementOverflow) { if (isElementOverflow) { if (!this.instance) { this.createInstance(); } else { this.instance.enable(); } } else { this.instance?.disable(); } } listenToHostResize() { dimensionsChanges(this.host()) .pipe(takeUntil(this.visibleInternal), takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.setInstanceWidth(this.instance, this.hostWidth); }); } setInstanceWidth(instance, width) { const inPixels = coerceCssPixelValue(width); instance.popper.style.width = inPixels; instance.popper.style.maxWidth = inPixels; instance.popper.firstElementChild.style.maxWidth = inPixels; } onHidden(instance = this.instance) { this.destroyView(); const isVisible = false; // `model()` uses `OutputEmitterRef` internally to emit events when the // signal changes. If the directive is destroyed, it will throw an error: // "Unexpected emit for destroyed `OutputRef`". if (!this.destroyed) { this.isVisible.set(isVisible); this.onHide.emit(); } this.visibleInternal.next(isVisible); if (this.visible.observed) { this.ngZone.run(() => this.visible.next(isVisible)); } this.globalConfig.onHidden?.(instance); } isOverflowing$() { const host = this.host(); const notifiers$ = [overflowChanges(host)]; // We need to handle cases where the host has a static width but the content might change if (this.staticWidthHost()) { notifiers$.push(this.contentChanged.pipe( // We need to wait for the content to be rendered before we can check if it's overflowing. switchMap(() => { return new Observable((subscriber) => { const id = window.requestAnimationFrame(() => { subscriber.next(); subscriber.complete(); }); return () => cancelAnimationFrame(id); }); }), map(() => isElementOverflow(host)))); } return merge(...notifiers$); } setupListeners() { effect(() => { // Capture signal read to track its changes. this.content(); untracked(() => this.contentChanged.next()); }); effect(() => this.setStatus(this.isEnabled())); effect(() => { const isVisible = this.isVisible(); isVisible ? this.show() : this.hide(); }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: TippyDirective, deps: [{ token: TIPPY_CONFIG }, { token: i0.Injector }, { token: i1.ViewService }, { token: i0.ViewContainerRef }, { token: i0.NgZone }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.0", type: TippyDirective, isStandalone: true, selector: "[tp]", inputs: { appendTo: { classPropertyName: "appendTo", publicName: "tpAppendTo", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "tp", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "tpDelay", isSignal: true, isRequired: false, transformFunction: null }, duration: { classPropertyName: "duration", publicName: "tpDuration", isSignal: true, isRequired: false, transformFunction: null }, hideOnClick: { classPropertyName: "hideOnClick", publicName: "tpHideOnClick", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "tpInteractive", isSignal: true, isRequired: false, transformFunction: null }, interactiveBorder: { classPropertyName: "interactiveBorder", publicName: "tpInteractiveBorder", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "tpMaxWidth", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "tpOffset", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "tpPlacement", isSignal: true, isRequired: false, transformFunction: null }, popperOptions: { classPropertyName: "popperOptions", publicName: "tpPopperOptions", isSignal: true, isRequired: false, transformFunction: null }, showOnCreate: { classPropertyName: "showOnCreate", publicName: "tpShowOnCreate", isSignal: true, isRequired: false, transformFunction: null }, trigger: { classPropertyName: "trigger", publicName: "tpTrigger", isSignal: true, isRequired: false, transformFunction: null }, triggerTarget: { classPropertyName: "triggerTarget", publicName: "tpTriggerTarget", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "tpZIndex", isSignal: true, isRequired: false, transformFunction: null }, animation: { classPropertyName: "animation", publicName: "tpAnimation", isSignal: true, isRequired: false, transformFunction: null }, useTextContent: { classPropertyName: "useTextContent", publicName: "tpUseTextContent", isSignal: true, isRequired: false, transformFunction: null }, isLazy: { classPropertyName: "isLazy", publicName: "tpIsLazy", isSignal: true, isRequired: false, transformFunction: null }, variation: { classPropertyName: "variation", publicName: "tpVariation", isSignal: true, isRequired: false, transformFunction: null }, isEnabled: { classPropertyName: "isEnabled", publicName: "tpIsEnabled", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "tpClassName", isSignal: true, isRequired: false, transformFunction: null }, onlyTextOverflow: { classPropertyName: "onlyTextOverflow", publicName: "tpOnlyTextOverflow", isSignal: true, isRequired: false, transformFunction: null }, staticWidthHost: { classPropertyName: "staticWidthHost", publicName: "tpStaticWidthHost", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "tpData", isSignal: true, isRequired: false, transformFunction: null }, useHostWidth: { classPropertyName: "useHostWidth", publicName: "tpUseHostWidth", isSignal: true, isRequired: false, transformFunction: null }, hideOnEscape: { classPropertyName: "hideOnEscape", publicName: "tpHideOnEscape", isSignal: true, isRequired: false, transformFunction: null }, popperWidth: { classPropertyName: "popperWidth", publicName: "tpPopperWidth", isSignal: true, isRequired: false, transformFunction: null }, customHost: { classPropertyName: "customHost", publicName: "tpHost", isSignal: true, isRequired: false, transformFunction: null }, isVisible: { classPropertyName: "isVisible", publicName: "tpIsVisible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onShow: "tpOnShow", onHide: "tpOnHide", isVisible: "tpIsVisibleChange", visible: "tpVisible" }, exportAs: ["tippy"], usesOnChanges: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: TippyDirective, decorators: [{ type: Directive, args: [{ // eslint-disable-next-line @angular-eslint/directive-selector selector: '[tp]', exportAs: 'tippy', standalone: true, }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [TIPPY_CONFIG] }] }, { type: i0.Injector }, { type: i1.ViewService }, { type: i0.ViewContainerRef }, { type: i0.NgZone }, { type: i0.ElementRef }], propDecorators: { visible: [{ type: Output, args: ['tpVisible'] }] } }); class TippyService { constructor(globalConfig, view, injector) { this.globalConfig = globalConfig; this.view = view; this.injector = injector; this._tippyFactory = inject(TippyFactory); } create(host, content, options = {}) { const variation = options.variation || this.globalConfig.defaultVariation || ''; const config = { onShow: (instance) => { host.setAttribute('data-tippy-open', ''); if (!instance.$viewOptions) { instance.$viewOptions = { injector: Injector.create({ providers: [ { provide: TIPPY_REF, useValue: instance, }, ], parent: options.injector || this.injector, }), }; if (isTemplateRef(content)) { instance.$viewOptions.context = { $implicit: instance.hide.bind(instance), ...options.context, }; } else if (isComponent(content)) { instance.context = options.context; instance.data = options.data; } } instance.view ||= this.view.createView(content, { ...options, ...instance.$viewOptions, }); instance.setContent(instance.view.getElement()); options?.onShow?.(instance); }, onHidden: (instance) => { host.removeAttribute('data-tippy-open'); if (!options.preserveView) { instance.view?.destroy(); instance.view = null; } options?.onHidden?.(instance); }, ...onlyTippyProps(this.globalConfig), ...this.globalConfig.variations?.[variation], ...onlyTippyProps(options), onCreate: (instance) => { instance.popper.classList.add(`tippy-variation-${variation}`); if (options.className) { for (const klass of normalizeClassName(options.className)) { instance.popper.classList.add(klass); } } this.globalConfig.onCreate?.(instance); options.onCreate?.(instance); }, }; return this._tippyFactory.create(host, config); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: TippyService, deps: [{ token: TIPPY_CONFIG }, { token: i1.ViewService }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: TippyService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: TippyService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [TIPPY_CONFIG] }] }, { type: i1.ViewService }, { type: i0.Injector }] }); /** * Generated bundle index. Do not edit. */ export { TIPPY_REF, TippyDirective, TippyService, inView, injectTippyRef, overflowChanges }; //# sourceMappingURL=ngneat-helipopper.mjs.map