diff --git a/examples/use_element_bounding/src/main.rs b/examples/use_element_bounding/src/main.rs index f0bb028..48ab2e4 100644 --- a/examples/use_element_bounding/src/main.rs +++ b/examples/use_element_bounding/src/main.rs @@ -1,13 +1,47 @@ +use leptos::html::Textarea; use leptos::*; -use leptos_use::docs::demo_or_body; -use leptos_use::use_element_bounding; +use leptos_use::docs::{demo_or_body, Note}; +use leptos_use::{use_element_bounding, UseElementBoundingReturn}; #[component] fn Demo() -> impl IntoView { + let el = create_node_ref:: + } } fn main() { diff --git a/examples/use_element_bounding/style/output.css b/examples/use_element_bounding/style/output.css index ab5191f..263b210 100644 --- a/examples/use_element_bounding/style/output.css +++ b/examples/use_element_bounding/style/output.css @@ -1,4 +1,4 @@ -[type='text'],[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select { +[type='text'],input:where(:not([type])),[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select { -webkit-appearance: none; -moz-appearance: none; appearance: none; @@ -15,7 +15,7 @@ --tw-shadow: 0 0 #0000; } -[type='text']:focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus { +[type='text']:focus, input:where(:not([type])):focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus { outline: 2px solid transparent; outline-offset: 2px; --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); @@ -44,6 +44,11 @@ input::placeholder,textarea::placeholder { ::-webkit-date-and-time-value { min-height: 1.5em; + text-align: inherit; +} + +::-webkit-datetime-edit { + display: inline-flex; } ::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field { @@ -61,7 +66,7 @@ select { print-color-adjust: exact; } -[multiple] { +[multiple],[size]:where(select:not([size="1"])) { background-image: initial; background-position: initial; background-repeat: unset; @@ -126,10 +131,26 @@ select { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); } +@media (forced-colors: active) { + [type='checkbox']:checked { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + [type='radio']:checked { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); } +@media (forced-colors: active) { + [type='radio']:checked { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + [type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus { border-color: transparent; background-color: currentColor; @@ -144,6 +165,14 @@ select { background-repeat: no-repeat; } +@media (forced-colors: active) { + [type='checkbox']:indeterminate { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + [type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus { border-color: transparent; background-color: currentColor; @@ -264,8 +293,41 @@ select { --tw-backdrop-sepia: ; } -.block { - display: block; +.static { + position: static; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.h-\[175px\] { + height: 175px; +} + +.w-\[335px\] { + width: 335px; +} + +.resize { + resize: both; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.p-4 { + padding: 1rem; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.leading-10 { + line-height: 2.5rem; } .text-\[--brand-color\] { diff --git a/src/lib.rs b/src/lib.rs index 1e1c5a8..133ecb0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,6 @@ mod is_none; mod is_ok; mod is_some; mod on_click_outside; -mod use_element_bounding; mod signal_debounced; mod signal_throttled; mod use_active_element; @@ -28,6 +27,7 @@ mod use_document; mod use_document_visibility; mod use_draggable; mod use_drop_zone; +mod use_element_bounding; mod use_element_hover; mod use_element_size; mod use_element_visibility; @@ -70,7 +70,6 @@ pub use is_none::*; pub use is_ok::*; pub use is_some::*; pub use on_click_outside::*; -pub use use_element_bounding::*; pub use signal_debounced::*; pub use signal_throttled::*; pub use use_active_element::*; @@ -84,6 +83,7 @@ pub use use_document::*; pub use use_document_visibility::*; pub use use_draggable::*; pub use use_drop_zone::*; +pub use use_element_bounding::*; pub use use_element_hover::*; pub use use_element_size::*; pub use use_element_visibility::*; diff --git a/src/use_element_bounding.rs b/src/use_element_bounding.rs index 15cd98e..d5b963a 100644 --- a/src/use_element_bounding.rs +++ b/src/use_element_bounding.rs @@ -1,7 +1,12 @@ +use crate::core::ElementMaybeSignal; +use crate::{ + use_event_listener_with_options, use_resize_observer, use_window, UseEventListenerOptions, +}; use default_struct_builder::DefaultBuilder; +use leptos::ev::{resize, scroll}; use leptos::*; -/// +/// Reactive [bounding box](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) of an HTML element /// /// ## Demo /// @@ -11,33 +16,202 @@ use leptos::*; /// /// ``` /// # use leptos::*; -/// # use leptos_use::use_element_bounding; +/// # use leptos::html::Div; +/// # use leptos_use::{use_element_bounding, UseElementBoundingReturn}; /// # /// # #[component] /// # fn Demo() -> impl IntoView { -/// use_element_bounding(); -/// # -/// # view! { } +/// let el = create_node_ref::
(); +/// let UseElementBoundingReturn { +/// x, y, top,right,bottom,left, width, height, .. +/// } = use_element_bounding(el); +/// +/// view! {
} /// # } /// ``` -pub fn use_element_bounding() -> UseElementBoundingReturn { - use_element_bounding_with_options(UseElementBoundingOptions::default()) +pub fn use_element_bounding(target: El) -> UseElementBoundingReturn +where + El: Into> + Clone, + T: Into + Clone + 'static, +{ + use_element_bounding_with_options(target, UseElementBoundingOptions::default()) } /// Version of [`use_element_bounding`] that takes a `UseElementBoundingOptions`. See [`use_element_bounding`] for how to use. -pub fn use_element_bounding_with_options(options: UseElementBoundingOptions) -> UseElementBoundingReturn { - UseElementBoundingReturn {} +pub fn use_element_bounding_with_options( + target: El, + options: UseElementBoundingOptions, +) -> UseElementBoundingReturn +where + El: Into> + Clone, + T: Into + Clone + 'static, +{ + let UseElementBoundingOptions { + reset, + window_resize, + window_scroll, + immediate, + } = options; + + let (height, set_height) = create_signal(0.0); + let (width, set_width) = create_signal(0.0); + let (left, set_left) = create_signal(0.0); + let (right, set_right) = create_signal(0.0); + let (top, set_top) = create_signal(0.0); + let (bottom, set_bottom) = create_signal(0.0); + let (x, set_x) = create_signal(0.0); + let (y, set_y) = create_signal(0.0); + + let target = target.into(); + + let update = { + let target = target.clone(); + + move || { + let el = target.get_untracked(); + + if let Some(el) = el { + let rect = el.into().get_bounding_client_rect(); + + set_height.set(rect.height()); + set_width.set(rect.width()); + set_left.set(rect.x()); + set_right.set(rect.x() + rect.width()); + set_top.set(rect.y()); + set_bottom.set(rect.y() + rect.height()); + set_x.set(rect.x()); + set_y.set(rect.y()); + } else if reset { + set_height.set(0.0); + set_width.set(0.0); + set_left.set(0.0); + set_right.set(0.0); + set_top.set(0.0); + set_bottom.set(0.0); + set_x.set(0.0); + set_y.set(0.0); + } + } + }; + + use_resize_observer(target.clone(), { + let update = update.clone(); + + move |_, _| { + update(); + } + }); + + let _ = watch( + move || target.get(), + { + let update = update.clone(); + move |_, _, _| { + update(); + } + }, + false, + ); + + if window_scroll { + let _ = use_event_listener_with_options( + use_window(), + scroll, + { + let update = update.clone(); + move |_| update() + }, + UseEventListenerOptions::default() + .capture(true) + .passive(true), + ); + } + + if window_resize { + let _ = use_event_listener_with_options( + use_window(), + resize, + { + let update = update.clone(); + move |_| update() + }, + UseEventListenerOptions::default().passive(true), + ); + } + + if immediate { + update(); + } + + UseElementBoundingReturn { + height: height.into(), + width: width.into(), + left: left.into(), + right: right.into(), + top: top.into(), + bottom: bottom.into(), + x: x.into(), + y: y.into(), + update, + } } /// Options for [`use_element_bounding_with_options`]. #[derive(DefaultBuilder)] -pub struct UseElementBoundingOptions {} +pub struct UseElementBoundingOptions { + /// Reset values to 0 on component disposal + /// + /// Default: `true` + pub reset: bool, + + /// Listen to window resize event + /// + /// Default: `true` + pub window_resize: bool, + + /// Listen to window scroll event + /// + /// Default: `true` + pub window_scroll: bool, + + /// Immediately call update + /// + /// Default: `true` + pub immediate: bool, +} impl Default for UseElementBoundingOptions { fn default() -> Self { - Self {} + Self { + reset: true, + window_resize: true, + window_scroll: true, + immediate: true, + } } } /// Return type of [`use_element_bounding`]. -pub struct UseElementBoundingReturn {} \ No newline at end of file +pub struct UseElementBoundingReturn +where + F: Fn() + Clone, +{ + /// Reactive version of [`BoudingClientRect.height`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/height) + pub height: Signal, + /// Reactive version of [`BoudingClientRect.width`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/width) + pub width: Signal, + /// Reactive version of [`BoudingClientRect.left`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/left) + pub left: Signal, + /// Reactive version of [`BoudingClientRect.right`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/right) + pub right: Signal, + /// Reactive version of [`BoudingClientRect.top`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/top) + pub top: Signal, + /// Reactive version of [`BoudingClientRect.bottom`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/bottom) + pub bottom: Signal, + /// Reactive version of [`BoudingClientRect.x`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/x) + pub x: Signal, + /// Reactive version of [`BoudingClientRect.y`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/y) + pub y: Signal, + /// Function to re-evaluate `get_bounding_client_rect()` and update the signals. + pub update: F, +} diff --git a/src/use_element_size.rs b/src/use_element_size.rs index bc6a226..5d881e1 100644 --- a/src/use_element_size.rs +++ b/src/use_element_size.rs @@ -49,8 +49,7 @@ cfg_if! { if #[cfg(not(feature = "ssr"))] { /// - [`use_resize_observer`] pub fn use_element_size(target: El) -> UseElementSizeReturn where - El: Clone, - El: Into>, + El: Into> + Clone, T: Into + Clone + 'static, { use_element_size_with_options(target, UseElementSizeOptions::default()) @@ -63,8 +62,7 @@ pub fn use_element_size_with_options( options: UseElementSizeOptions, ) -> UseElementSizeReturn where - El: Clone, - El: Into>, + El: Into> + Clone, T: Into + Clone + 'static, { let UseElementSizeOptions { box_, initial_size } = options;