import type { Getter } from "melt"; import { extract } from "./extract.svelte.js"; import { useResizeObserver, watch } from "runed"; import { tick } from "svelte"; export interface TextareaAutosizeOptions { /** Textarea element to autosize. */ element: Getter; /** Textarea content. */ input: Getter; /** Function called when the textarea size changes. */ onResize?: () => void; /** * Specify the style property that will be used to manipulate height. Can be `height | minHeight`. * @default `height` **/ styleProp?: "height" | "minHeight"; } export class TextareaAutosize { #options: TextareaAutosizeOptions; element = $derived.by(() => extract(this.#options.element)); input = $derived.by(() => extract(this.#options.input)); styleProp = $derived.by(() => extract(this.#options.styleProp, "height")); textareaScrollHeight = $state(1); textareaOldWidth = $state(0); constructor(options: TextareaAutosizeOptions) { this.#options = options; watch([() => this.input, () => this.element], () => { tick().then(() => this.triggerResize()); }); watch( () => this.textareaScrollHeight, () => options?.onResize?.() ); useResizeObserver( () => this.element, ([entry]) => { if (!entry) return; const { contentRect } = entry; if (this.textareaOldWidth === contentRect.width) return; requestAnimationFrame(() => { this.textareaOldWidth = contentRect.width; this.triggerResize(); }); } ); } triggerResize() { if (!this.element) return; let height = ""; this.element.style[this.styleProp] = "1px"; this.textareaScrollHeight = this.element?.scrollHeight; height = `${this.textareaScrollHeight}px`; this.element.style[this.styleProp] = height; } }