seitu
Version:
89 lines (88 loc) • 2.68 kB
JavaScript
import { createReadableSubscription, createSubscription } from "../../core/subscription/index.mjs";
//#region src/web/element-size/index.ts
const emptySize = {
width: 0,
height: 0
};
/**
* Tracks an element's width and height through `ResizeObserver`. Sizes ignore
* CSS transforms. A null element and the server snapshot are `0 × 0`.
*
* @example Vanilla
* ```ts twoslash
* import { createElementSize } from 'seitu/web'
*
* const size = createElementSize({
* element: document.querySelector('.container'),
* })
*
* size.subscribe(({ width, height }) => {
* console.log(width, height)
* })
*
* const { width } = size.get()
* console.log(width)
* ```
*
* @example React (callback ref)
* ```tsx twoslash title="page.tsx"
* 'use client'
*
* import * as React from 'react'
* import { createElementSize } from 'seitu/web'
* import { useSubscription } from 'seitu/react'
*
* function Panel() {
* const [ref, setRef] = React.useState<HTMLDivElement | null>(null)
* const size = useSubscription(() => createElementSize({
* element: ref,
* }), { deps: [ref] })
*
* return (
* <div ref={setRef}>
* {size.width} × {size.height}
* </div>
* )
* }
* ```
*/
function createElementSize(options) {
const { box = "border-box" } = options;
const resolveElement = () => typeof options.element === "function" ? options.element() : options.element;
const get = () => {
const element = resolveElement();
if (!element) return emptySize;
const style = getComputedStyle(element);
const px = (value) => Number.parseFloat(value) || 0;
const extraX = px(style.paddingLeft) + px(style.paddingRight) + px(style.borderLeftWidth) + px(style.borderRightWidth);
const extraY = px(style.paddingTop) + px(style.paddingBottom) + px(style.borderTopWidth) + px(style.borderBottomWidth);
const width = px(style.width);
const height = px(style.height);
const isBorderBox = style.boxSizing === "border-box";
if (!width && !height) return emptySize;
if (box === "border-box") return isBorderBox ? {
width,
height
} : {
width: width + extraX,
height: height + extraY
};
return isBorderBox ? {
width: Math.max(0, width - extraX),
height: Math.max(0, height - extraY)
} : {
width,
height
};
};
const { subscribe, notify } = createSubscription({ onFirstSubscribe() {
const element = resolveElement();
if (!element || typeof ResizeObserver === "undefined") return;
const observer = new ResizeObserver(() => notify());
observer.observe(element, { box });
return () => observer.disconnect();
} });
return createReadableSubscription(get, subscribe, notify, () => emptySize);
}
//#endregion
export { createElementSize };