diff --git a/README.md b/README.md index 603cbbd..4869d68 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ A library that allows href to understand Angular's router while retaining its de 2. Support scroll with the `#` attributes and let you configure the [scrolling logic](#scroll-logic) 3. Automatically append `rel="nooepener"` & `target="_blank"` to external link [if wished so](#installation) 4. Support using `href` with the html `button` [attribute](#directive) +5. Enable easy `Scroll when ready` mechanism +6. Let you transform text to well formatted `anchor` ## Demo - https://stackblitz.com/~/github.com/rbalet/ngx-href @@ -32,6 +34,7 @@ import { ngxHrefModule, ngxHrefService } from 'ngx-href' * defaultOffset="0" * navbarOffset="0" * rel=undefined + * retryTimeout=undefined * target="_self" **/ ngxHrefModule.forRoot({}), @@ -43,6 +46,7 @@ import { ngxHrefModule, ngxHrefService } from 'ngx-href' defaultOffset:"30", navbarOffset:"60", rel:"noopener nofollow", + retryTimeout: 300, target:"_blank", }), ], @@ -99,6 +103,14 @@ ngAfterContentInit(): void { } ``` +### retryTimeout +**Default:** `undefined` +**Accepted value:** `number` + +Trigger a second `scrollTo` event after `retryTimeout` milliseconds. + +**Note:** This should be avoided, prefer playing with skeleton and fixed height + ## External link ### Rel attribute @@ -119,17 +131,15 @@ Can also be passed individually directly through html ``` -### target attribute - - ## Usage -Wherever you plan to use the href directive +Wherever you plan to use the href directive or pipe ```typescript -import { ngxHrefModule } from 'ngx-href' +import { NgxHrefDirective, ToAnchorPipe } from 'ngx-href' imports: [ - ngxHrefModule, + NgxHrefDirective, + NgxHrefPipe, ] ``` @@ -166,6 +176,20 @@ Normal use ``` +### Pipe: _ToAnchorPipe_ +The `toAnchor` pipe let you +1. transform an element ot a correct anchor +example: `my Title $%` will be transform to `my-title` + +2. Emit that this anchor have been created, so that we can scroll to that element + +```html + +
+ + +
+``` ### Service ```typescript @@ -187,6 +211,7 @@ Normal use

A title

``` + ## Authors and acknowledgment * maintainer [Raphaël Balet](https://github.com/rbalet) diff --git a/package.json b/package.json index a332090..c472930 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ngx-href", - "version": "17.1.2", + "version": "17.2.0", "license": "MIT", "author": "Raphael Balet", "maintainers": [ diff --git a/projects/ngx-href-tester/src/app/app.module.ts b/projects/ngx-href-tester/src/app/app.module.ts index fc41832..8a58229 100644 --- a/projects/ngx-href-tester/src/app/app.module.ts +++ b/projects/ngx-href-tester/src/app/app.module.ts @@ -43,6 +43,7 @@ export const routes: Routes = [ defaultOffset: 100, defaultRelAttr: '', defaultTargetAttr: '_blank', + retryTimeout: 300, }), ], bootstrap: [AppComponent], diff --git a/projects/ngx-href/README.md b/projects/ngx-href/README.md index 603cbbd..4869d68 100644 --- a/projects/ngx-href/README.md +++ b/projects/ngx-href/README.md @@ -11,6 +11,8 @@ A library that allows href to understand Angular's router while retaining its de 2. Support scroll with the `#` attributes and let you configure the [scrolling logic](#scroll-logic) 3. Automatically append `rel="nooepener"` & `target="_blank"` to external link [if wished so](#installation) 4. Support using `href` with the html `button` [attribute](#directive) +5. Enable easy `Scroll when ready` mechanism +6. Let you transform text to well formatted `anchor` ## Demo - https://stackblitz.com/~/github.com/rbalet/ngx-href @@ -32,6 +34,7 @@ import { ngxHrefModule, ngxHrefService } from 'ngx-href' * defaultOffset="0" * navbarOffset="0" * rel=undefined + * retryTimeout=undefined * target="_self" **/ ngxHrefModule.forRoot({}), @@ -43,6 +46,7 @@ import { ngxHrefModule, ngxHrefService } from 'ngx-href' defaultOffset:"30", navbarOffset:"60", rel:"noopener nofollow", + retryTimeout: 300, target:"_blank", }), ], @@ -99,6 +103,14 @@ ngAfterContentInit(): void { } ``` +### retryTimeout +**Default:** `undefined` +**Accepted value:** `number` + +Trigger a second `scrollTo` event after `retryTimeout` milliseconds. + +**Note:** This should be avoided, prefer playing with skeleton and fixed height + ## External link ### Rel attribute @@ -119,17 +131,15 @@ Can also be passed individually directly through html ``` -### target attribute - - ## Usage -Wherever you plan to use the href directive +Wherever you plan to use the href directive or pipe ```typescript -import { ngxHrefModule } from 'ngx-href' +import { NgxHrefDirective, ToAnchorPipe } from 'ngx-href' imports: [ - ngxHrefModule, + NgxHrefDirective, + NgxHrefPipe, ] ``` @@ -166,6 +176,20 @@ Normal use ``` +### Pipe: _ToAnchorPipe_ +The `toAnchor` pipe let you +1. transform an element ot a correct anchor +example: `my Title $%` will be transform to `my-title` + +2. Emit that this anchor have been created, so that we can scroll to that element + +```html + +
+ + +
+``` ### Service ```typescript @@ -187,6 +211,7 @@ Normal use

A title

