mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Enhanced return details and search components with new features and improvements.
- ✨ **Feature**: Added InViewport directive for element visibility detection - ✨ **Feature**: Introduced new button for navigation in return details - 🛠️ **Refactor**: Improved scroll position restoration logic and removed deprecated files - 📚 **Docs**: Updated README with usage instructions for new directives Ref: #5034
This commit is contained in:
@@ -1,7 +1,66 @@
|
||||
# utils-scroll-position
|
||||
# Scroll Position Library
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
## Overview
|
||||
|
||||
## Running unit tests
|
||||
Provides utilities for storing, restoring, and observing scroll position across navigation or component view changes.
|
||||
|
||||
Run `nx test utils-scroll-position` to execute the unit tests.
|
||||
## Features
|
||||
|
||||
- Store current scroll position in session storage.
|
||||
- Restore saved scroll position with an optional delay.
|
||||
- Observe when an element enters or leaves the viewport.
|
||||
|
||||
## Usage
|
||||
|
||||
### Storing Scroll Position
|
||||
|
||||
Call the function to save the current scroll position:
|
||||
|
||||
```typescript
|
||||
import { storeScrollPosition } from '@isa/utils/scroll-position';
|
||||
|
||||
storeScrollPosition();
|
||||
```
|
||||
|
||||
### Restoring Scroll Position
|
||||
|
||||
Inject the restore function and call it with an optional delay:
|
||||
|
||||
```typescript
|
||||
import { injectRestoreScrollPosition } from '@isa/utils/scroll-position';
|
||||
|
||||
const restorePosition = injectRestoreScrollPosition();
|
||||
await restorePosition(200);
|
||||
```
|
||||
|
||||
### Automatic Restoration
|
||||
|
||||
Provide environment initializer in your app module to auto-save scroll positions:
|
||||
|
||||
```typescript
|
||||
import { provideScrollPositionRestoration } from '@isa/utils/scroll-position';
|
||||
|
||||
provideScrollPositionRestoration();
|
||||
```
|
||||
|
||||
### Marking a Route for Auto Restoration
|
||||
|
||||
To enable automatic scroll restoration on a specific route, add the “scrollPositionRestoration” property in its data:
|
||||
|
||||
```typescript
|
||||
{
|
||||
path: 'example',
|
||||
component: ExampleComponent,
|
||||
data: {
|
||||
scrollPositionRestoration: true
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
### Detecting Element Visibility
|
||||
|
||||
Apply the directive to a component template to emit true or false when entering or exiting the viewport:
|
||||
|
||||
```html
|
||||
<div utilScrolledIntoViewport (utilScrolledIntoViewport)="onVisibilityChange($event)">...</div>
|
||||
```
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './lib/scroll-position-restoration';
|
||||
export * from './lib/scrolled-into-viewport.directive';
|
||||
export * from './lib/inject-restore-scroll-position';
|
||||
export * from './lib/provide-scroll-position-restoration';
|
||||
export * from './lib/store-scroll-position';
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ViewportScroller } from '@angular/common';
|
||||
import { inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { SessionStorageProvider } from '@isa/core/storage';
|
||||
|
||||
/**
|
||||
* Returns a function that restores the scroll position from session storage.
|
||||
*
|
||||
* The returned function waits for an optional delay to ensure the DOM is ready before scrolling.
|
||||
*
|
||||
* @returns A function accepting an optional delay (in milliseconds) and restoring scroll position.
|
||||
*/
|
||||
export function injectRestoreScrollPosition(): () => Promise<void> {
|
||||
const router = inject(Router);
|
||||
const viewportScroller = inject(ViewportScroller);
|
||||
const sessionStorage = inject(SessionStorageProvider);
|
||||
|
||||
return async (delay = 0) => {
|
||||
const url = router.url;
|
||||
const position = await sessionStorage.get(url);
|
||||
|
||||
if (position) {
|
||||
// wait for the next tick to ensure the DOM is ready
|
||||
await new Promise((r) => setTimeout(r, delay));
|
||||
sessionStorage.clear(url);
|
||||
viewportScroller.scrollToPosition(position as [number, number]);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { ViewportScroller } from '@angular/common';
|
||||
import { EnvironmentProviders, inject, provideEnvironmentInitializer } from '@angular/core';
|
||||
import { NavigationStart, Router } from '@angular/router';
|
||||
import { SessionStorageProvider } from '@isa/core/storage';
|
||||
import { getDeepestActivatedRoute } from './utils/get-deepest-activated-route';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* Provides an environment initializer that restores scroll position upon navigation and page unload.
|
||||
*
|
||||
* Listens to router navigation events and the window's beforeunload event to store the current scroll position.
|
||||
*
|
||||
* @returns EnvironmentProviders for scroll position restoration.
|
||||
*/
|
||||
export function provideScrollPositionRestoration(): EnvironmentProviders {
|
||||
return provideEnvironmentInitializer(() => {
|
||||
const router = inject(Router);
|
||||
const viewportScroller = inject(ViewportScroller);
|
||||
const sessionStorage = inject(SessionStorageProvider);
|
||||
|
||||
function storeScrollPosition() {
|
||||
const url = router.url;
|
||||
const route = getDeepestActivatedRoute(router.routerState.root);
|
||||
|
||||
if (route.snapshot.data?.['scrollPositionRestoration']) {
|
||||
sessionStorage.set(url, viewportScroller.getScrollPosition());
|
||||
}
|
||||
}
|
||||
|
||||
if (window) {
|
||||
window.addEventListener('beforeunload', () => {
|
||||
storeScrollPosition();
|
||||
});
|
||||
}
|
||||
|
||||
router.events.pipe(takeUntilDestroyed()).subscribe((event) => {
|
||||
if (event instanceof NavigationStart) {
|
||||
storeScrollPosition();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import { ViewportScroller } from '@angular/common';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { EnvironmentProviders, inject, provideEnvironmentInitializer } from '@angular/core';
|
||||
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
|
||||
import { SessionStorageProvider } from '@isa/core/storage';
|
||||
|
||||
// const route: Route = {
|
||||
// component: AnyComponent,
|
||||
// data: {
|
||||
// scrollPositionRestoration: true
|
||||
// }
|
||||
// }
|
||||
|
||||
const getDeepestActivatedRoute = (route: ActivatedRoute): ActivatedRoute => {
|
||||
while (route.firstChild) {
|
||||
route = route.firstChild;
|
||||
}
|
||||
return route;
|
||||
};
|
||||
|
||||
export function provideScrollPositionRestoration(): EnvironmentProviders {
|
||||
return provideEnvironmentInitializer(() => {
|
||||
const router = inject(Router);
|
||||
const viewportScroller = inject(ViewportScroller);
|
||||
const sessionStorage = inject(SessionStorageProvider);
|
||||
|
||||
function storeScrollPosition() {
|
||||
const url = router.url;
|
||||
const route = getDeepestActivatedRoute(router.routerState.root);
|
||||
|
||||
if (route.snapshot.data?.['scrollPositionRestoration']) {
|
||||
sessionStorage.set(url, viewportScroller.getScrollPosition());
|
||||
}
|
||||
}
|
||||
|
||||
if (window) {
|
||||
window.addEventListener('beforeunload', () => {
|
||||
storeScrollPosition();
|
||||
});
|
||||
}
|
||||
|
||||
router.events.pipe(takeUntilDestroyed()).subscribe((event) => {
|
||||
if (event instanceof NavigationStart) {
|
||||
storeScrollPosition();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function storeScrollPosition() {
|
||||
const router = inject(Router);
|
||||
const viewportScroller = inject(ViewportScroller);
|
||||
const sessionStorage = inject(SessionStorageProvider);
|
||||
|
||||
const url = router.url;
|
||||
sessionStorage.set(url, viewportScroller.getScrollPosition());
|
||||
}
|
||||
|
||||
export function injectRestoreScrollPosition(): () => Promise<void> {
|
||||
const router = inject(Router);
|
||||
const viewportScroller = inject(ViewportScroller);
|
||||
const sessionStorage = inject(SessionStorageProvider);
|
||||
|
||||
return async (delay = 0) => {
|
||||
const url = router.url;
|
||||
const position = await sessionStorage.get(url);
|
||||
|
||||
if (position) {
|
||||
// wait for the next tick to ensure the DOM is ready
|
||||
await new Promise((r) => setTimeout(r, delay));
|
||||
sessionStorage.clear(url);
|
||||
viewportScroller.scrollToPosition(position as [number, number]);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import { Directive, ElementRef, AfterViewInit, OnDestroy, output } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[utilScrolledIntoViewport]',
|
||||
})
|
||||
export class ScrolledIntoViewportDirective implements AfterViewInit, OnDestroy {
|
||||
/**
|
||||
* Emits true when the element enters the viewport and false when it leaves.
|
||||
*/
|
||||
scrolledIntoViewport = output<boolean>({ alias: 'utilScrolledIntoViewport' });
|
||||
|
||||
private observer: IntersectionObserver | null = null;
|
||||
|
||||
constructor(private elementRef: ElementRef<HTMLElement>) {}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
this.scrolledIntoViewport.emit(true);
|
||||
} else {
|
||||
this.scrolledIntoViewport.emit(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: [0, 0.1, 0.5, 1.0] },
|
||||
);
|
||||
|
||||
this.observer.observe(this.elementRef.nativeElement);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
libs/utils/scroll-position/src/lib/store-scroll-position.ts
Normal file
17
libs/utils/scroll-position/src/lib/store-scroll-position.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ViewportScroller } from '@angular/common';
|
||||
import { inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { SessionStorageProvider } from '@isa/core/storage';
|
||||
|
||||
/**
|
||||
* Stores the current scroll position in session storage.
|
||||
* Uses the current router URL as the key.
|
||||
*/
|
||||
export async function storeScrollPosition() {
|
||||
const router = inject(Router);
|
||||
const viewportScroller = inject(ViewportScroller);
|
||||
const sessionStorage = inject(SessionStorageProvider);
|
||||
|
||||
const url = router.url;
|
||||
sessionStorage.set(url, viewportScroller.getScrollPosition());
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
/**
|
||||
* Recursively retrieves the deepest activated route.
|
||||
*
|
||||
* @param route - The starting ActivatedRoute instance.
|
||||
* @returns The deepest ActivatedRoute found.
|
||||
*/
|
||||
export const getDeepestActivatedRoute = (route: ActivatedRoute): ActivatedRoute => {
|
||||
while (route.firstChild) {
|
||||
route = route.firstChild;
|
||||
}
|
||||
return route;
|
||||
};
|
||||
Reference in New Issue
Block a user