diff --git a/packages/leptos/Cargo.toml b/packages/leptos/Cargo.toml index 9560810..f318da0 100644 --- a/packages/leptos/Cargo.toml +++ b/packages/leptos/Cargo.toml @@ -12,6 +12,7 @@ version.workspace = true cfg-if = "1.0.0" floating-ui-dom = { path = "../dom", version = "0.0.4" } leptos = { version = "0.6.9", features = ["nightly"] } +paste = "1.0.0" web-sys.workspace = true [dev-dependencies] diff --git a/packages/leptos/src/arrow.rs b/packages/leptos/src/arrow.rs index 8e399bc..4037733 100644 --- a/packages/leptos/src/arrow.rs +++ b/packages/leptos/src/arrow.rs @@ -1,42 +1,47 @@ -use std::ops::Deref; +use std::marker::PhantomData; use floating_ui_dom::{ Arrow as CoreArrow, ArrowOptions as CoreArrowOptions, Middleware, MiddlewareReturn, MiddlewareState, Padding, ARROW_NAME, }; -use leptos::{html::ElementDescriptor, NodeRef}; +use leptos::html::ElementDescriptor; + +use crate::node_ref::NodeRefAsElement; /// Options for [`Arrow`]. #[derive(Clone)] -pub struct ArrowOptions +pub struct ArrowOptions where - Descriptor: ElementDescriptor + Deref + Clone + 'static, - Element: Deref, + Ref: NodeRefAsElement + Copy + 'static, + RefEl: ElementDescriptor + Clone + 'static, { /// The arrow element to be positioned. - pub element: NodeRef, + pub element: Ref, /// The padding between the arrow element and the floating element edges. /// Useful when the floating element has rounded corners. /// /// Defaults to `0` on all sides. pub padding: Option, + + phantom: PhantomData, } -impl ArrowOptions +impl ArrowOptions where - Descriptor: ElementDescriptor + Deref + Clone + 'static, - Element: Deref, + Ref: NodeRefAsElement + Copy + 'static, + RefEl: ElementDescriptor + Clone + 'static, { - pub fn new(element: NodeRef) -> Self { + pub fn new(element: Ref) -> Self { ArrowOptions { element, padding: None, + phantom: PhantomData, } } /// Set `element` option. - pub fn element(mut self, value: NodeRef) -> Self { + pub fn element(mut self, value: Ref) -> Self { self.element = value; self } @@ -52,29 +57,28 @@ where /// /// See for the original documentation. #[derive(Clone)] -pub struct Arrow +pub struct Arrow where - Descriptor: ElementDescriptor + Deref + Clone + 'static, - Element: Deref + Clone, + Ref: NodeRefAsElement + Copy + 'static, + RefEl: ElementDescriptor + Clone + 'static, { - options: ArrowOptions, + options: ArrowOptions, } -impl Arrow +impl Arrow where - Descriptor: ElementDescriptor + Deref + Clone + 'static, - Element: Deref + Clone, + Ref: NodeRefAsElement + Copy + 'static, + RefEl: ElementDescriptor + Clone + 'static, { - pub fn new(options: ArrowOptions) -> Self { + pub fn new(options: ArrowOptions) -> Self { Arrow { options } } } -impl Middleware - for Arrow +impl Middleware for Arrow where - Descriptor: ElementDescriptor + Deref + Clone + 'static, - Element: Deref + Clone, + Ref: NodeRefAsElement + Copy + 'static, + RefEl: ElementDescriptor + Clone + 'static, { fn name(&self) -> &'static str { ARROW_NAME @@ -84,7 +88,7 @@ where &self, state: MiddlewareState, ) -> MiddlewareReturn { - let element = self.options.element.get_untracked(); + let element = self.options.element.get_untracked_as_element(); if let Some(element) = element { let element: &web_sys::Element = &element; diff --git a/packages/leptos/src/lib.rs b/packages/leptos/src/lib.rs index e992827..8631370 100644 --- a/packages/leptos/src/lib.rs +++ b/packages/leptos/src/lib.rs @@ -1,4 +1,5 @@ mod arrow; +mod node_ref; mod types; mod use_floating; mod utils; diff --git a/packages/leptos/src/node_ref.rs b/packages/leptos/src/node_ref.rs new file mode 100644 index 0000000..81c2487 --- /dev/null +++ b/packages/leptos/src/node_ref.rs @@ -0,0 +1,251 @@ +use std::ops::Deref; + +use leptos::{html::ElementDescriptor, HtmlElement, NodeRef}; +use web_sys::Element; + +pub trait NodeRefAsElement { + fn get_as_element(&self) -> Option; + + fn get_untracked_as_element(&self) -> Option; + + fn on_load(self, f: F) + where + F: FnOnce(HtmlElement) + 'static; +} + +impl NodeRefAsElement for NodeRef { + fn get_as_element(&self) -> Option { + self.get().map(|html_element| { + let element: &web_sys::Element = html_element.deref(); + element.clone() + }) + } + + fn get_untracked_as_element(&self) -> Option { + self.get_untracked().map(|html_element| { + let element: &web_sys::Element = html_element.deref(); + element.clone() + }) + } + + fn on_load(self, f: F) + where + F: FnOnce(HtmlElement) + 'static, + { + self.on_load(f) + } +} + +macro_rules! generate_html_tags { + ($( + $tag:ty + ),* $(,)?) => { + paste::paste! { + $( + impl NodeRefAsElement]> for NodeRef]> { + fn get_as_element(&self) -> Option { + self.get().map(|html_element| { + let element: &web_sys::Element = html_element.deref(); + element.clone() + }) + } + + fn get_untracked_as_element(&self) -> Option { + self.get_untracked().map(|html_element| { + let element: &web_sys::Element = html_element.deref(); + element.clone() + }) + } + + fn on_load(self, f: F) + where + F: FnOnce(HtmlElement]>) + 'static, + { + self.on_load(f) + } + } + )* + } + } +} + +macro_rules! generate_math_tags { + ($( + $tag:ty + ),* $(,)?) => { + paste::paste! { + $( + impl NodeRefAsElement]> for NodeRef]> { + fn get_as_element(&self) -> Option { + self.get().map(|html_element| { + let element: &web_sys::Element = html_element.deref(); + element.clone() + }) + } + + fn get_untracked_as_element(&self) -> Option { + self.get_untracked().map(|html_element| { + let element: &web_sys::Element = html_element.deref(); + element.clone() + }) + } + + fn on_load(self, f: F) + where + F: FnOnce(HtmlElement]>) + 'static, + { + self.on_load(f) + } + } + )* + } + } +} + +macro_rules! generate_svg_tags { + ($( + $tag:ty + ),* $(,)?) => { + paste::paste! { + $( + impl NodeRefAsElement]> for NodeRef]> { + fn get_as_element(&self) -> Option { + self.get().map(|html_element| { + let element: &web_sys::Element = html_element.deref(); + element.clone() + }) + } + + fn get_untracked_as_element(&self) -> Option { + self.get_untracked().map(|html_element| { + let element: &web_sys::Element = html_element.deref(); + element.clone() + }) + } + + fn on_load(self, f: F) + where + F: FnOnce(HtmlElement]>) + 'static, + { + self.on_load(f) + } + } + )* + } + } +} + +generate_html_tags![ + Html, Base, Head, Link, Meta, Style, Title, Body, Address, Article, Aside, Footer, Header, + Hgroup, H1, H2, H3, H4, H5, H6, Main, Nav, Section, Blockquote, Dd, Div, Dl, Dt, Figcaption, + Figure, Hr, Li, Ol, P, Pre, Ul, A, Abbr, B, Bdi, Bdo, Br, Cite, Code, Data, Dfn, Em, I, Kbd, + Mark, Q, Rp, Rt, Ruby, S, Samp, Small, Span, Strong, Sub, Sup, Time, U, Var, Wbr, Area, Audio, + Img, Map, Track, Video, Embed, Iframe, Object, Param, Picture, Portal, Source, Svg, Math, + Canvas, Noscript, Script, Del, Ins, Caption, Col, Colgroup, Table, Tbody, Td, Tfoot, Th, Thead, + Tr, Button, Datalist, Fieldset, Form, Input, Label, Legend, Meter, Optgroup, Option_, Output, + Progress, Select, Textarea, Details, Dialog, Menu, Summary, Slot, Template, +]; + +generate_math_tags![ + Math, + Mi, + Mn, + Mo, + Ms, + Mspace, + Mtext, + Menclose, + Merror, + Mfenced, + Mfrac, + Mpadded, + Mphantom, + Mroot, + Mrow, + Msqrt, + Mstyle, + Mmultiscripts, + Mover, + Mprescripts, + Msub, + Msubsup, + Msup, + Munder, + Munderover, + Mtable, + Mtd, + Mtr, + Maction, + Annotation, + AnnotationXml, + Semantics, +]; + +generate_svg_tags![ + A, + Animate, + AnimateMotion, + AnimateTransform, + Circle, + ClipPath, + Defs, + Desc, + Discard, + Ellipse, + FeBlend, + FeColorMatrix, + FeComponentTransfer, + FeComposite, + FeConvolveMatrix, + FeDiffuseLighting, + FeDisplacementMap, + FeDistantLight, + FeDropShadow, + FeFlood, + FeFuncA, + FeFuncB, + FeFuncG, + FeFuncR, + FeGaussianBlur, + FeImage, + FeMerge, + FeMergeNode, + FeMorphology, + FeOffset, + FePointLight, + FeSpecularLighting, + FeSpotLight, + FeTile, + FeTurbulence, + Filter, + ForeignObject, + G, + Hatch, + Hatchpath, + Image, + Line, + LinearGradient, + Marker, + Mask, + Metadata, + Mpath, + Path, + Pattern, + Polygon, + Polyline, + RadialGradient, + Rect, + Script, + Set, + Stop, + Style, + Svg, + Switch, + Symbol, + Text, + TextPath, + Title, + Tspan, + Use, + View, +]; diff --git a/packages/leptos/src/use_floating.rs b/packages/leptos/src/use_floating.rs index 39a42a5..f1ce2d6 100644 --- a/packages/leptos/src/use_floating.rs +++ b/packages/leptos/src/use_floating.rs @@ -1,31 +1,31 @@ -use std::{cell::RefCell, ops::Deref, rc::Rc}; +use std::{cell::RefCell, rc::Rc}; use floating_ui_dom::{ compute_position, ComputePositionConfig, MiddlewareData, Placement, Strategy, }; use leptos::{ create_effect, create_memo, create_rw_signal, create_signal, html::ElementDescriptor, - on_cleanup, watch, NodeRef, SignalGet, SignalGetUntracked, SignalUpdate, + on_cleanup, watch, SignalGet, SignalGetUntracked, SignalUpdate, }; use crate::{ + node_ref::NodeRefAsElement, types::{FloatingStyles, UseFloatingOptions, UseFloatingReturn}, utils::{get_dpr::get_dpr, round_by_dpr::round_by_dpr}, WhileElementsMountedCleanupFn, }; /// Computes the `x` and `y` coordinates that will place the floating element next to a reference element. -pub fn use_floating( - reference: NodeRef, - floating: NodeRef, +pub fn use_floating< + Reference: NodeRefAsElement + Copy + 'static, + ReferenceEl: ElementDescriptor + Clone + 'static, + Floating: NodeRefAsElement + Copy + 'static, + FloatingEl: ElementDescriptor + Clone + 'static, +>( + reference: Reference, + floating: Floating, options: UseFloatingOptions, -) -> UseFloatingReturn -where - Reference: ElementDescriptor + Deref + Clone + 'static, - ReferenceEl: Deref, - Floating: ElementDescriptor + Deref + Clone + 'static, - FloatingEl: Deref, -{ +) -> UseFloatingReturn { let open_option = move || options.open.get().unwrap_or(true); let placement_option_untracked = move || { options @@ -58,7 +58,7 @@ where will_change: None, }; - if let Some(floating_element) = floating.get() { + if let Some(floating_element) = floating.get_as_element() { let x_val = round_by_dpr(&floating_element, x()); let y_val = round_by_dpr(&floating_element, y()); @@ -84,8 +84,8 @@ where }); let update = move || { - if let Some(reference_element) = reference.get_untracked() { - if let Some(floating_element) = floating.get_untracked() { + if let Some(reference_element) = reference.get_untracked_as_element() { + if let Some(floating_element) = floating.get_untracked_as_element() { let config = ComputePositionConfig { placement: Some(placement_option_untracked()), strategy: Some(strategy_option_untracked()), @@ -124,8 +124,8 @@ where attach_cleanup_rc(); if let Some(while_elements_mounted) = options.while_elements_mounted.get() { - if let Some(reference_element) = reference.get() { - if let Some(floating_element) = floating.get() { + if let Some(reference_element) = reference.get_as_element() { + if let Some(floating_element) = floating.get_as_element() { attach_while_elements_mounted_cleanup.replace(Some(while_elements_mounted( &reference_element, &floating_element, diff --git a/packages/leptos/tests/visual/src/spec/arrow.rs b/packages/leptos/tests/visual/src/spec/arrow.rs index 64ab24f..aaa8aaa 100644 --- a/packages/leptos/tests/visual/src/spec/arrow.rs +++ b/packages/leptos/tests/visual/src/spec/arrow.rs @@ -4,7 +4,10 @@ use floating_ui_leptos::{ OffsetOptions, Padding, Placement, Shift, ShiftOptions, Side, UseFloatingOptions, UseFloatingReturn, ARROW_NAME, }; -use leptos::{html::Div, *}; +use leptos::{ + html::{AnyElement, Div}, + *, +}; use crate::utils::{ all_placements::ALL_PLACEMENTS, @@ -15,8 +18,7 @@ use crate::utils::{ pub fn Arrow() -> impl IntoView { let reference_ref = create_node_ref::
(); let floating_ref = create_node_ref::
(); - let arrow_div_ref = create_node_ref::
(); - // let arrow_svg_ref = create_node_ref::(); + let arrow_ref = create_node_ref::(); let (placement, set_placement) = create_signal(Placement::Bottom); let (padding, set_padding) = create_signal(0); @@ -51,7 +53,7 @@ pub fn Arrow() -> impl IntoView { DetectOverflowOptions::default().padding(Padding::All(10.0)), ))), Box::new(Arrow::new( - ArrowOptions::new(arrow_div_ref).padding(Padding::All(padding() as f64)), + ArrowOptions::new(arrow_ref).padding(Padding::All(padding() as f64)), )), ]); @@ -115,47 +117,64 @@ pub fn Arrow() -> impl IntoView { false => "Floating".into() }} -
"-15px".into(), - _ => match arrow_y() { - Some(arrow_y) => format!("{}px", arrow_y), - None => "".into() - } - } - style:right=move || match static_side() { - Side::Right => "-15px", - _ => "" - } - style:bottom=move || match static_side() { - Side::Bottom => "-15px", - _ => "" - } - style:left=move || match static_side() { - Side::Left => "-15px".into(), - _ => match arrow_x() { - Some(arrow_x) => format!("{}px", arrow_x), - None => "".into() - } - } - /> - - // TODO: replace this with Show - // match svg() { - // // TODO: copy attributes to SVG - // true => view!{ - // - // }.into_any(), - // false => view! { - - // }.into_any() - // } + {move || match svg() { + true => view!{ + "-15px".into(), + _ => match arrow_y() { + Some(arrow_y) => format!("{}px", arrow_y), + None => "".into() + } + } + style:right=move || match static_side() { + Side::Right => "-15px", + _ => "" + } + style:bottom=move || match static_side() { + Side::Bottom => "-15px", + _ => "" + } + style:left=move || match static_side() { + Side::Left => "-15px".into(), + _ => match arrow_x() { + Some(arrow_x) => format!("{}px", arrow_x), + None => "".into() + } + } + /> + }.into_any().node_ref(arrow_ref), + false => view!{ +
"-15px".into(), + _ => match arrow_y() { + Some(arrow_y) => format!("{}px", arrow_y), + None => "".into() + } + } + style:right=move || match static_side() { + Side::Right => "-15px", + _ => "" + } + style:bottom=move || match static_side() { + Side::Bottom => "-15px", + _ => "" + } + style:left=move || match static_side() { + Side::Left => "-15px".into(), + _ => match arrow_x() { + Some(arrow_x) => format!("{}px", arrow_x), + None => "".into() + } + } + /> + }.into_any().node_ref(arrow_ref) + }}