|
import type { Getter } from "melt"; |
|
import { extract } from "./extract.svelte.js"; |
|
import { useResizeObserver, watch } from "runed"; |
|
import { tick } from "svelte"; |
|
|
|
export interface TextareaAutosizeOptions { |
|
|
|
element: Getter<HTMLElement | undefined>; |
|
|
|
input: Getter<string>; |
|
|
|
onResize?: () => void; |
|
|
|
|
|
|
|
|
|
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; |
|
}; |
|
} |
|
|