import type * as flexui from '@openflexui/core'
import { type GeneralComponent } from './component'
export type IntersectionObserverMargins = {
left?: number
top?: number
right?: number
bottom?: number
}
const normalizeMarginRect = (margins: IntersectionObserverMargins) => ({
left: margins.left || 0,
top: margins.top || 0,
right: margins.right || 0,
bottom: margins.bottom || 0,
})
const defaultMargins = {
left: 0,
top: 0,
right: 0,
bottom: 0,
}
export class IntersectionObserver {
private _$comp: GeneralComponent
private _$thresholds: number[]
private _$initialRatio: number
private _$observeAll: boolean
private _$selector: string | null = null
private _$margins: IntersectionObserverMargins | null = null
private _$observers: flexui.backend.Observer[] = []
constructor(
comp: GeneralComponent,
thresholds: number[],
initialRatio: number,
observeAll: boolean,
) {
this._$comp = comp
this._$thresholds = thresholds
this._$initialRatio = initialRatio
this._$observeAll = observeAll
}
relativeTo(selector: string, margins?: IntersectionObserverMargins): this {
this._$selector = selector
this._$margins = margins ?? defaultMargins
return this
}
relativeToViewport(margins?: IntersectionObserverMargins): this {
this._$selector = null
this._$margins = margins ?? defaultMargins
return this
}
observe(
targetSelector: string,
listener: (status: flexui.backend.IntersectionStatus) => void,
) {
const shadowRoot = this._$comp._$.getShadowRoot()
let targets: flexui.Element[]
if (!shadowRoot) {
targets = []
} else if (this._$observeAll) {
targets = shadowRoot.querySelectorAll(targetSelector)
} else {
const elem = shadowRoot.querySelector(targetSelector)
if (elem === null) {
targets = []
} else {
targets = [elem]
}
}
let relativeElement: flexui.Element | null
if (this._$selector === null || !shadowRoot) {
relativeElement = null
} else {
const rel = shadowRoot.querySelector(this._$selector)
if (rel === null) {
} else {
relativeElement = rel
}
}
if (!this._$margins) {
throw new Error('`relativeTo` or `relativeToViewport` should be called before observe')
}
const { left, top, right, bottom } = normalizeMarginRect(this._$margins)
const margin = `${top}px ${right}px ${bottom}px ${left}px`
targets.forEach((target) => {
let initial: number | null = this._$initialRatio
const observer = target.createIntersectionObserver(
relativeElement,
margin,
this._$thresholds,
(args) => {
if (initial !== null) {
let i = 0
for (; i < this._$thresholds.length; i += 1) {
const threshold = this._$thresholds[i]!
if (initial === args.intersectionRatio) continue
if ((initial - threshold) * (args.intersectionRatio - threshold) <= 0) break
}
initial = null
if (i < this._$thresholds.length) return
}
listener(args)
},
)
if (observer === null) {
} else {
this._$observers.push(observer)
}
})
}
disconnect() {
const observers = this._$observers
this._$observers = []
observers.forEach((observer) => observer.disconnect())
}
}