Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(esl-utils): Support for window as a target for ESLResizeObserver… #1611

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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);
}
Expand All @@ -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 {
Expand Down
39 changes: 25 additions & 14 deletions src/modules/esl-event-listener/core/targets/resize.adapter.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<Element, ESLResizeObserverTarget>();
protected static readonly mapping = new WeakMap<Element | Window, ESLResizeObserverTarget>();
/** {@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;

Expand All @@ -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 {
Expand All @@ -58,17 +59,27 @@ 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 {
if (typeof event === 'string' && event !== 'resize') return;

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);
}
}
}