use crate::core::{ElementMaybeSignal, IntoElementMaybeSignal, MaybeRwSignal};
use default_struct_builder::DefaultBuilder;
use leptos::prelude::*;
use std::sync::Arc;
pub fn use_textarea_autosize<El, M>(
el: El,
) -> UseTextareaAutosizeReturn<impl Fn() + Clone + Send + Sync>
where
El: IntoElementMaybeSignal<web_sys::Element, M> + Clone,
{
use_textarea_autosize_with_options::<El, M>(el, UseTextareaAutosizeOptions::default())
}
pub fn use_textarea_autosize_with_options<El, M>(
el: El,
options: UseTextareaAutosizeOptions,
) -> UseTextareaAutosizeReturn<impl Fn() + Clone + Send + Sync>
where
El: IntoElementMaybeSignal<web_sys::Element, M> + Clone,
{
#[cfg(not(feature = "ssr"))]
{
use crate::sendwrap_fn;
use wasm_bindgen::JsCast;
let el = el.into_element_maybe_signal();
let textarea = Signal::derive_local(move || {
el.get()
.map(|el| el.unchecked_into::<web_sys::HtmlTextAreaElement>())
});
let UseTextareaAutosizeOptions {
content,
watch: watch_fn,
on_resize,
style_target,
style_prop,
} = options;
let (content, set_content) = content.into_signal();
let (textarea_scroll_height, set_textarea_scroll_height) = signal(1);
let (textarea_old_width, set_textarea_old_width) = signal(0.0);
let trigger_resize = sendwrap_fn!(move || {
textarea.with_untracked(|textarea| {
if let Some(textarea) = textarea {
let mut height = "".to_string();
let border_offset =
if let Ok(Some(style)) = window().get_computed_style(textarea) {
(parse_num(
&style
.get_property_value("border-top-width")
.unwrap_or_default(),
) + parse_num(
&style
.get_property_value("border-bottom-width")
.unwrap_or_default(),
)) as i32
} else {
0
};
web_sys::HtmlElement::style(textarea)
.set_property(&style_prop, "1px")
.ok();
set_textarea_scroll_height.set(textarea.scroll_height() + border_offset + 1);
if let Some(style_target) = style_target.get() {
style_target
.unchecked_into::<web_sys::HtmlElement>()
.style()
.set_property(
&style_prop,
&format!("{}px", textarea_scroll_height.get_untracked()),
)
.ok();
} else {
height = format!("{}px", textarea_scroll_height.get_untracked());
}
web_sys::HtmlElement::style(textarea)
.set_property(&style_prop, &height)
.ok();
}
})
});
Effect::watch(
move || {
content.with(|_| ());
textarea.with(|_| ());
},
{
let trigger_resize = trigger_resize.clone();
move |_, _, _| {
trigger_resize();
}
},
true,
);
Effect::watch(
move || textarea_scroll_height.track(),
move |_, _, _| {
on_resize();
},
false,
);
crate::use_resize_observer(textarea, {
let trigger_resize = trigger_resize.clone();
move |entries, _| {
for entry in entries {
let width = entry.content_rect().width();
if width != textarea_old_width.get_untracked() {
set_textarea_old_width.set(width);
trigger_resize();
}
}
}
});
Effect::watch(
move || watch_fn(),
{
let trigger_resize = trigger_resize.clone();
move |_, _, _| {
trigger_resize();
}
},
false,
);
UseTextareaAutosizeReturn {
content,
set_content,
trigger_resize,
}
}
#[cfg(feature = "ssr")]
{
let _ = el;
let _ = options;
let (content, set_content) = signal("".to_string());
UseTextareaAutosizeReturn {
content: content.into(),
set_content,
trigger_resize: || {},
}
}
}
#[derive(DefaultBuilder)]
#[cfg_attr(feature = "ssr", allow(dead_code))]
pub struct UseTextareaAutosizeOptions {
#[builder(into)]
content: MaybeRwSignal<String>,
watch: Arc<dyn Fn() + Send + Sync>,
on_resize: Arc<dyn Fn() + Send + Sync>,
#[builder(skip)]
style_target: ElementMaybeSignal<web_sys::Element>,
#[builder(into)]
style_prop: String,
}
impl Default for UseTextareaAutosizeOptions {
fn default() -> Self {
Self {
content: MaybeRwSignal::default(),
watch: Arc::new(|| ()),
on_resize: Arc::new(|| ()),
style_target: Default::default(),
style_prop: "height".to_string(),
}
}
}
impl UseTextareaAutosizeOptions {
#[cfg_attr(feature = "ssr", allow(dead_code))]
pub fn style_target<M>(
self,
style_target: impl IntoElementMaybeSignal<web_sys::Element, M>,
) -> Self {
Self {
style_target: style_target.into_element_maybe_signal(),
..self
}
}
}
pub struct UseTextareaAutosizeReturn<F>
where
F: Fn() + Clone + Send + Sync,
{
pub content: Signal<String>,
pub set_content: WriteSignal<String>,
pub trigger_resize: F,
}
#[cfg(not(feature = "ssr"))]
fn parse_num(s: &str) -> u32 {
s.chars()
.map_while(|c| c.to_digit(10))
.fold(0, |acc, digit| acc * 10 + digit)
}