INIT
CI / build (push) Has been skipped
CI / deploy (push) Successful in 1m11s

This commit is contained in:
eewing
2026-02-18 15:17:47 -06:00
parent e74c106f85
commit ec317eb17c
11532 changed files with 1631690 additions and 1 deletions
@@ -0,0 +1,34 @@
import { type ConfigurableWindow } from "../../internal/configurable-globals.js";
import type { MaybeElementGetter } from "../../internal/types.js";
export type ElementSizeOptions = ConfigurableWindow & {
box?: "content-box" | "border-box";
};
/**
* Returns a reactive value holding the size of `node`.
*
* Accepts an `options` object with the following properties:
* - `initialSize`: The initial size of the element. Defaults to `{ width: 0, height: 0 }`.
* - `box`: The box model to use. Can be either `"content-box"` or `"border-box"`. Defaults to `"border-box"`.
*
* @returns an object with `width` and `height` properties.
*
* @see {@link https://runed.dev/docs/utilities/element-size}
*/
export declare class ElementSize {
#private;
constructor(node: MaybeElementGetter, options?: ElementSizeOptions);
calculateSize(): {
width: number;
height: number;
} | undefined;
getSize(): {
width: number;
height: number;
};
get current(): {
width: number;
height: number;
};
get width(): number;
get height(): number;
}
+114
View File
@@ -0,0 +1,114 @@
import { defaultWindow } from "../../internal/configurable-globals.js";
import { get } from "../../internal/utils/get.js";
import { createSubscriber } from "svelte/reactivity";
/**
* Returns a reactive value holding the size of `node`.
*
* Accepts an `options` object with the following properties:
* - `initialSize`: The initial size of the element. Defaults to `{ width: 0, height: 0 }`.
* - `box`: The box model to use. Can be either `"content-box"` or `"border-box"`. Defaults to `"border-box"`.
*
* @returns an object with `width` and `height` properties.
*
* @see {@link https://runed.dev/docs/utilities/element-size}
*/
export class ElementSize {
// no need to use `$state` here since we are using createSubscriber
#size = {
width: 0,
height: 0,
};
#observed = false;
#options;
#node;
#window;
// we use a derived here to extract the width so that if the width doesn't change we don't get a state update
// which we would get if we would just use a getter since the version of the subscriber will be changing
#width = $derived.by(() => {
this.#subscribe?.();
return this.getSize().width;
});
// we use a derived here to extract the height so that if the height doesn't change we don't get a state update
// which we would get if we would just use a getter since the version of the subscriber will be changing
#height = $derived.by(() => {
this.#subscribe?.();
return this.getSize().height;
});
// we need to use a derived here because the class will be created before the node is bound to the ref
#subscribe = $derived.by(() => {
const node$ = get(this.#node);
if (!node$)
return;
return createSubscriber((update) => {
if (!this.#window)
return;
const observer = new this.#window.ResizeObserver((entries) => {
this.#observed = true;
for (const entry of entries) {
const boxSize = this.#options.box === "content-box" ? entry.contentBoxSize : entry.borderBoxSize;
const boxSizeArr = Array.isArray(boxSize) ? boxSize : [boxSize];
this.#size.width = boxSizeArr.reduce((acc, size) => Math.max(acc, size.inlineSize), 0);
this.#size.height = boxSizeArr.reduce((acc, size) => Math.max(acc, size.blockSize), 0);
}
update();
});
observer.observe(node$);
return () => {
this.#observed = false;
observer.disconnect();
};
});
});
constructor(node, options = { box: "border-box" }) {
this.#window = options.window ?? defaultWindow;
this.#options = options;
this.#node = node;
this.#size = {
width: 0,
height: 0,
};
}
calculateSize() {
const element = get(this.#node);
// no element or no window, return undefined, we will return 0x0 in the getSize method
if (!element || !this.#window) {
return;
}
const offsetWidth = element.offsetWidth;
const offsetHeight = element.offsetHeight;
// easy mode, just return offsets
if (this.#options.box === "border-box") {
return {
width: offsetWidth,
height: offsetHeight,
};
}
// hard mode, we need to calculate the content size
const style = this.#window.getComputedStyle(element);
const paddingWidth = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
const paddingHeight = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
const borderWidth = parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);
const borderHeight = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
const contentWidth = offsetWidth - paddingWidth - borderWidth;
const contentHeight = offsetHeight - paddingHeight - borderHeight;
return {
width: contentWidth,
height: contentHeight,
};
}
getSize() {
// if the resize observer already run we can just return the size
// otherwise we calculate the size if possible or we return the initial size
return this.#observed ? this.#size : (this.calculateSize() ?? this.#size);
}
get current() {
this.#subscribe?.();
return this.getSize();
}
get width() {
return this.#width;
}
get height() {
return this.#height;
}
}
+1
View File
@@ -0,0 +1 @@
export * from "./element-size.svelte.js";
+1
View File
@@ -0,0 +1 @@
export * from "./element-size.svelte.js";