``` + ## Authors and acknowledgment * maintainer [Raphaël Balet](https://github.com/rbalet) diff --git a/projects/ngx-href/package.json b/projects/ngx-href/package.json index 3070670..55034ec 100644 --- a/projects/ngx-href/package.json +++ b/projects/ngx-href/package.json @@ -1,6 +1,6 @@ { "name": "ngx-href", - "version": "17.1.2", + "version": "17.2.0", "license": "MIT", "author": { "name": "Raphaël Balet", diff --git a/projects/ngx-href/src/lib/href.interface.ts b/projects/ngx-href/src/lib/href.interface.ts index 575560d..b613c3e 100644 --- a/projects/ngx-href/src/lib/href.interface.ts +++ b/projects/ngx-href/src/lib/href.interface.ts @@ -5,4 +5,5 @@ export interface NgxHrefServiceConfig { defaultOffset?: number defaultRelAttr?: string defaultTargetAttr?: string + retryTimeout?: number } diff --git a/projects/ngx-href/src/lib/href.service.ts b/projects/ngx-href/src/lib/href.service.ts index 30e7d9c..a3e9a1b 100644 --- a/projects/ngx-href/src/lib/href.service.ts +++ b/projects/ngx-href/src/lib/href.service.ts @@ -8,6 +8,7 @@ import { NgxHrefServiceConfig } from './href.interface' }) export class NgxHrefService { anchor$: BehaviorSubject = new BehaviorSubject(null) + loadedAnchor$: BehaviorSubject = new BehaviorSubject(null) // Trigger the scrollTo mechanism from outside avoidSpam?: boolean behavior!: ScrollBehavior @@ -15,8 +16,9 @@ export class NgxHrefService { navbarOffset!: number defaultRelAttr?: string defaultTargetAttr!: string + retryTimeout?: number - private _actualAnchor = '' + private _actualAnchor?: string constructor(@Inject(NgxHrefServiceProvider) _config: NgxHrefServiceConfig) { this.avoidSpam = _config.avoidSpam @@ -25,24 +27,29 @@ export class NgxHrefService { this.navbarOffset = typeof _config.navbarOffset === 'number' ? _config.navbarOffset : 0 this.defaultRelAttr = _config.defaultRelAttr this.defaultTargetAttr = _config.defaultTargetAttr || '_self' + this.retryTimeout = _config.retryTimeout + + this.loadedAnchor$.subscribe((anchor) => { + if (anchor === this._actualAnchor) { + this.scrollTo(anchor, 9) // 9: triggered only once + } + }) } scrollTo(anchor?: string, counter = 0) { - if (!anchor) return - - const newAnchor = anchor.replace(/ /g, '') - - if (counter === 0) this.anchor$.next(newAnchor) - if ( + !anchor || counter >= 10 || // Counter over, this element doesn't exist or the page is too slow - (newAnchor === this._actualAnchor && counter === 0) // Multiple click on the same url + (anchor === this._actualAnchor && counter === 0) // Multiple click on the same url ) return - this._actualAnchor = newAnchor + if (counter === 0) { + this._actualAnchor = anchor + this.anchor$.next(anchor) + } - const anchorRef = document.getElementById(newAnchor) + const anchorRef = document.getElementById(anchor) if (anchorRef) { const offsetPosition = @@ -55,11 +62,21 @@ export class NgxHrefService { behavior: this.behavior, }) - this._actualAnchor = '' + if (this.retryTimeout) + setTimeout(() => { + window.scrollTo({ + top: offsetPosition, + behavior: this.behavior, + }) + }, this.retryTimeout) + + this._actualAnchor = undefined } else { setTimeout(() => { + if (anchor !== this._actualAnchor) return + counter++ - this.scrollTo(newAnchor, counter) + this.scrollTo(anchor, counter) }, 200) } } diff --git a/projects/ngx-href/src/lib/to-anchor.pipe.ts b/projects/ngx-href/src/lib/to-anchor.pipe.ts new file mode 100644 index 0000000..2aed563 --- /dev/null +++ b/projects/ngx-href/src/lib/to-anchor.pipe.ts @@ -0,0 +1,71 @@ +import { Pipe, PipeTransform } from '@angular/core' +import { NgxHrefService } from './href.service' + +@Pipe({ + standalone: true, + name: 'toAnchor', +}) +export class ToAnchorPipe implements PipeTransform { + private _removedChars = [ + ';', + ':', + '!', + '"', + '(', + ')', + '[', + ']', + '{', + '}', + '*', + '/', + '%', + '^', + '+', + '<', + '=', + '>', + '~', + ] + + private _replacedChars: { [key: string]: string } = { + ' ': '-', + ',': '-', + "'": '-', + à: 'a', + â: 'a', + ã: 'a', + ä: 'ae', + ç: 'c', + é: 'e', + è: 'e', + ê: 'e', + ë: 'e', + î: 'i', + ï: 'i', + ñ: 'n', + ô: 'o', + ö: 'oe', + ß: 'ss', + û: 'u', + ü: 'ue', + } + + constructor(private _ngxHrefService: NgxHrefService) {} + + transform(id: string, emit = true): string { + if (!id) return '' + + let anchor = id.toLocaleLowerCase() + Object.entries(this._replacedChars).forEach(([specialChar, replacement]) => { + anchor = anchor.split(specialChar).join(replacement) + }) + this._removedChars.forEach((char) => { + anchor = anchor.split(char).join('') + }) + + if (emit) this._ngxHrefService.loadedAnchor$.next(anchor) + + return anchor + } +} diff --git a/projects/ngx-href/src/public-api.ts b/projects/ngx-href/src/public-api.ts index 1e8bd9f..6ed8374 100644 --- a/projects/ngx-href/src/public-api.ts +++ b/projects/ngx-href/src/public-api.ts @@ -5,3 +5,4 @@ export * from './lib/href.directive' export * from './lib/href.module' export * from './lib/href.service' +export * from './lib/to-anchor.pipe'