Skip to content

Commit

Permalink
Merge pull request #166 from arturovt/feat/config-lib
Browse files Browse the repository at this point in the history
feat: add `@ngneat/helipopper/config`
  • Loading branch information
NetanelBasal authored Nov 20, 2024
2 parents cf879bc + fe59df7 commit 8d713e9
Show file tree
Hide file tree
Showing 16 changed files with 143 additions and 57 deletions.
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

0 comments on commit 8d713e9

Please sign in to comment.