import { RNSharedElementContent } from "./RNSharedElementContent.web";
import { RNSharedElementStyle } from "./RNSharedElementStyle.web";
import { Rect } from "./Rect.web";
import { IHTMLElement } from "./types";
export type RNSharedElementNodeStyleCallback = (
value: RNSharedElementStyle
) => void;
export type RNSharedElementNodeContentCallback = (
value: RNSharedElementContent
) => void;
export class RNSharedElementNode {
public readonly domNode: IHTMLElement;
public readonly ancestorDomNode: IHTMLElement;
public readonly isParent: boolean;
private hideRefCount: number = 0;
private hideOpacity: string | null = null;
private refCount: number = 1;
private styleCache: RNSharedElementStyle | null = null;
private styleCallbacks: RNSharedElementNodeStyleCallback[] | null = null;
private contentCache: RNSharedElementContent | null = null;
private contentCallbacks: RNSharedElementNodeContentCallback[] | null = null;
constructor(
domNode: IHTMLElement,
isParent: boolean,
ancestorDomNode: IHTMLElement
) {
this.domNode = domNode;
this.isParent = isParent;
this.ancestorDomNode = ancestorDomNode;
}
addRef() {
return ++this.refCount;
}
releaseRef() {
return --this.refCount;
}
addHideRef() {
this.hideRefCount++;
if (this.hideRefCount === 1) {
const element = this.resolvedElement;
this.hideOpacity = element!.style.opacity;
element!.style.opacity = "0";
}
}
releaseHideRef() {
this.hideRefCount--;
if (this.hideRefCount === 0) {
const element = this.resolvedElement;
element!.style.opacity = this.hideOpacity;
}
}
get resolvedElement(): IHTMLElement | null {
let element: IHTMLElement = this.domNode;
if (this.isParent) {
if (element.childNodes.length === 1) {
element = element.childNodes[0];
} else if (element.childNodes.length <= 0) {
console.log("Child for parent doesnt exist");
return null;
}
}
const { childNodes } = element;
if (childNodes.length === 2) {
for (let i = 0; i < 2; i++) {
const childNode: IHTMLElement = childNodes[i];
if (childNode.tagName === "IMG") {
element = childNodes[i ? 0 : i + 1];
break;
}
}
}
return element;
}
get resolvedAncestor(): IHTMLElement | null {
return this.ancestorDomNode;
}
requestStyle(): Promise<RNSharedElementStyle> {
if (this.styleCache) {
return Promise.resolve(this.styleCache);
}
return new Promise((resolve) => {
this.styleCallbacks = this.styleCallbacks || [];
this.styleCallbacks.push(resolve);
if (!this.fetchInitialStyle()) {
console.debug("Failed to fetch style");
}
});
}
private fetchInitialStyle(): boolean {
const element = this.resolvedElement;
const ancestor = this.resolvedAncestor;
if (!element || !ancestor) return false;
if (!this.styleCallbacks) return true;
const rect = element.getBoundingClientRect();
const ancestorRect = ancestor.getBoundingClientRect();
const translateX = ancestorRect.x;
const translateY = ancestorRect.y;
const layout = new Rect({
x: rect.x - translateX,
y: rect.y - translateY,
width: rect.width,
height: rect.height,
});
const style = new RNSharedElementStyle(
layout,
window.getComputedStyle(element, null)
);
this.styleCache = style;
const callbacks = this.styleCallbacks;
this.styleCallbacks = null;
callbacks.forEach((callback) => callback(style));
return true;
}
async requestContent(): Promise<RNSharedElementContent> {
if (this.contentCache) return this.contentCache;
return new Promise((resolve) => {
if (this.contentCallbacks) return;
this.contentCallbacks = this.contentCallbacks || [];
this.contentCallbacks.push(resolve);
this.fetchInitialContent();
});
}
private async fetchInitialContent(): Promise<boolean> {
const element = this.resolvedElement;
if (!element) return false;
if (!this.contentCallbacks) return true;
const size = await RNSharedElementContent.getSize(element);
if (!size) {
return false;
}
const content = new RNSharedElementContent(element, size);
this.contentCache = content;
const callbacks = this.contentCallbacks;
this.contentCallbacks = null;
callbacks.forEach((callback) => callback(content));
return true;
}
}