@indielayer/ui
Version:
Indielayer UI Components with Tailwind CSS build for Vue 3
163 lines (125 loc) • 3.49 kB
text/typescript
import EventEmitter from 'node:events'
type GetDOMRect = (element: HTMLElement) => DOMRectReadOnly | undefined | void;
const emitter = new EventEmitter()
emitter.setMaxListeners(100)
const elementToDOMRect = new Map<HTMLElement, DOMRect>()
let defaultDomRect: DOMRectReadOnly = new DOMRect(0, 0, 0, 0)
let disabled: boolean = false
let getDOMRect: GetDOMRect | undefined = undefined
export function disableResizeObserverForCurrentTest() {
disabled = true
}
export function setDefaultElementSize({
height,
width,
}: {
height: number;
width: number;
}) {
defaultDomRect = new DOMRect(0, 0, width, height)
emitter.emit('change')
}
export function setElementSizeFunction(value: GetDOMRect) {
getDOMRect = value
emitter.emit('change')
}
export function setElementSize({
element,
height,
width,
}: {
element: HTMLElement;
height: number;
width: number;
}) {
elementToDOMRect.set(element, new DOMRect(0, 0, width, height))
emitter.emit('change', element)
}
export function simulateUnsupportedEnvironmentForTest() {
// @ts-expect-error Simulate API being unsupported
window.ResizeObserver = null
}
export function mockResizeObserver() {
disabled = false
const originalResizeObserver = window.ResizeObserver
window.ResizeObserver = MockResizeObserver
return function unmockResizeObserver() {
window.ResizeObserver = originalResizeObserver
defaultDomRect = new DOMRect(0, 0, 0, 0)
disabled = false
getDOMRect = undefined
elementToDOMRect.clear()
}
}
class MockResizeObserver implements ResizeObserver {
readonly #callback: ResizeObserverCallback
#disconnected: boolean = false
#elements: Set<HTMLElement> = new Set()
constructor(callback: ResizeObserverCallback) {
this.#callback = callback
emitter.addListener('change', this.#onChange)
}
observe(element: HTMLElement) {
if (this.#disconnected) {
return
}
this.#elements.add(element)
this.#notify([element])
}
unobserve(element: HTMLElement) {
this.#elements.delete(element)
}
disconnect() {
this.#disconnected = true
this.#elements.clear()
emitter.removeListener('change', this.#onChange)
}
#notify(elements: HTMLElement[]) {
if (disabled) {
return
}
const entries = elements.map((element) => {
const computedStyle = window.getComputedStyle(element)
const writingMode = computedStyle.writingMode
let contentRect: DOMRectReadOnly =
elementToDOMRect.get(element) ?? defaultDomRect
if (getDOMRect) {
const contentRectOverride = getDOMRect(element)
if (contentRectOverride) {
contentRect = contentRectOverride
}
}
let blockSize = 0
let inlineSize = 0
if (writingMode.includes('vertical')) {
blockSize = contentRect.width
inlineSize = contentRect.height
} else {
blockSize = contentRect.height
inlineSize = contentRect.width
}
return {
borderBoxSize: [
{
blockSize,
inlineSize,
},
],
contentBoxSize: [],
contentRect,
devicePixelContentBoxSize: [],
target: element,
}
})
this.#callback(entries, this)
}
#onChange = (target?: HTMLElement) => {
if (target) {
if (this.#elements.has(target)) {
this.#notify([target])
}
} else {
this.#notify(Array.from(this.#elements))
}
}
}