diff --git a/src/modules/esl-event-listener/core/targets/resize.adapter.event.ts b/src/modules/esl-event-listener/core/targets/resize.adapter.event.ts index 3e80cdbe0..1b6aef06e 100644 --- a/src/modules/esl-event-listener/core/targets/resize.adapter.event.ts +++ b/src/modules/esl-event-listener/core/targets/resize.adapter.event.ts @@ -1,4 +1,5 @@ import {overrideEvent} from '../../../esl-utils/dom/events/misc'; +import {getWindowRect} from '../../../esl-utils/dom/window'; import type {ESLResizeObserverTarget} from './resize.adapter'; @@ -33,7 +34,7 @@ export class ESLElementResizeEvent extends UIEvent implements ResizeObserverEntr */ public readonly devicePixelContentBoxSize: readonly ResizeObserverSize[]; - protected constructor(target: Element) { + protected constructor(target: Element | Window) { super('resize', {bubbles: false, cancelable: false}); overrideEvent(this, 'target', target); } @@ -52,10 +53,33 @@ export class ESLElementResizeEvent extends UIEvent implements ResizeObserverEntr return event; } - // /** Creates {@link ESLElementResizeEvent} from resize {@link Event} */ - // public static fromEvent(event: UIEvent): ESLElementResizeEvent { - // // TODO: converter - // } + /** Creates {@link ESLElementResizeEvent} from resize {@link Event} */ + public static fromEvent(e: Event): ESLElementResizeEvent | never { + const {target} = e; + if (!target) throw new Error('[ESLElementResizeEvent]: original event should have a `target`'); + let borderBoxSize: ResizeObserverSize[]; + let contentBoxSize: ResizeObserverSize[]; + + if (target instanceof Element) { + const rect = target.getBoundingClientRect(); + contentBoxSize = [{inlineSize: target.clientWidth, blockSize: target.clientHeight}]; + borderBoxSize = [{inlineSize: rect.width, blockSize: rect.height}]; + } else if (target instanceof Window) { + const wndRect = getWindowRect(); + contentBoxSize = [{inlineSize: wndRect.width, blockSize: wndRect.height}]; + borderBoxSize = contentBoxSize; + } else throw new Error('Event target must be an element or window object'); + + const contentRect = new DOMRectReadOnly(0, 0, contentBoxSize[0].inlineSize, contentBoxSize[0].blockSize); + const devicePixelContentBoxSize = [{ + inlineSize: contentBoxSize[0].inlineSize * window.devicePixelRatio, + blockSize: contentBoxSize[0].blockSize * window.devicePixelRatio + }]; + + const event = new ESLElementResizeEvent(target); + Object.assign(event, {contentRect, borderBoxSize, contentBoxSize, devicePixelContentBoxSize}); + return event; + } } declare global { diff --git a/src/modules/esl-event-listener/core/targets/resize.adapter.ts b/src/modules/esl-event-listener/core/targets/resize.adapter.ts index 3afcfc8c2..686212a0b 100644 --- a/src/modules/esl-event-listener/core/targets/resize.adapter.ts +++ b/src/modules/esl-event-listener/core/targets/resize.adapter.ts @@ -1,5 +1,6 @@ import {SyntheticEventTarget} from '../../../esl-utils/dom/events/target'; import {resolveDomTarget} from '../../../esl-utils/abstract/dom-target'; +import {ESLEventListener} from '../listener'; import {ESLElementResizeEvent} from './resize.adapter.event'; import type {ESLDomElementTarget} from '../../../esl-utils/abstract/dom-target'; @@ -9,36 +10,36 @@ export {ESLElementResizeEvent}; /** Adapter class for {@link ResizeObserver} that implements {@link EventTarget} */ export class ESLResizeObserverTarget extends SyntheticEventTarget { /** {@link ESLResizeObserverTarget} instances holder */ - protected static readonly mapping = new WeakMap(); + protected static readonly mapping = new WeakMap(); /** {@link ResizeObserver} instance to observe DOM element related to {@link ESLResizeObserverTarget} */ protected static readonly observer$$ = new ResizeObserver((changes: ResizeObserverEntry[]) => changes.forEach(this.handleChange, this) ); - /** Observed {@link Element} of the {@link ESLResizeObserverTarget} instance */ - public readonly target: Element; + /** Observed {@link Element} of the {@link ESLResizeObserverTarget} instance or {@link Window} object */ + public readonly target: Element | Window; /** Internal method to handle {@link ResizeObserver} entry change */ protected static handleChange( this: typeof ESLResizeObserverTarget, - entry: ResizeObserverEntry + entry: ResizeObserverEntry | Event ): void { - const adapter = this.mapping.get(entry.target); + const adapter = this.mapping.get(entry.target as Element | Window); if (!adapter) return; - adapter.dispatchEvent(ESLElementResizeEvent.fromEntry(entry)); + adapter.dispatchEvent(entry instanceof Event ? ESLElementResizeEvent.fromEvent(entry) : ESLElementResizeEvent.fromEntry(entry)); } /** Creates {@link ESLResizeObserverTarget} instance for the {@link ESLDomElementTarget} */ - public static for(target: ESLDomElementTarget): ESLResizeObserverTarget { + public static for(target: ESLDomElementTarget | Window): ESLResizeObserverTarget { return new ESLResizeObserverTarget(target); } /** * Creates {@link ESLResizeObserverTarget} for the {@link ESLDomElementTarget}. - * Note the {@link ESLResizeObserverTarget} instances are singletons relatively to the {@link Element} + * Note the {@link ESLResizeObserverTarget} instances are singletons relatively to the {@link Element} or {@link Window} */ - protected constructor(target: ESLDomElementTarget) { - target = resolveDomTarget(target); + protected constructor(target: ESLDomElementTarget | Window) { + if (!(target instanceof Window)) target = resolveDomTarget(target); const instance = ESLResizeObserverTarget.mapping.get(target); if (instance) return instance; @@ -47,7 +48,7 @@ export class ESLResizeObserverTarget extends SyntheticEventTarget { ESLResizeObserverTarget.mapping.set(this.target, this); } - /** Subscribes to the observed target {@link Element} changes */ + /** Subscribes to the observed target {@link Element} or {@link Window} changes */ public override addEventListener(callback: EventListener): void; public override addEventListener(event: 'resize', callback: EventListener): void; public override addEventListener(event: any, callback: EventListener = event): void { @@ -58,10 +59,15 @@ export class ESLResizeObserverTarget extends SyntheticEventTarget { super.addEventListener('resize', callback); if (this.getEventListeners('resize').length > 1) return; - ESLResizeObserverTarget.observer$$.observe(this.target); + + if (this.target instanceof Window) { + ESLEventListener.subscribe(this, (e: Event) => (this.constructor as typeof ESLResizeObserverTarget).handleChange(e), {event: 'resize', target: window}); + } else { + ESLResizeObserverTarget.observer$$.observe(this.target); + } } - /** Unsubscribes from the observed target {@link Element} changes */ + /** Unsubscribes from the observed target {@link Element} or {@link Window} changes */ public override removeEventListener(callback: EventListener): void; public override removeEventListener(event: 'resize', callback: EventListener): void; public override removeEventListener(event: any, callback: EventListener = event): void { @@ -69,6 +75,11 @@ export class ESLResizeObserverTarget extends SyntheticEventTarget { super.removeEventListener('resize', callback); if (this.hasEventListener('resize')) return; - ESLResizeObserverTarget.observer$$.unobserve(this.target); + + if (this.target instanceof Window) { + ESLEventListener.get(this, 'resize').forEach((listener) => listener.unsubscribe()); + } else { + ESLResizeObserverTarget.observer$$.unobserve(this.target); + } } }