-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(components/dimension): provide signal to observe element size
```ts import {Component, effect, ElementRef, inject} from '@angular/core'; import {dimension} from '@scion/components/dimension'; @component({...}) class YourComponent { private host = inject(ElementRef<HTMLElement>); private dimension = dimension(this.host); constructor() { effect(() => console.log('size', this.dimension())); } } ```
- Loading branch information
1 parent
c1fef65
commit 462919d
Showing
8 changed files
with
674 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
projects/scion/components/dimension/src/dimension.signal.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* | ||
* Copyright (c) 2018-2024 Swiss Federal Railways | ||
* | ||
* This program and the accompanying materials are made | ||
* available under the terms of the Eclipse Public License 2.0 | ||
* which is available at https://www.eclipse.org/legal/epl-2.0/ | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
|
||
import {assertInInjectionContext, assertNotInReactiveContext, computed, DestroyRef, effect, ElementRef, inject, Injector, isSignal, signal, Signal} from '@angular/core'; | ||
import {SciDimension} from './dimension'; | ||
import {coerceElement} from '@angular/cdk/coercion'; | ||
import {Objects} from '@scion/toolkit/util'; | ||
|
||
/** | ||
* Creates a signal to observe the size of an element. | ||
* | ||
* The element to observe can be passed as a signal, in particular useful for observing view children as they cannot be read in the constructor. | ||
* The signal uses to the native {@link ResizeObserver} to monitor element size changes. Destroying the injection context will unsubscribe from {@link ResizeObserver}. | ||
* | ||
* Usage: | ||
* - Must be called within an injection context or with an injector provided for automatic unsubscription. | ||
* - Must NOT be called within a reactive context to avoid repeated subscriptions. | ||
*/ | ||
export function dimension(elementLike: HTMLElement | ElementRef<HTMLElement> | Signal<HTMLElement | ElementRef<HTMLElement>>, options?: {injector?: Injector}): Signal<SciDimension>; | ||
export function dimension(elementLike: HTMLElement | ElementRef<HTMLElement> | Signal<HTMLElement | ElementRef<HTMLElement> | undefined>, options?: {injector?: Injector}): Signal<SciDimension | undefined>; | ||
export function dimension(elementLike: HTMLElement | ElementRef<HTMLElement> | Signal<HTMLElement | ElementRef<HTMLElement> | undefined>, options?: {injector?: Injector}): Signal<SciDimension | undefined> { | ||
assertNotInReactiveContext(dimension, 'Invoking `dimension` causes new subscriptions every time. Move `dimension` outside of the reactive context and read the signal value where needed.'); | ||
if (!options?.injector) { | ||
assertInInjectionContext(dimension); | ||
} | ||
|
||
const injector = options?.injector ?? inject(Injector); | ||
const element = computed(() => coerceElement(isSignal(elementLike) ? elementLike() : elementLike)); | ||
const onResize = signal<void>(undefined, {equal: () => false}); | ||
|
||
// Signal 'onResize' when the element size changes. | ||
// Note: Run callback in animation frame to avoid the error: "ResizeObserver loop completed with undelivered notifications". | ||
const resizeObserver = new ResizeObserver(() => requestAnimationFrame(() => onResize.set())); | ||
injector.get(DestroyRef).onDestroy(() => resizeObserver.disconnect()); | ||
|
||
// Connnect to the element. | ||
effect(onCleanup => { | ||
const el = element(); | ||
if (el) { | ||
resizeObserver.observe(el); | ||
onCleanup(() => resizeObserver.unobserve(el)); | ||
} | ||
}, {injector}); | ||
|
||
return computed(() => { | ||
const el = element(); | ||
if (!el) { | ||
return undefined; | ||
} | ||
|
||
// Track when the element is resized. | ||
onResize(); | ||
|
||
return { | ||
clientWidth: el.clientWidth, | ||
offsetWidth: el.offsetWidth, | ||
clientHeight: el.clientHeight, | ||
offsetHeight: el.offsetHeight, | ||
element: el, | ||
}; | ||
}, {equal: Objects.isEqual}); | ||
} |
Oops, something went wrong.