Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add @ngneat/helipopper/config #166

Merged
merged 1 commit into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 43 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ $ pnpm i @ngneat/helipopper
Configure it as shown below:

```ts
import { provideTippyConfig, tooltipVariation, popperVariation } from '@ngneat/helipopper';
import { provideTippyLoader provideTippyConfig, tooltipVariation, popperVariation } from '@ngneat/helipopper/config';

bootstrapApplication(AppComponent, {
providers: [
provideTippyLoader(() => import('tippy.js')),
provideTippyConfig({
loader: () => import('tippy.js'),
defaultVariation: 'tooltip',
variations: {
tooltip: tooltipVariation,
Expand All @@ -65,14 +65,12 @@ bootstrapApplication(AppComponent, {
});
```

Please note that the `loader` property is required, as it specifies how Tippy is loaded - either synchronously or asynchronously. When dynamic import is used, the library will load only when the first Tippy directive is rendered. If we want it to load synchronously, we use the following:
Please note that the `provideTippyLoader` is required, as it specifies how Tippy is loaded - either synchronously or asynchronously. When dynamic import is used, the library will load only when the first Tippy directive is rendered. If we want it to load synchronously, we use the following:

```ts
import tippy from 'tippy.js';

provideTippyConfig({
loader: () => tippy,
});
provideTippyLoader(() => tippy);
```

Add the styles you want to `styles.scss`:
Expand All @@ -85,7 +83,19 @@ Add the styles you want to `styles.scss`:

You have the freedom to [customize](https://atomiks.github.io/tippyjs/v6/themes/) it if you need to.

Import the standalone `TippyDirective` and use it in your templates:
Import the standalone `TippyDirective` in your components:

```ts
import { TippyDirective } from '@ngneat/helipopper';

@Component({
standalone: true,
imports: [TippyDirective],
})
class ExampleComponent {}
```

And use it in your templates:

```html
<button tp="Helpful Message">I have a tooltip</button>
Expand Down Expand Up @@ -119,11 +129,11 @@ export const tooltipVariation = {
### Use `Component` as content

```ts
import { TIPPY_REF, TippyInstance } from '@ngneat/helipopper';
import { injectTippyRef, TippyInstance } from '@ngneat/helipopper/config';

@Component()
class MyComponent {
tippy = inject(TIPPY_REF);
tippy = injectTippyRef();
}
```

Expand All @@ -137,7 +147,9 @@ You can pass the `onlyTextOverflow` input to show the tooltip only when the host

```html
<div style="max-width: 100px;" class="overflow-hidden flex">
<p class="ellipsis" [tp]="text" tpPlacement="right" [tpOnlyTextOverflow]="true">{{ text }}</p>
<p class="ellipsis" [tp]="text" tpPlacement="right" [tpOnlyTextOverflow]="true">
{{ text }}
</p>
</div>
```

Expand Down Expand Up @@ -185,7 +197,12 @@ Note that it's using [`IntersectionObserver`](https://caniuse.com/intersectionob
First, define the `contextMenu` variation:

```ts
import { popperVariation, tooltipVariation, provideTippyConfig, withContextMenuVariation } from '@ngneat/helipopper';
import {
popperVariation,
tooltipVariation,
provideTippyConfig,
withContextMenuVariation,
} from '@ngneat/helipopper/config';

bootstrapApplication(AppComponent, {
providers: [
Expand All @@ -212,7 +229,14 @@ Now you can use it in your template:
</ng-template>

<ul>
<li *ngFor="let item of list" [tp]="contextMenu" [tpData]="item" tpVariation="contextMenu">{{ item.label }}</li>
<li
*ngFor="let item of list"
[tp]="contextMenu"
[tpData]="item"
tpVariation="contextMenu"
>
{{ item.label }}
</li>
</ul>
```

Expand All @@ -230,7 +254,9 @@ Now you can use it in your template:
Use isVisible to trigger show and hide. Set trigger to manual.

```html
<div tp="Helpful Message" tpTrigger="manual" [tpIsVisible]="visibility">Click Open to see me</div>
<div tp="Helpful Message" tpTrigger="manual" [tpIsVisible]="visibility">
Click Open to see me
</div>

<button (click)="visibility = true">Open</button>
<button (click)="visibility = false">Close</button>
Expand Down Expand Up @@ -296,9 +322,11 @@ class Component {
tippy: TippyInstance;
private tippyService = inject(TippyService);

show() {
async show() {
if (!this.tippy) {
this.tippy = this.tippyService.create(this.inputName, 'this field is required');
this.tippy = await firstValueFrom(
this.tippyService.create(this.inputName, 'this field is required')
);
}

this.tippy.show();
Expand Down
6 changes: 6 additions & 0 deletions projects/ngneat/helipopper/config/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "src/public-api.ts"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { TippyProps } from './tippy.types';

// A variation is a set of predefined tippy properties.
type Variation = Partial<TippyProps>;

export const tooltipVariation: Variation = {
Expand Down
13 changes: 13 additions & 0 deletions projects/ngneat/helipopper/config/src/inject-tippy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { inject } from '@angular/core';

import { TIPPY_REF, type TippyInstance } from './tippy.types';

export function injectTippyRef(): TippyInstance {
const instance = inject(TIPPY_REF, { optional: true });

if (instance) {
return instance;
}

throw new Error('tp is not provided in the current context or on one of its ancestors');
}
16 changes: 16 additions & 0 deletions projects/ngneat/helipopper/config/src/providers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { makeEnvironmentProviders, type Provider } from '@angular/core';

import {
TIPPY_CONFIG,
TIPPY_LOADER,
type TippyLoader,
type TippyConfig,
} from './tippy.types';

export function provideTippyLoader(loader: TippyLoader) {
return makeEnvironmentProviders([{ provide: TIPPY_LOADER, useValue: loader }]);
}

export function provideTippyConfig(config: TippyConfig): Provider {
return { provide: TIPPY_CONFIG, useValue: config };
}
16 changes: 16 additions & 0 deletions projects/ngneat/helipopper/config/src/public-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export { tooltipVariation, popperVariation, withContextMenuVariation } from './defaults';
export {
CreateOptions,
TippyInstance,
TippyProps,
ExtendedTippyProps,
TippyElement,
ExtendedTippyInstance,
TippyConfig,
TippyLoader,
TIPPY_LOADER,
TIPPY_REF,
TIPPY_CONFIG,
} from './tippy.types';
export { provideTippyLoader, provideTippyConfig } from './providers';
export { injectTippyRef } from './inject-tippy';
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type tippy from 'tippy.js';
import type { Instance, Props } from 'tippy.js';
import { ElementRef, InjectionToken, type InputSignal } from '@angular/core';
import { ElementRef, InjectionToken } from '@angular/core';
import type { ResolveViewRef, ViewOptions } from '@ngneat/overview';

export interface CreateOptions extends Partial<TippyProps>, ViewOptions {
Expand All @@ -10,8 +10,6 @@ export interface CreateOptions extends Partial<TippyProps>, ViewOptions {
data: any;
}

export const TIPPY_REF = new InjectionToken<TippyInstance>('TIPPY_REF');

export interface TippyInstance extends Instance {
data?: any;
}
Expand All @@ -33,8 +31,12 @@ export interface ExtendedTippyInstance<T> extends TippyInstance {
context?: ViewOptions['context'];
}

export interface TippyConfig extends Partial<ExtendedTippyProps> {
loader: () => typeof tippy | Promise<{ default: typeof tippy }>;
}
export type TippyConfig = Partial<ExtendedTippyProps>;

export type TippyLoader = () => typeof tippy | Promise<{ default: typeof tippy }>;

export const TIPPY_LOADER = new InjectionToken<TippyLoader>('TIPPY_LOADER');

export const TIPPY_REF = /* @__PURE__ */ new InjectionToken<TippyInstance>('TIPPY_REF');

export const TIPPY_CONFIG = new InjectionToken<TippyConfig>('Tippy config');
16 changes: 0 additions & 16 deletions projects/ngneat/helipopper/src/lib/providers.ts

This file was deleted.

2 changes: 1 addition & 1 deletion projects/ngneat/helipopper/src/lib/tippy.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import {
TippyConfig,
TippyInstance,
TippyProps,
} from './tippy.types';
} from '@ngneat/helipopper/config';
import { TippyFactory } from './tippy.factory';
import { coerceBooleanAttribute } from './coercion';

Expand Down
11 changes: 6 additions & 5 deletions projects/ngneat/helipopper/src/lib/tippy.factory.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type tippy from 'tippy.js';
import { inject, Injectable, NgZone } from '@angular/core';
import { defer, from, map, type Observable, of, shareReplay } from 'rxjs';

import { TIPPY_CONFIG, type TippyProps } from './tippy.types';
import { TIPPY_LOADER, type TippyProps } from '@ngneat/helipopper/config';

// We need to use `isPromise` instead of checking whether
// `value instanceof Promise`. In zone.js patched environments, `global.Promise`
Expand All @@ -17,7 +16,7 @@ function isPromise<T>(value: any): value is Promise<T> {
export class TippyFactory {
private readonly _ngZone = inject(NgZone);

private readonly _config = inject(TIPPY_CONFIG);
private readonly _loader = inject(TIPPY_LOADER);

private _tippyImpl$: Observable<typeof tippy> | null = null;

Expand All @@ -31,8 +30,10 @@ export class TippyFactory {
// synchronous and to avoid triggering the `defer` callback repeatedly
// when new subscribers arrive.
this._tippyImpl$ ||= defer(() => {
const maybeTippy = this._ngZone.runOutsideAngular(() => this._config.loader());
return isPromise(maybeTippy) ? from(maybeTippy).pipe(map((tippy) => tippy.default)) : of(maybeTippy);
const maybeTippy = this._ngZone.runOutsideAngular(() => this._loader());
return isPromise(maybeTippy)
? from(maybeTippy).pipe(map((tippy) => tippy.default))
: of(maybeTippy);
}).pipe(shareReplay());

return this._tippyImpl$.pipe(
Expand Down
2 changes: 1 addition & 1 deletion projects/ngneat/helipopper/src/lib/tippy.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
TIPPY_REF,
TippyConfig,
TippyInstance,
} from './tippy.types';
} from '@ngneat/helipopper/config';
import { normalizeClassName, onlyTippyProps } from './utils';
import { TippyFactory } from './tippy.factory';

Expand Down
4 changes: 2 additions & 2 deletions projects/ngneat/helipopper/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ElementRef } from '@angular/core';
import { Observable } from 'rxjs';
import { auditTime, map } from 'rxjs/operators';
import { TippyElement } from './tippy.types';
import { ElementRef } from '@angular/core';
import type { TippyElement } from '@ngneat/helipopper/config';

import { IntersectionObserver } from './intersection-observer';

Expand Down
16 changes: 13 additions & 3 deletions projects/ngneat/helipopper/src/public-api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
export { TippyDirective } from './lib/tippy.directive';
export { tooltipVariation, popperVariation, withContextMenuVariation } from './lib/defaults';
export { TippyService } from './lib/tippy.service';
export { inView, overflowChanges } from './lib/utils';
export { ExtendedTippyInstance, TippyInstance, TIPPY_REF, TippyConfig, TIPPY_CONFIG } from './lib/tippy.types';
export { provideTippyConfig, injectTippyRef } from './lib/providers';

export {
ExtendedTippyInstance,
TippyInstance,
TIPPY_REF,
TippyConfig,
TIPPY_CONFIG,
tooltipVariation,
popperVariation,
withContextMenuVariation,
provideTippyConfig,
injectTippyRef,
} from '@ngneat/helipopper/config';
17 changes: 12 additions & 5 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ReactiveFormsModule } from '@angular/forms';
import { ExampleComponent } from './example/example.component';
import { TippyDirective } from '@ngneat/helipopper';
import {
popperVariation,
provideTippyConfig,
TippyDirective,
tooltipVariation,
withContextMenuVariation,
} from '@ngneat/helipopper';
provideTippyConfig,
provideTippyLoader,
} from '@ngneat/helipopper/config';
import { PlaygroundComponent } from './playground/playground.component';
import { IsVisibleComponent } from './is-visible/isVisible.component';

Expand All @@ -22,11 +23,17 @@ function getZIndex() {
}

@NgModule({
declarations: [AppComponent, ExampleComponent, PlaygroundComponent, ExampleComponent, IsVisibleComponent],
declarations: [
AppComponent,
ExampleComponent,
PlaygroundComponent,
ExampleComponent,
IsVisibleComponent,
],
imports: [BrowserModule, AppRoutingModule, ReactiveFormsModule, TippyDirective],
providers: [
provideTippyLoader(() => import('tippy.js')),
provideTippyConfig({
loader: () => import('tippy.js'),
defaultVariation: 'tooltip',
zIndexGetter: getZIndex,
variations: {
Expand Down
5 changes: 2 additions & 3 deletions src/app/example/example.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { TIPPY_REF, TippyInstance } from '@ngneat/helipopper';
import { injectTippyRef } from 'projects/ngneat/helipopper/src/lib/providers';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { injectTippyRef } from '@ngneat/helipopper';

@Component({
selector: 'app-example',
Expand Down
3 changes: 3 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"module": "es2020",
"lib": ["es2020", "dom"],
"paths": {
"@ngneat/helipopper/config": [
"projects/ngneat/helipopper/config/src/public-api.ts"
],
"@ngneat/helipopper": ["projects/ngneat/helipopper/src/public-api.ts"]
},
"useDefineForClassFields": false
Expand Down
Loading