├── .prettierignore ├── book ├── theme │ ├── theme.css │ ├── theme.js │ ├── tabs.css │ ├── trunk.css │ ├── trunk.js │ └── tabs.js ├── src │ ├── frameworks │ │ ├── README.md │ │ ├── yew.md │ │ ├── dioxus.md │ │ └── dom.md │ ├── detect-overflow.md │ ├── middleware │ │ ├── flip.md │ │ ├── auto-placement.md │ │ ├── hide.md │ │ ├── inline.md │ │ ├── arrow.md │ │ └── size.md │ ├── examples.md │ ├── SUMMARY.md │ ├── introduction.md │ └── virtual-elements.md └── book.toml ├── packages ├── yew │ ├── src │ │ ├── utils.rs │ │ ├── utils │ │ │ ├── get_dpr.rs │ │ │ └── round_by_dpr.rs │ │ ├── use_auto_update.rs │ │ ├── lib.rs │ │ └── arrow.rs │ ├── tests │ │ ├── visual │ │ │ ├── src │ │ │ │ ├── spec.rs │ │ │ │ ├── utils.rs │ │ │ │ ├── utils │ │ │ │ │ ├── new.rs │ │ │ │ │ ├── all_placements.rs │ │ │ │ │ └── use_size.rs │ │ │ │ └── main.rs │ │ │ ├── index.html │ │ │ ├── Cargo.toml │ │ │ └── index.css │ │ └── README.md │ ├── example │ │ ├── src │ │ │ ├── main.rs │ │ │ └── app.rs │ │ ├── README.md │ │ ├── Cargo.toml │ │ └── index.html │ ├── Cargo.toml │ └── README.md ├── dioxus │ ├── src │ │ ├── utils.rs │ │ ├── utils │ │ │ ├── get_dpr.rs │ │ │ └── round_by_dpr.rs │ │ ├── use_auto_update.rs │ │ ├── lib.rs │ │ └── arrow.rs │ ├── example │ │ ├── src │ │ │ ├── main.rs │ │ │ └── app.rs │ │ ├── Cargo.toml │ │ └── index.html │ ├── tests │ │ └── README.md │ ├── Cargo.toml │ └── README.md ├── leptos │ ├── src │ │ ├── utils.rs │ │ ├── utils │ │ │ ├── get_dpr.rs │ │ │ └── round_by_dpr.rs │ │ ├── lib.rs │ │ └── arrow.rs │ ├── tests │ │ ├── visual │ │ │ ├── src │ │ │ │ ├── utils.rs │ │ │ │ ├── utils │ │ │ │ │ ├── new.rs │ │ │ │ │ ├── all_placements.rs │ │ │ │ │ ├── use_size.rs │ │ │ │ │ └── use_resize.rs │ │ │ │ ├── spec.rs │ │ │ │ ├── main.rs │ │ │ │ ├── spec │ │ │ │ │ ├── virtual_element.rs │ │ │ │ │ ├── placement.rs │ │ │ │ │ ├── scrollbars.rs │ │ │ │ │ ├── containing_block.rs │ │ │ │ │ ├── table.rs │ │ │ │ │ └── border.rs │ │ │ │ └── app.rs │ │ │ ├── index.html │ │ │ ├── Cargo.toml │ │ │ └── index.css │ │ └── README.md │ ├── example │ │ ├── src │ │ │ ├── main.rs │ │ │ └── app.rs │ │ ├── README.md │ │ ├── Cargo.toml │ │ └── index.html │ ├── Cargo.toml │ └── README.md ├── dom │ ├── src │ │ ├── utils │ │ │ ├── rects_are_equal.rs │ │ │ ├── is_static_positioned.rs │ │ │ ├── get_html_offset.rs │ │ │ ├── get_window_scroll_bar_x.rs │ │ │ ├── get_visual_offsets.rs │ │ │ ├── get_document_rect.rs │ │ │ ├── get_css_dimensions.rs │ │ │ ├── get_viewport_rect.rs │ │ │ ├── get_rect_relative_to_offset_parent.rs │ │ │ └── get_bounding_client_rect.rs │ │ ├── platform │ │ │ ├── is_rtl.rs │ │ │ ├── get_client_length.rs │ │ │ ├── get_dimensions.rs │ │ │ ├── get_client_rects.rs │ │ │ ├── get_element_rects.rs │ │ │ ├── get_scale.rs │ │ │ ├── convert_offset_parent_relative_rect_to_viewport_relative_rect.rs │ │ │ └── get_offset_parent.rs │ │ ├── utils.rs │ │ ├── types.rs │ │ ├── platform.rs │ │ ├── lib.rs │ │ └── middleware.rs │ ├── example │ │ ├── README.md │ │ ├── Cargo.toml │ │ └── index.html │ ├── Cargo.toml │ └── README.md ├── core │ ├── src │ │ ├── middleware.rs │ │ ├── lib.rs │ │ └── test_utils.rs │ ├── Cargo.toml │ └── README.md └── utils │ ├── Cargo.toml │ └── README.md ├── .github ├── FUNDING.yml ├── renovate.json └── workflows │ ├── upstream.yml │ ├── ci.yml │ ├── release.yml │ └── website.yml ├── book-examples ├── src │ ├── utils.rs │ ├── components.rs │ ├── positioning.rs │ ├── main.rs │ ├── utils │ │ └── rem_to_px.rs │ ├── components │ │ ├── reference.rs │ │ ├── grid_item.rs │ │ ├── floating.rs │ │ └── chrome.rs │ ├── app.rs │ └── positioning │ │ ├── size.rs │ │ ├── flip.rs │ │ └── shift.rs ├── style │ └── tailwind.css ├── Trunk.toml ├── index.html ├── main.js └── Cargo.toml ├── upstream.toml ├── .prettierrc.json ├── package.json ├── scripts ├── Cargo.toml └── src │ └── lib.rs ├── deny.toml ├── LICENSE.md ├── .pre-commit-config.yaml ├── Cargo.toml ├── README.md └── .gitignore /.prettierignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /book/theme/theme.css: -------------------------------------------------------------------------------- 1 | table { 2 | margin: unset; 3 | } 4 | -------------------------------------------------------------------------------- /packages/yew/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub mod get_dpr; 2 | pub mod round_by_dpr; 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: RustForWeb 2 | open_collective: rustforweb 3 | -------------------------------------------------------------------------------- /book-examples/src/utils.rs: -------------------------------------------------------------------------------- 1 | mod rem_to_px; 2 | 3 | pub use rem_to_px::*; 4 | -------------------------------------------------------------------------------- /packages/dioxus/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub mod get_dpr; 2 | pub mod round_by_dpr; 3 | -------------------------------------------------------------------------------- /packages/leptos/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub mod get_dpr; 2 | pub mod round_by_dpr; 3 | -------------------------------------------------------------------------------- /book-examples/style/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /packages/yew/tests/visual/src/spec.rs: -------------------------------------------------------------------------------- 1 | pub mod arrow; 2 | pub mod placement; 3 | pub mod relative; 4 | -------------------------------------------------------------------------------- /upstream.toml: -------------------------------------------------------------------------------- 1 | [releases] 2 | core = "1.7.3" 3 | dom = "1.7.4" 4 | utils = "0.2.10" 5 | vue = "1.1.9" 6 | -------------------------------------------------------------------------------- /packages/yew/tests/visual/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub mod all_placements; 2 | pub mod new; 3 | pub mod use_scroll; 4 | pub mod use_size; 5 | -------------------------------------------------------------------------------- /packages/leptos/tests/visual/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub mod all_placements; 2 | pub mod new; 3 | pub mod use_resize; 4 | pub mod use_scroll; 5 | pub mod use_size; 6 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>RustForWeb/.github:renovate-config"] 4 | } 5 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-tailwindcss"], 3 | "bracketSpacing": false, 4 | "printWidth": 120, 5 | "singleQuote": true, 6 | "tabWidth": 4 7 | } 8 | -------------------------------------------------------------------------------- /book-examples/Trunk.toml: -------------------------------------------------------------------------------- 1 | [[hooks]] 2 | stage = "pre_build" 3 | command = "sh" 4 | command_arguments = [ 5 | "-c", 6 | "npx tailwindcss -i style/tailwind.css -o style/tailwind.output.css", 7 | ] 8 | -------------------------------------------------------------------------------- /packages/yew/src/utils/get_dpr.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_dom::dom::get_window; 2 | use web_sys::Node; 3 | 4 | pub fn get_dpr(element: &Node) -> f64 { 5 | get_window(Some(element)).device_pixel_ratio() 6 | } 7 | -------------------------------------------------------------------------------- /book-examples/src/components.rs: -------------------------------------------------------------------------------- 1 | mod chrome; 2 | mod floating; 3 | mod grid_item; 4 | mod reference; 5 | 6 | pub use chrome::*; 7 | pub use floating::*; 8 | pub use grid_item::*; 9 | pub use reference::*; 10 | -------------------------------------------------------------------------------- /packages/dioxus/src/utils/get_dpr.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_dom::dom::get_window; 2 | use web_sys::Element; 3 | 4 | pub fn get_dpr(element: &Element) -> f64 { 5 | get_window(Some(element)).device_pixel_ratio() 6 | } 7 | -------------------------------------------------------------------------------- /packages/leptos/src/utils/get_dpr.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_dom::dom::get_window; 2 | use web_sys::Element; 3 | 4 | pub fn get_dpr(element: &Element) -> f64 { 5 | get_window(Some(element)).device_pixel_ratio() 6 | } 7 | -------------------------------------------------------------------------------- /book-examples/src/positioning.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "flip")] 2 | pub mod flip; 3 | #[cfg(feature = "placement")] 4 | pub mod placement; 5 | #[cfg(feature = "shift")] 6 | pub mod shift; 7 | #[cfg(feature = "size")] 8 | pub mod size; 9 | -------------------------------------------------------------------------------- /book/src/frameworks/README.md: -------------------------------------------------------------------------------- 1 | # Frameworks 2 | 3 | Rust Floating UI is available for the following frameworks: 4 | 5 | - [DOM (`web-sys`)](./dom.md) 6 | - [Dioxus](./dioxus.md) 7 | - [Leptos](./leptos.md) 8 | - [Yew](./yew.md) 9 | -------------------------------------------------------------------------------- /packages/yew/src/utils/round_by_dpr.rs: -------------------------------------------------------------------------------- 1 | use web_sys::Node; 2 | 3 | use crate::utils::get_dpr::get_dpr; 4 | 5 | pub fn round_by_dpr(element: &Node, value: f64) -> f64 { 6 | let dpr = get_dpr(element); 7 | (value * dpr).round() / dpr 8 | } 9 | -------------------------------------------------------------------------------- /packages/dom/src/utils/rects_are_equal.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::ClientRectObject; 2 | 3 | pub fn rects_are_equal(a: &ClientRectObject, b: &ClientRectObject) -> bool { 4 | a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height 5 | } 6 | -------------------------------------------------------------------------------- /packages/dioxus/src/utils/round_by_dpr.rs: -------------------------------------------------------------------------------- 1 | use web_sys::Element; 2 | 3 | use crate::utils::get_dpr::get_dpr; 4 | 5 | pub fn round_by_dpr(element: &Element, value: f64) -> f64 { 6 | let dpr = get_dpr(element); 7 | (value * dpr).round() / dpr 8 | } 9 | -------------------------------------------------------------------------------- /packages/leptos/src/utils/round_by_dpr.rs: -------------------------------------------------------------------------------- 1 | use web_sys::Element; 2 | 3 | use crate::utils::get_dpr::get_dpr; 4 | 5 | pub fn round_by_dpr(element: &Element, value: f64) -> f64 { 6 | let dpr = get_dpr(element); 7 | (value * dpr).round() / dpr 8 | } 9 | -------------------------------------------------------------------------------- /packages/leptos/tests/visual/src/utils/new.rs: -------------------------------------------------------------------------------- 1 | use leptos::prelude::*; 2 | 3 | #[component] 4 | pub fn New() -> impl IntoView { 5 | view! { 6 |

New

7 |

"This route lets you work on new features! Have fun :-)"

8 | } 9 | } 10 | -------------------------------------------------------------------------------- /book/src/detect-overflow.md: -------------------------------------------------------------------------------- 1 | # Detect Overflow 2 | 3 | Detects when the floating or reference element is overflowing a clipping container or custom boundary. 4 | 5 | TODO 6 | 7 | ## See Also 8 | 9 | - [Floating UI documentation](https://floating-ui.com/docs/detectOverflow) 10 | -------------------------------------------------------------------------------- /book-examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/dioxus/example/src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | 3 | use crate::app::App; 4 | 5 | pub fn main() { 6 | console_log::init_with_level(log::Level::Debug).expect("Console logger should be available"); 7 | console_error_panic_hook::set_once(); 8 | 9 | dioxus::launch(App); 10 | } 11 | -------------------------------------------------------------------------------- /packages/dom/src/platform/is_rtl.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::dom::get_computed_style; 2 | use web_sys::Element; 3 | 4 | pub fn is_rtl(element: &Element) -> bool { 5 | get_computed_style(element) 6 | .get_property_value("direction") 7 | .unwrap_or_default() 8 | == "rtl" 9 | } 10 | -------------------------------------------------------------------------------- /packages/yew/example/src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | 3 | use crate::app::App; 4 | 5 | pub fn main() { 6 | console_log::init_with_level(log::Level::Debug).expect("Console logger should be available"); 7 | console_error_panic_hook::set_once(); 8 | 9 | yew::Renderer::::new().render(); 10 | } 11 | -------------------------------------------------------------------------------- /packages/yew/tests/visual/src/utils/new.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | #[function_component] 4 | pub fn New() -> Html { 5 | html! { 6 | <> 7 |

{"New"}

8 |

{"This route lets you work on new features! Have fun :-)"}

9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/leptos/example/src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | 3 | use leptos::prelude::*; 4 | 5 | use crate::app::App; 6 | 7 | pub fn main() { 8 | console_log::init_with_level(log::Level::Debug).expect("Console logger should be available"); 9 | console_error_panic_hook::set_once(); 10 | 11 | mount_to_body(App); 12 | } 13 | -------------------------------------------------------------------------------- /packages/yew/tests/README.md: -------------------------------------------------------------------------------- 1 | # Floating UI Yew Tests 2 | 3 | Implementation of [Floating UI tests](https://github.com/floating-ui/floating-ui/tree/master/packages/dom/test). 4 | 5 | ## Unit tests 6 | 7 | _TODO_ 8 | 9 | ## Visual tests 10 | 11 | The visual tests are validated against the Playwright tests from Floating UI. 12 | -------------------------------------------------------------------------------- /book-examples/src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod components; 3 | mod positioning; 4 | mod utils; 5 | 6 | use leptos::prelude::*; 7 | 8 | use crate::app::App; 9 | 10 | pub fn main() { 11 | _ = console_log::init_with_level(log::Level::Debug); 12 | console_error_panic_hook::set_once(); 13 | 14 | mount_to_body(App); 15 | } 16 | -------------------------------------------------------------------------------- /packages/dioxus/tests/README.md: -------------------------------------------------------------------------------- 1 | # Floating UI Dioxus Tests 2 | 3 | Implementation of [Floating UI tests](https://github.com/floating-ui/floating-ui/tree/master/packages/dom/test). 4 | 5 | ## Unit tests 6 | 7 | _TODO_ 8 | 9 | ## Visual tests 10 | 11 | The visual tests are validated against the Playwright tests from Floating UI. 12 | -------------------------------------------------------------------------------- /packages/leptos/tests/README.md: -------------------------------------------------------------------------------- 1 | # Floating UI Leptos Tests 2 | 3 | Implementation of [Floating UI tests](https://github.com/floating-ui/floating-ui/tree/master/packages/dom/test). 4 | 5 | ## Unit tests 6 | 7 | _TODO_ 8 | 9 | ## Visual tests 10 | 11 | The visual tests are validated against the Playwright tests from Floating UI. 12 | -------------------------------------------------------------------------------- /packages/dom/src/platform/get_client_length.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::Length; 2 | use web_sys::Element; 3 | 4 | pub fn get_client_length(element: &Element, length: Length) -> f64 { 5 | match length { 6 | Length::Width => element.client_width() as f64, 7 | Length::Height => element.client_height() as f64, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /book/src/middleware/flip.md: -------------------------------------------------------------------------------- 1 | # Flip 2 | 3 | Changes the placement of the floating element to keep it in view. 4 | 5 | This prevents the floating element from overflowing along its side axis by flipping it to the opposite side by default. 6 | 7 | TODO 8 | 9 | ## See Also 10 | 11 | - [Floating UI documentation](https://floating-ui.com/docs/flip) 12 | -------------------------------------------------------------------------------- /packages/dom/src/platform/get_dimensions.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::Dimensions; 2 | use web_sys::Element; 3 | 4 | use crate::utils::get_css_dimensions::{CssDimensions, get_css_dimensions}; 5 | 6 | pub fn get_dimensions(element: &Element) -> Dimensions { 7 | let CssDimensions { dimensions, .. } = get_css_dimensions(element); 8 | dimensions 9 | } 10 | -------------------------------------------------------------------------------- /packages/dom/src/utils/is_static_positioned.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::dom::get_computed_style; 2 | use web_sys::Element; 3 | 4 | pub fn is_static_positioned(element: &Element) -> bool { 5 | get_computed_style(element) 6 | .get_property_value("position") 7 | .expect("Computed style should have position.") 8 | == "static" 9 | } 10 | -------------------------------------------------------------------------------- /packages/dom/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub mod get_bounding_client_rect; 2 | pub mod get_css_dimensions; 3 | pub mod get_document_rect; 4 | pub mod get_html_offset; 5 | pub mod get_rect_relative_to_offset_parent; 6 | pub mod get_viewport_rect; 7 | pub mod get_visual_offsets; 8 | pub mod get_window_scroll_bar_x; 9 | pub mod is_static_positioned; 10 | pub mod rects_are_equal; 11 | -------------------------------------------------------------------------------- /packages/yew/tests/visual/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rust Floating UI Testing Grounds 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/leptos/tests/visual/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rust Floating UI Testing Grounds 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/dom/example/README.md: -------------------------------------------------------------------------------- 1 | # Floating UI DOM Example 2 | 3 | Implementation of the [Floating UI tutorial](https://floating-ui.com/docs/tutorial) with [Floating UI DOM](../). 4 | 5 | ## Installation 6 | 7 | ```sh 8 | # Install Trunk 9 | cargo install trunk 10 | ``` 11 | 12 | ## Development 13 | 14 | ```sh 15 | # Start development server 16 | trunk serve 17 | ``` 18 | -------------------------------------------------------------------------------- /packages/yew/example/README.md: -------------------------------------------------------------------------------- 1 | # Floating UI Yew Example 2 | 3 | Implementation of the [Floating UI tutorial](https://floating-ui.com/docs/tutorial) with [Floating UI Yew](../). 4 | 5 | ## Installation 6 | 7 | ```sh 8 | # Install Trunk 9 | cargo install trunk 10 | ``` 11 | 12 | ## Development 13 | 14 | ```sh 15 | # Start development server 16 | trunk serve 17 | ``` 18 | -------------------------------------------------------------------------------- /packages/leptos/example/README.md: -------------------------------------------------------------------------------- 1 | # Floating UI Leptos Example 2 | 3 | Implementation of the [Floating UI tutorial](https://floating-ui.com/docs/tutorial) with [Floating UI Leptos](../). 4 | 5 | ## Installation 6 | 7 | ```sh 8 | # Install Trunk 9 | cargo install trunk 10 | ``` 11 | 12 | ## Development 13 | 14 | ```sh 15 | # Start development server 16 | trunk serve 17 | ``` 18 | -------------------------------------------------------------------------------- /book/src/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## Smart Anchor Positioning 4 | 5 | Anchor a floating element next to another element while making sure it stays in view by **avoiding collisions**. This lets you position tooltips, popovers, or dropdowns optimally. 6 | 7 | ```toml,trunk 8 | package = "floating-ui-book" 9 | features = ["arrow", "flip", "placement", "shift", "size", "virtual"] 10 | ``` 11 | -------------------------------------------------------------------------------- /book/src/middleware/auto-placement.md: -------------------------------------------------------------------------------- 1 | # Auto Placement 2 | 3 | Chooses the placement that has the most space available automatically. 4 | 5 | This is useful when you don't know which placement will be best for the floating element, or don't want to have to explicitly specify it. 6 | 7 | TODO 8 | 9 | ## See Also 10 | 11 | - [Floating UI documentation](https://floating-ui.com/docs/autoPlacement) 12 | -------------------------------------------------------------------------------- /book/src/middleware/hide.md: -------------------------------------------------------------------------------- 1 | # Hide 2 | 3 | A data provider that allows you to hide the floating element in applicable situations. 4 | 5 | This is useful for situations where you want to hide the floating element because it appears detached from the reference element (or attached to nothing). 6 | 7 | TODO 8 | 9 | ## See Also 10 | 11 | - [Floating UI documentation](https://floating-ui.com/docs/hide) 12 | -------------------------------------------------------------------------------- /book/theme/theme.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('message', (event) => { 2 | if (!event.data.mdbookTrunk) { 3 | return; 4 | } 5 | 6 | const data = event.data.mdbookTrunk; 7 | const iframe = Array.from(document.getElementsByTagName('iframe')).find( 8 | (iframe) => iframe.contentWindow === event.source 9 | ); 10 | if (iframe) { 11 | iframe.style.height = `${data.height}px`; 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /book/src/middleware/inline.md: -------------------------------------------------------------------------------- 1 | # Inline 2 | 3 | Improves positioning for inline reference elements that span over multiple lines. 4 | 5 | This is useful for reference elements such as hyperlinks or range selections, as the default positioning using `getBoundingClientRect()` may appear “detached” when measuring over the bounding box. 6 | 7 | TODO 8 | 9 | ## See Also 10 | 11 | - [Floating UI documentation](https://floating-ui.com/docs/inline) 12 | -------------------------------------------------------------------------------- /book/src/middleware/arrow.md: -------------------------------------------------------------------------------- 1 | # Arrow 2 | 3 | Provides positioning data for an arrow element (triangle or caret) inside the floating element, such that it appears to be pointing toward the center of the reference element. 4 | 5 | This is useful to add an additional visual cue to the floating element about which element it is referring to. 6 | 7 | TODO 8 | 9 | ## See Also 10 | 11 | - [Floating UI documentation](https://floating-ui.com/docs/arrow) 12 | -------------------------------------------------------------------------------- /book/src/middleware/size.md: -------------------------------------------------------------------------------- 1 | # Size 2 | 3 | Provides data to change the size of a floating element. 4 | 5 | This is useful to ensure the floating element isn't too big to fit in the viewport (or more specifically, its clipping context), especially when a maximum size isn't specified. It also allows matching the width/height of the reference element. 6 | 7 | TODO 8 | 9 | ## See Also 10 | 11 | - [Floating UI documentation](https://floating-ui.com/docs/size) 12 | -------------------------------------------------------------------------------- /packages/leptos/tests/visual/src/spec.rs: -------------------------------------------------------------------------------- 1 | pub mod arrow; 2 | pub mod auto_placement; 3 | pub mod auto_update; 4 | pub mod border; 5 | pub mod containing_block; 6 | pub mod decimal_size; 7 | pub mod flip; 8 | pub mod hide; 9 | pub mod inline; 10 | pub mod offset; 11 | pub mod placement; 12 | pub mod relative; 13 | pub mod scroll; 14 | pub mod scrollbars; 15 | pub mod shift; 16 | pub mod size; 17 | pub mod table; 18 | pub mod transform; 19 | pub mod virtual_element; 20 | -------------------------------------------------------------------------------- /book-examples/main.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | const resizeObserver = new ResizeObserver(() => { 3 | if (window.top) { 4 | window.top.postMessage({ 5 | mdbookTrunk: { 6 | width: document.body.scrollWidth, 7 | height: document.body.scrollHeight 8 | } 9 | }); 10 | } 11 | }); 12 | resizeObserver.observe(document.body); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/yew/tests/visual/src/utils/all_placements.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_yew::Placement; 2 | 3 | pub const ALL_PLACEMENTS: [Placement; 12] = [ 4 | Placement::TopStart, 5 | Placement::Top, 6 | Placement::TopEnd, 7 | Placement::RightStart, 8 | Placement::Right, 9 | Placement::RightEnd, 10 | Placement::BottomEnd, 11 | Placement::Bottom, 12 | Placement::BottomStart, 13 | Placement::LeftEnd, 14 | Placement::Left, 15 | Placement::LeftStart, 16 | ]; 17 | -------------------------------------------------------------------------------- /packages/leptos/tests/visual/src/utils/all_placements.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_leptos::Placement; 2 | 3 | pub const ALL_PLACEMENTS: [Placement; 12] = [ 4 | Placement::TopStart, 5 | Placement::Top, 6 | Placement::TopEnd, 7 | Placement::RightStart, 8 | Placement::Right, 9 | Placement::RightEnd, 10 | Placement::BottomEnd, 11 | Placement::Bottom, 12 | Placement::BottomStart, 13 | Placement::LeftEnd, 14 | Placement::Left, 15 | Placement::LeftStart, 16 | ]; 17 | -------------------------------------------------------------------------------- /packages/dom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "floating-ui-dom" 3 | description = "Rust port of Floating UI. Floating UI for the web." 4 | homepage = "https://floating-ui.rustforweb.org" 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | floating-ui-core.workspace = true 14 | floating-ui-utils = { workspace = true, features = ["dom"] } 15 | web-sys.workspace = true 16 | -------------------------------------------------------------------------------- /packages/dioxus/example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "floating-ui-dioxus-example" 3 | description = "Example for Floating UI Dioxus." 4 | publish = false 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | console_error_panic_hook.workspace = true 14 | console_log.workspace = true 15 | dioxus.workspace = true 16 | floating-ui-dioxus.workspace = true 17 | log.workspace = true 18 | -------------------------------------------------------------------------------- /packages/yew/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "floating-ui-yew" 3 | description = "Floating UI for Yew." 4 | homepage = "https://floating-ui.rustforweb.org/frameworks/yew.html" 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | floating-ui-dom.workspace = true 14 | web-sys.workspace = true 15 | yew.workspace = true 16 | 17 | [dev-dependencies] 18 | wasm-bindgen-test.workspace = true 19 | -------------------------------------------------------------------------------- /packages/yew/example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "floating-ui-yew-example" 3 | description = "Example for Floating UI Yew." 4 | publish = false 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | console_error_panic_hook.workspace = true 14 | console_log.workspace = true 15 | floating-ui-yew.workspace = true 16 | log.workspace = true 17 | yew = { workspace = true, features = ["csr"] } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rustforweb/floating-ui", 3 | "description": "Rust port of Floating UI.", 4 | "author": "Rust for Web ", 5 | "repository": "github:RustForWeb/floating-ui", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": {}, 9 | "devDependencies": { 10 | "prettier": "^3.3.3", 11 | "prettier-plugin-tailwindcss": "^0.7.0", 12 | "tailwindcss": "^3.4.10" 13 | }, 14 | "engines": { 15 | "node": ">=18" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /book/theme/tabs.css: -------------------------------------------------------------------------------- 1 | .mdbook-tabs { 2 | display: flex; 3 | } 4 | 5 | .mdbook-tab { 6 | background-color: var(--table-alternate-bg); 7 | padding: 0.5rem 1rem; 8 | cursor: pointer; 9 | border: none; 10 | font-size: 1.6rem; 11 | line-height: 1.45em; 12 | } 13 | 14 | .mdbook-tab.active { 15 | background-color: var(--table-header-bg); 16 | font-weight: bold; 17 | } 18 | 19 | .mdbook-tab-content { 20 | padding: 1rem 0rem; 21 | } 22 | 23 | .mdbook-tab-content table { 24 | margin: unset; 25 | } 26 | -------------------------------------------------------------------------------- /packages/dioxus/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "floating-ui-dioxus" 3 | description = "Floating UI for Dioxus." 4 | homepage = "https://floating-ui.rustforweb.org/frameworks/dioxus.html" 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | dioxus = { workspace = true, features = ["web"] } 14 | floating-ui-dom.workspace = true 15 | web-sys.workspace = true 16 | 17 | [dev-dependencies] 18 | wasm-bindgen-test.workspace = true 19 | -------------------------------------------------------------------------------- /packages/dom/src/utils/get_html_offset.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::{Coords, dom::NodeScroll}; 2 | use web_sys::Element; 3 | 4 | use crate::utils::get_window_scroll_bar_x::get_window_scroll_bar_x; 5 | 6 | pub fn get_html_offset(document_element: &Element, scroll: &NodeScroll) -> Coords { 7 | let html_rect = document_element.get_bounding_client_rect(); 8 | let x = html_rect.left() + scroll.scroll_left 9 | - get_window_scroll_bar_x(document_element, Some(&html_rect)); 10 | let y = html_rect.top() + scroll.scroll_top; 11 | 12 | Coords { x, y } 13 | } 14 | -------------------------------------------------------------------------------- /packages/dom/example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "floating-ui-dom-example" 3 | description = "Example for Floating UI DOM." 4 | publish = false 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib"] 14 | 15 | [dependencies] 16 | console_error_panic_hook.workspace = true 17 | console_log.workspace = true 18 | floating-ui-dom.workspace = true 19 | log.workspace = true 20 | wasm-bindgen.workspace = true 21 | web-sys.workspace = true 22 | -------------------------------------------------------------------------------- /packages/leptos/tests/visual/src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod spec; 3 | mod utils; 4 | 5 | use leptos::prelude::*; 6 | use wasm_bindgen::JsCast; 7 | use web_sys::HtmlElement; 8 | 9 | use crate::app::App; 10 | 11 | pub fn main() { 12 | _ = console_log::init_with_level(log::Level::Debug); 13 | console_error_panic_hook::set_once(); 14 | 15 | let owner = mount_to( 16 | document() 17 | .get_element_by_id("root") 18 | .unwrap() 19 | .unchecked_into::(), 20 | App, 21 | ); 22 | owner.forget(); 23 | } 24 | -------------------------------------------------------------------------------- /scripts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripts" 3 | description = "Scripts for Rust Floating UI." 4 | publish = false 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | env_logger = "0.11.3" 14 | log = "0.4.21" 15 | octocrab = "0.49.0" 16 | semver = "1.0.22" 17 | serde.workspace = true 18 | strum = { version = "0.27.0", features = ["derive"] } 19 | tempfile = "3.10.1" 20 | tokio = { version = "1.37.0", features = ["full"] } 21 | toml = "0.9.0" 22 | -------------------------------------------------------------------------------- /packages/core/src/middleware.rs: -------------------------------------------------------------------------------- 1 | //! Middleware implementations for [`compute_position`][`crate::compute_position::compute_position`]. 2 | //! 3 | //! See [the Rust Floating UI book](https://floating-ui.rustforweb.org/middleware/index.html) for more documentation. 4 | 5 | mod arrow; 6 | mod auto_placement; 7 | mod flip; 8 | mod hide; 9 | mod inline; 10 | mod offset; 11 | mod shift; 12 | mod size; 13 | 14 | pub use arrow::*; 15 | pub use auto_placement::*; 16 | pub use flip::*; 17 | pub use hide::*; 18 | pub use inline::*; 19 | pub use offset::*; 20 | pub use shift::*; 21 | pub use size::*; 22 | -------------------------------------------------------------------------------- /book/src/frameworks/yew.md: -------------------------------------------------------------------------------- 1 | # Yew 2 | 3 | This package provides [Yew](https://yew.rs/) bindings for `floating-ui-dom` - a library that provides anchor positioning for a floating element to position it next to a given reference element. 4 | 5 | ## Installation 6 | 7 | ```shell 8 | cargo add floating-ui-yew 9 | ``` 10 | 11 | - [View on crates.io](https://crates.io/crates/floating-ui-yew) 12 | - [View on docs.rs](https://docs.rs/floating-ui-yew/latest/floating_ui_yew/) 13 | - [View source](https://github.com/RustForWeb/floating-ui/tree/main/packages/yew) 14 | 15 | ## Usage 16 | 17 | TODO 18 | -------------------------------------------------------------------------------- /packages/yew/tests/visual/src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod spec; 3 | mod utils; 4 | 5 | use web_sys::window; 6 | 7 | use crate::app::App; 8 | 9 | pub fn main() { 10 | _ = console_log::init_with_level(log::Level::Debug); 11 | console_error_panic_hook::set_once(); 12 | 13 | yew::Renderer::::with_root( 14 | window() 15 | .expect("Window should exist.") 16 | .document() 17 | .expect("Document should exist.") 18 | .get_element_by_id("root") 19 | .expect("Element should exist."), 20 | ) 21 | .render(); 22 | } 23 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Daniëlle Huisman"] 3 | language = "en" 4 | src = "src" 5 | title = "Rust Floating UI" 6 | 7 | [preprocessor.tabs] 8 | 9 | [preprocessor.trunk] 10 | 11 | [output.html] 12 | additional-css = ["theme/tabs.css", "theme/theme.css", "theme/trunk.css"] 13 | additional-js = ["theme/tabs.js", "theme/theme.js", "theme/trunk.js"] 14 | edit-url-template = "https://github.com/RustForWeb/floating-ui/edit/main/book/{path}" 15 | git-repository-url = "https://github.com/RustForWeb/floating-ui" 16 | 17 | [output.trunk] 18 | serve = true 19 | 20 | [rust] 21 | edition = "2024" 22 | -------------------------------------------------------------------------------- /packages/dom/src/platform/get_client_rects.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::ClientRectObject; 2 | 3 | use crate::types::ElementOrVirtual; 4 | 5 | pub fn get_client_rects(element: ElementOrVirtual) -> Vec { 6 | match element { 7 | ElementOrVirtual::Element(element) => { 8 | ClientRectObject::from_dom_rect_list(element.get_client_rects()) 9 | } 10 | ElementOrVirtual::VirtualElement(virtual_element) => virtual_element 11 | .get_client_rects() 12 | .expect("Virtual element must implement `get_client_rects`."), 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/leptos/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "floating-ui-leptos" 3 | description = "Floating UI for Leptos." 4 | homepage = "https://floating-ui.rustforweb.org/frameworks/leptos.html" 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | floating-ui-dom.workspace = true 14 | leptos.workspace = true 15 | leptos-node-ref.workspace = true 16 | send_wrapper.workspace = true 17 | web-sys.workspace = true 18 | 19 | [dev-dependencies] 20 | wasm-bindgen-test.workspace = true 21 | -------------------------------------------------------------------------------- /packages/leptos/example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "floating-ui-leptos-example" 3 | description = "Example for Floating UI Leptos." 4 | publish = false 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | console_error_panic_hook.workspace = true 14 | console_log.workspace = true 15 | floating-ui-leptos.workspace = true 16 | leptos = { workspace = true, features = ["csr"] } 17 | leptos-node-ref.workspace = true 18 | log.workspace = true 19 | send_wrapper.workspace = true 20 | -------------------------------------------------------------------------------- /book/src/frameworks/dioxus.md: -------------------------------------------------------------------------------- 1 | # Dioxus 2 | 3 | This package provides [Dioxus](https://dioxuslabs.com/) bindings for `floating-ui-dom` - a library that provides anchor positioning for a floating element to position it next to a given reference element. 4 | 5 | ## Installation 6 | 7 | ```shell 8 | cargo add floating-ui-dioxus 9 | ``` 10 | 11 | - [View on crates.io](https://crates.io/crates/floating-ui-dioxus) 12 | - [View on docs.rs](https://docs.rs/floating-ui-dioxus/latest/floating_ui_dioxus/) 13 | - [View source](https://github.com/RustForWeb/floating-ui/tree/main/packages/dioxus) 14 | 15 | ## Usage 16 | 17 | TODO 18 | -------------------------------------------------------------------------------- /book/src/frameworks/dom.md: -------------------------------------------------------------------------------- 1 | # DOM 2 | 3 | This package provides [DOM (`web-sys`)](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html) bindings for `floating-ui-core` - a library that provides anchor positioning for a floating element to position it next to a given reference element. 4 | 5 | ## Installation 6 | 7 | ```shell 8 | cargo add floating-ui-dom 9 | ``` 10 | 11 | - [View on crates.io](https://crates.io/crates/floating-ui-dom) 12 | - [View on docs.rs](https://docs.rs/floating-ui-dom/latest/floating_ui_dom/) 13 | - [View source](https://github.com/RustForWeb/floating-ui/tree/main/packages/dom) 14 | 15 | ## Usage 16 | 17 | TODO 18 | -------------------------------------------------------------------------------- /packages/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "floating-ui-core" 3 | description = "Rust port of Floating UI. Positioning library for floating elements: tooltips, popovers, dropdowns, and more." 4 | homepage = "https://floating-ui.rustforweb.org" 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [package.metadata.cargo-machete] 13 | ignored = ["dyn_std"] 14 | 15 | [dependencies] 16 | dyn_derive.workspace = true 17 | dyn_std.workspace = true 18 | floating-ui-utils.workspace = true 19 | serde.workspace = true 20 | serde_json.workspace = true 21 | -------------------------------------------------------------------------------- /packages/yew/tests/visual/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "floating-ui-yew-test-visual" 3 | description = "Visual tests for Floating UI Yew." 4 | publish = false 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | console_error_panic_hook.workspace = true 14 | console_log.workspace = true 15 | convert_case = "0.6.0" 16 | floating-ui-yew.workspace = true 17 | log.workspace = true 18 | wasm-bindgen.workspace = true 19 | web-sys.workspace = true 20 | yew = { workspace = true, features = ["csr"] } 21 | yew-router.workspace = true 22 | -------------------------------------------------------------------------------- /packages/utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "floating-ui-utils" 3 | description = "Rust port of Floating UI. Utilities for Floating UI." 4 | homepage = "https://floating-ui.rustforweb.org" 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [package.metadata.docs.rs] 13 | all-features = true 14 | 15 | [package.metadata.cargo-machete] 16 | ignored = ["dyn_std"] 17 | 18 | [features] 19 | default = [] 20 | dom = ["dep:web-sys"] 21 | 22 | [dependencies] 23 | cfg-if.workspace = true 24 | dyn_derive.workspace = true 25 | dyn_std.workspace = true 26 | serde.workspace = true 27 | web-sys = { workspace = true, optional = true } 28 | -------------------------------------------------------------------------------- /packages/leptos/tests/visual/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "floating-ui-leptos-test-visual" 3 | description = "Visual tests for Floating UI Leptos." 4 | publish = false 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | console_error_panic_hook.workspace = true 14 | console_log.workspace = true 15 | convert_case = "0.6.0" 16 | floating-ui-leptos.workspace = true 17 | leptos = { workspace = true, features = ["csr"] } 18 | leptos-node-ref.workspace = true 19 | leptos_router.workspace = true 20 | log.workspace = true 21 | send_wrapper.workspace = true 22 | wasm-bindgen.workspace = true 23 | web-sys.workspace = true 24 | -------------------------------------------------------------------------------- /packages/dom/src/types.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_core::{Boundary as CoreBoundary, Middleware}; 2 | use floating_ui_utils::{ 3 | DefaultVirtualElement as CoreDefaultVirtualElement, ElementOrVirtual as CoreElementOrVirtual, 4 | OwnedElementOrVirtual as CoreOwnedElementOrVirtual, 5 | }; 6 | use web_sys::{Element, Window}; 7 | 8 | pub type Boundary = CoreBoundary; 9 | 10 | pub type DefaultVirtualElement = CoreDefaultVirtualElement; 11 | pub type ElementOrVirtual<'a> = CoreElementOrVirtual<'a, Element>; 12 | pub type OwnedElementOrVirtual = CoreOwnedElementOrVirtual; 13 | 14 | /// Vector of middleware used in [`ComputePositionConfig`][`crate::ComputePositionConfig`]. 15 | pub type MiddlewareVec = Vec>>; 16 | -------------------------------------------------------------------------------- /book-examples/src/utils/rem_to_px.rs: -------------------------------------------------------------------------------- 1 | use leptos::prelude::*; 2 | 3 | pub fn rem_to_px(value: f64) -> f64 { 4 | document() 5 | .document_element() 6 | .map(|document_element| { 7 | value 8 | * window() 9 | .get_computed_style(&document_element) 10 | .expect("Valid element.") 11 | .expect("Element should have computed style.") 12 | .get_property_value("font-size") 13 | .expect("Computed style should have font size.") 14 | .replace("px", "") 15 | .parse::() 16 | .expect("Font size should be a float.") 17 | }) 18 | .unwrap_or(value * 16.0) 19 | } 20 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [graph] 2 | all-features = true 3 | 4 | [advisories] 5 | ignore = [ 6 | { id = "RUSTSEC-2023-0071", reason = "No safe upgrade is available `rsa`." }, 7 | { id = "RUSTSEC-2024-0370", reason = "No safe upgrade is available `proc-macro-error`." }, 8 | { id = "RUSTSEC-2024-0436", reason = "No maintained version available for `paste`." }, 9 | ] 10 | 11 | [bans] 12 | allow-wildcard-paths = true 13 | multiple-versions = "allow" 14 | wildcards = "deny" 15 | 16 | [licenses] 17 | allow = [ 18 | "Apache-2.0", 19 | "BSD-3-Clause", 20 | "BSL-1.0", 21 | "CC0-1.0", 22 | "ISC", 23 | "MIT", 24 | "Unicode-3.0", 25 | "Zlib", 26 | ] 27 | confidence-threshold = 1.0 28 | 29 | [sources] 30 | unknown-git = "deny" 31 | unknown-registry = "deny" 32 | -------------------------------------------------------------------------------- /packages/yew/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Rust Floating UI Logo 4 | 5 |

6 | 7 |

floating-ui-yew

8 | 9 | This is the library to use Floating UI with Yew. 10 | 11 | [Rust Floating UI](https://github.com/RustForWeb/floating-ui) is a Rust port of [Floating UI](https://floating-ui.com). 12 | 13 | ## Documentation 14 | 15 | See [the Rust Floating UI book](https://floating-ui.rustforweb.org/) for documentation. 16 | 17 | ## Rust for Web 18 | 19 | The Rust Floating UI project is part of [Rust for Web](https://github.com/RustForWeb). 20 | 21 | [Rust for Web](https://github.com/RustForWeb) creates and ports web libraries for Rust. All projects are free and open source. 22 | -------------------------------------------------------------------------------- /packages/core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Rust port of [Floating UI](https://floating-ui.com/). 2 | //! 3 | //! This is the platform-agnostic core of Floating UI, exposing the main [`compute_position`][`crate::compute_position::compute_position()`] function but no platform interface logic. 4 | //! 5 | //! See [the Rust Floating UI book](https://floating-ui.rustforweb.org/) for more documenation. 6 | //! 7 | //! See [@floating-ui/core](https://www.npmjs.com/package/@floating-ui/core) for the original package. 8 | 9 | mod compute_coords_from_placement; 10 | mod compute_position; 11 | mod detect_overflow; 12 | pub mod middleware; 13 | mod types; 14 | 15 | #[cfg(test)] 16 | mod test_utils; 17 | 18 | pub use compute_coords_from_placement::*; 19 | pub use compute_position::*; 20 | pub use detect_overflow::*; 21 | pub use types::*; 22 | -------------------------------------------------------------------------------- /packages/dioxus/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Rust Floating UI Logo 4 | 5 |

6 | 7 |

floating-ui-dioxus

8 | 9 | This is the library to use Floating UI with Dioxus. 10 | 11 | [Rust Floating UI](https://github.com/RustForWeb/floating-ui) is a Rust port of [Floating UI](https://floating-ui.com). 12 | 13 | ## Documentation 14 | 15 | See [the Rust Floating UI book](https://floating-ui.rustforweb.org/) for documentation. 16 | 17 | ## Rust for Web 18 | 19 | The Rust Floating UI project is part of [Rust for Web](https://github.com/RustForWeb). 20 | 21 | [Rust for Web](https://github.com/RustForWeb) creates and ports web libraries for Rust. All projects are free and open source. 22 | -------------------------------------------------------------------------------- /packages/leptos/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Rust Floating UI Logo 4 | 5 |

6 | 7 |

floating-ui-leptos

8 | 9 | This is the library to use Floating UI with Leptos. 10 | 11 | [Rust Floating UI](https://github.com/RustForWeb/floating-ui) is a Rust port of [Floating UI](https://floating-ui.com). 12 | 13 | ## Documentation 14 | 15 | See [the Rust Floating UI book](https://floating-ui.rustforweb.org/) for documentation. 16 | 17 | ## Rust for Web 18 | 19 | The Rust Floating UI project is part of [Rust for Web](https://github.com/RustForWeb). 20 | 21 | [Rust for Web](https://github.com/RustForWeb) creates and ports web libraries for Rust. All projects are free and open source. 22 | -------------------------------------------------------------------------------- /book/theme/trunk.css: -------------------------------------------------------------------------------- 1 | .mdbook-trunk-iframe { 2 | display: block; 3 | width: 100%; 4 | border: 0.1em solid var(--quote-border); 5 | } 6 | 7 | .mdbook-trunk-files-container { 8 | width: 100%; 9 | border: 0.1em solid var(--quote-border); 10 | border-top: 0em; 11 | background-color: var(--quote-border); 12 | } 13 | 14 | .mdbook-trunk-files { 15 | display: flex; 16 | } 17 | 18 | .mdbook-trunk-file { 19 | background-color: var(--table-alternate-bg); 20 | padding: 0.5rem 1rem; 21 | cursor: pointer; 22 | border: none; 23 | font-size: 1.6rem; 24 | line-height: 1.45em; 25 | } 26 | 27 | .mdbook-trunk-file.active { 28 | background-color: var(--table-header-bg); 29 | font-weight: bold; 30 | } 31 | 32 | .mdbook-trunk-file-content > pre { 33 | margin: 0rem; 34 | } 35 | -------------------------------------------------------------------------------- /book-examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "floating-ui-book" 3 | description = "Book examples for Floating UI." 4 | publish = false 5 | 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | default = ["arrow", "flip", "placement", "shift", "size", "virtual"] 14 | arrow = [] 15 | flip = [] 16 | placement = [] 17 | shift = [] 18 | size = [] 19 | virtual = [] 20 | 21 | [dependencies] 22 | console_error_panic_hook.workspace = true 23 | console_log.workspace = true 24 | convert_case = "0.10.0" 25 | floating-ui-leptos.workspace = true 26 | leptos = { workspace = true, features = ["csr"] } 27 | leptos-node-ref.workspace = true 28 | log.workspace = true 29 | send_wrapper.workspace = true 30 | tailwind_fuse = "0.3.1" 31 | -------------------------------------------------------------------------------- /book-examples/src/components/reference.rs: -------------------------------------------------------------------------------- 1 | use leptos::prelude::*; 2 | use leptos_node_ref::AnyNodeRef; 3 | use tailwind_fuse::tw_merge; 4 | 5 | #[component] 6 | pub fn Reference( 7 | #[prop(into, optional)] class: MaybeProp, 8 | #[prop(into, optional)] node_ref: AnyNodeRef, 9 | ) -> impl IntoView { 10 | view! { 11 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/dom/src/utils/get_window_scroll_bar_x.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::dom::{get_document_element, get_node_scroll}; 2 | use web_sys::{DomRect, Element}; 3 | 4 | use crate::utils::get_bounding_client_rect::get_bounding_client_rect; 5 | 6 | // If has a CSS width greater than the viewport, then this will be incorrect for RTL. 7 | pub fn get_window_scroll_bar_x(element: &Element, rect: Option<&DomRect>) -> f64 { 8 | let left_scroll = get_node_scroll(element.into()).scroll_left; 9 | 10 | if let Some(rect) = rect { 11 | rect.left() + left_scroll 12 | } else { 13 | get_bounding_client_rect( 14 | (&get_document_element(Some(element.into()))).into(), 15 | false, 16 | false, 17 | None, 18 | ) 19 | .left 20 | + left_scroll 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/dom/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Rust Floating UI Logo 4 | 5 |

6 | 7 |

floating-ui-dom

8 | 9 | This is the library to use Floating UI on the web, wrapping `floating-ui-core` with DOM interface logic. 10 | 11 | [Rust Floating UI](https://github.com/RustForWeb/floating-ui) is a Rust port of [Floating UI](https://floating-ui.com). 12 | 13 | ## Documentation 14 | 15 | See [the Rust Floating UI book](https://floating-ui.rustforweb.org/) for documentation. 16 | 17 | ## Rust for Web 18 | 19 | The Rust Floating UI project is part of [Rust for Web](https://github.com/RustForWeb). 20 | 21 | [Rust for Web](https://github.com/RustForWeb) creates and ports web libraries for Rust. All projects are free and open source. 22 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Rust Floating UI Logo 4 | 5 |

6 | 7 |

floating-ui-core

8 | 9 | This is the platform-agnostic core of Floating UI, exposing the main `compute_position` function but no platform interface logic. 10 | 11 | [Rust Floating UI](https://github.com/RustForWeb/floating-ui) is a Rust port of [Floating UI](https://floating-ui.com). 12 | 13 | ## Documentation 14 | 15 | See [the Rust Floating UI book](https://floating-ui.rustforweb.org/) for documentation. 16 | 17 | ## Rust for Web 18 | 19 | The Rust Floating UI project is part of [Rust for Web](https://github.com/RustForWeb). 20 | 21 | [Rust for Web](https://github.com/RustForWeb) creates and ports web libraries for Rust. All projects are free and open source. 22 | -------------------------------------------------------------------------------- /packages/utils/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Rust Floating UI Logo 4 | 5 |

6 | 7 |

floating-ui-utils

8 | 9 | Utility functions shared across Floating UI crates. You may use these functions in your own projects, but are subject to breaking changes. 10 | 11 | [Rust Floating UI](https://github.com/RustForWeb/floating-ui) is a Rust port of [Floating UI](https://floating-ui.com). 12 | 13 | ## Documentation 14 | 15 | See [the Rust Floating UI book](https://floating-ui.rustforweb.org/) for documentation. 16 | 17 | ## Rust for Web 18 | 19 | The Rust Floating UI project is part of [Rust for Web](https://github.com/RustForWeb). 20 | 21 | [Rust for Web](https://github.com/RustForWeb) creates and ports web libraries for Rust. All projects are free and open source. 22 | -------------------------------------------------------------------------------- /packages/leptos/tests/visual/src/utils/use_size.rs: -------------------------------------------------------------------------------- 1 | use leptos::prelude::*; 2 | use wasm_bindgen::closure::Closure; 3 | use web_sys::{Event, js_sys::Reflect, window}; 4 | 5 | pub fn use_size( 6 | initial_size: Option, 7 | key: Option<&'static str>, 8 | ) -> (ReadSignal, WriteSignal) { 9 | let initial_size = initial_size.unwrap_or(80); 10 | let key = key.unwrap_or("floating"); 11 | 12 | let (size, set_size) = signal(initial_size); 13 | 14 | let closure: Closure = Closure::new(move |event: Event| { 15 | set_size.set(event_target_value(&event).parse().unwrap()); 16 | }); 17 | 18 | Reflect::set( 19 | &window().expect("Window should exist."), 20 | &format!("__handleSizeChange_{key}").into(), 21 | &closure.into_js_value(), 22 | ) 23 | .expect("Reflect set should be successful."); 24 | 25 | (size, set_size) 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/upstream.yml: -------------------------------------------------------------------------------- 1 | name: Check for upstream releases 2 | 3 | on: 4 | schedule: 5 | - cron: '00 16 * * *' 6 | workflow_dispatch: {} 7 | 8 | permissions: 9 | contents: read 10 | pull-requests: write 11 | 12 | jobs: 13 | check: 14 | name: Check for upstream releases 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v6 20 | 21 | - name: Set up Rust toolchain 22 | uses: actions-rust-lang/setup-rust-toolchain@v1 23 | 24 | - name: Check for upstream releases 25 | run: cargo run -p scripts --bin upstream 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | GIT_USER_NAME: github-actions[bot] 29 | GIT_USER_EMAIL: github-actions[bot]@users.noreply.github.com 30 | RUST_LOG: upstream=debug 31 | -------------------------------------------------------------------------------- /book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Introduction](./introduction.md) 4 | - [Examples](./examples.md) 5 | - [Tutorial]() 6 | - [Compute Position](./compute-position.md) 7 | - [Auto Update](./auto-update.md) 8 | - [Middleware](./middleware/README.md) 9 | - [Arrow](./middleware/arrow.md) 10 | - [Auto Placement](./middleware/auto-placement.md) 11 | - [Flip](./middleware/flip.md) 12 | - [Hide](./middleware/hide.md) 13 | - [Inline](./middleware/inline.md) 14 | - [Offset](./middleware/offset.md) 15 | - [Shift](./middleware/shift.md) 16 | - [Size](./middleware/size.md) 17 | - [Detect Overflow](./detect-overflow.md) 18 | - [Virtual Elements](./virtual-elements.md) 19 | - [Platform](./platform.md) 20 | - [Frameworks](./frameworks/README.md) 21 | - [DOM](./frameworks/dom.md) 22 | - [Dioxus](./frameworks/dioxus.md) 23 | - [Leptos](./frameworks/leptos.md) 24 | - [Yew](./frameworks/yew.md) 25 | - [Contributing]() 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Rust for Web 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 21 | OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/dom/src/platform/get_element_rects.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_core::{GetElementRectsArgs, Platform as CorePlatform}; 2 | use floating_ui_utils::{ElementOrWindow, ElementRects, Rect}; 3 | use web_sys::{Element, Window}; 4 | 5 | use crate::{ 6 | platform::Platform, 7 | utils::get_rect_relative_to_offset_parent::get_rect_relative_to_offset_parent, 8 | }; 9 | 10 | pub fn get_element_rects(platform: &Platform, args: GetElementRectsArgs) -> ElementRects { 11 | let offset_parent = platform 12 | .get_offset_parent(args.floating) 13 | .expect("Platform implements get_offset_parent."); 14 | let dimensions = platform.get_dimensions(args.floating); 15 | 16 | let offset_parent_ref: ElementOrWindow = (&offset_parent).into(); 17 | 18 | ElementRects { 19 | reference: get_rect_relative_to_offset_parent( 20 | args.reference, 21 | offset_parent_ref.into(), 22 | args.strategy, 23 | ), 24 | floating: Rect { 25 | x: 0.0, 26 | y: 0.0, 27 | width: dimensions.width, 28 | height: dimensions.height, 29 | }, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/yew/tests/visual/src/utils/use_size.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::{JsCast, closure::Closure}; 2 | use web_sys::{Event, js_sys::Reflect, window}; 3 | use yew::{UseStateHandle, hook, use_state}; 4 | 5 | #[hook] 6 | pub fn use_size(initial_size: Option, key: Option<&'static str>) -> UseStateHandle { 7 | let initial_size = initial_size.unwrap_or(80); 8 | let key = key.unwrap_or("floating"); 9 | 10 | let size = use_state(|| initial_size); 11 | 12 | let closure: Closure = Closure::new({ 13 | let size = size.clone(); 14 | 15 | move |event: Event| { 16 | size.set( 17 | event 18 | .target() 19 | .unwrap() 20 | .unchecked_into::() 21 | .value() 22 | .parse() 23 | .unwrap(), 24 | ); 25 | } 26 | }); 27 | 28 | Reflect::set( 29 | &window().expect("Window should exist."), 30 | &format!("__handleSizeChange_{key}").into(), 31 | &closure.into_js_value(), 32 | ) 33 | .expect("Reflect set should be successful."); 34 | 35 | size 36 | } 37 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/mirrors-prettier 3 | rev: v3.1.0 4 | hooks: 5 | - id: prettier 6 | language: node 7 | additional_dependencies: 8 | - prettier@^3.6.2 9 | - prettier-plugin-tailwindcss@^0.7.0 10 | 11 | - repo: https://github.com/doublify/pre-commit-rust 12 | rev: v1.0 13 | hooks: 14 | - id: fmt 15 | - id: clippy 16 | 17 | - repo: https://github.com/EmbarkStudios/cargo-deny 18 | rev: 0.18.9 19 | hooks: 20 | - id: cargo-deny 21 | 22 | # - repo: https://github.com/bnjbvr/cargo-machete 23 | # rev: ba1bcd4 24 | # hooks: 25 | # - id: cargo-machete 26 | - repo: local 27 | hooks: 28 | - id: cargo-machete 29 | name: cargo-machete 30 | language: rust 31 | entry: cargo machete 32 | types: [file, toml] 33 | files: Cargo\.(toml|lock) 34 | pass_filenames: false 35 | 36 | - repo: https://github.com/DevinR528/cargo-sort 37 | rev: v2.0.2 38 | hooks: 39 | - id: cargo-sort 40 | args: ['--workspace'] 41 | -------------------------------------------------------------------------------- /book-examples/src/app.rs: -------------------------------------------------------------------------------- 1 | use leptos::prelude::*; 2 | 3 | #[component] 4 | pub fn App() -> impl IntoView { 5 | let mut views: Vec = vec![]; 6 | 7 | #[cfg(feature = "placement")] 8 | { 9 | use crate::positioning::placement::PlacementDemo; 10 | views.push( 11 | view! { 12 | 13 | } 14 | .into_any(), 15 | ); 16 | } 17 | #[cfg(feature = "shift")] 18 | { 19 | use crate::positioning::shift::ShiftDemo; 20 | views.push( 21 | view! { 22 | 23 | } 24 | .into_any(), 25 | ); 26 | } 27 | #[cfg(feature = "flip")] 28 | { 29 | use crate::positioning::flip::FlipDemo; 30 | views.push( 31 | view! { 32 | 33 | } 34 | .into_any(), 35 | ); 36 | } 37 | #[cfg(feature = "size")] 38 | { 39 | use crate::positioning::size::SizeDemo; 40 | views.push( 41 | view! { 42 | 43 | } 44 | .into_any(), 45 | ); 46 | } 47 | 48 | views.into_view() 49 | } 50 | -------------------------------------------------------------------------------- /packages/dom/src/platform/get_scale.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::Coords; 2 | 3 | use crate::{ 4 | types::ElementOrVirtual, 5 | utils::get_css_dimensions::{CssDimensions, get_css_dimensions}, 6 | }; 7 | 8 | pub fn get_scale(element_or_virtual: ElementOrVirtual) -> Coords { 9 | let dom_element = element_or_virtual.resolve(); 10 | 11 | if let Some(dom_element) = dom_element { 12 | let rect = dom_element.get_bounding_client_rect(); 13 | let CssDimensions { 14 | dimensions, 15 | should_fallback, 16 | } = get_css_dimensions(&dom_element); 17 | let mut x = if should_fallback { 18 | rect.width().round() 19 | } else { 20 | rect.width() 21 | } / dimensions.width; 22 | let mut y = if should_fallback { 23 | rect.height().round() 24 | } else { 25 | rect.height() 26 | } / dimensions.height; 27 | 28 | if x == 0.0 || x.is_nan() || x.is_infinite() { 29 | x = 1.0; 30 | } 31 | 32 | if y == 0.0 || y.is_nan() || y.is_infinite() { 33 | y = 1.0; 34 | } 35 | 36 | Coords { x, y } 37 | } else { 38 | Coords::new(1.0) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/dom/src/utils/get_visual_offsets.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::{ 2 | Coords, 3 | dom::{DomElementOrWindow, get_window}, 4 | }; 5 | use web_sys::Element; 6 | 7 | pub fn get_visual_offsets(_element: Option<&Element>) -> Coords { 8 | // TODO: web-sys does not support VisualViewport 9 | 10 | // let window = get_window(element.map(|element| element.as_ref())); 11 | 12 | // if !is_web_kit() || !window.visual_viewport { 13 | // Coords::new(0.0) 14 | // } else { 15 | // Coords { 16 | // x: todo!(), 17 | // y: todo!(), 18 | // } 19 | // } 20 | 21 | Coords::new(0.0) 22 | } 23 | 24 | pub fn should_add_visual_offsets( 25 | element: Option<&Element>, 26 | is_fixed: bool, 27 | floating_offset_parent: Option, 28 | ) -> bool { 29 | match floating_offset_parent { 30 | Some(DomElementOrWindow::Window(floating_offset_parent)) => { 31 | if is_fixed 32 | && *floating_offset_parent != get_window(element.map(|element| element.as_ref())) 33 | { 34 | false 35 | } else { 36 | is_fixed 37 | } 38 | } 39 | _ => false, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/leptos/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /packages/yew/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /packages/yew/src/use_auto_update.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use floating_ui_dom::{AutoUpdateOptions, auto_update}; 4 | use yew::prelude::*; 5 | 6 | use crate::types::WhileElementsMountedFn; 7 | 8 | /// Use [`auto_update`] with [`AutoUpdateOptions::default`]. 9 | /// 10 | /// Can be passed to [`UseFloatingOptions::while_elements_mounted`][crate::types::UseFloatingOptions::while_elements_mounted]. 11 | #[hook] 12 | pub fn use_auto_update() -> Rc> { 13 | use_memo((), |_| { 14 | let rc: Rc = Rc::new(|reference, floating, update| { 15 | auto_update(reference, floating, update, AutoUpdateOptions::default()).into() 16 | }); 17 | 18 | rc 19 | }) 20 | } 21 | 22 | /// Use [`auto_update`] with `options`. 23 | /// 24 | /// Can be passed to [`UseFloatingOptions::while_elements_mounted`][crate::types::UseFloatingOptions::while_elements_mounted]. 25 | #[hook] 26 | pub fn use_auto_update_with_options(options: AutoUpdateOptions) -> Rc> { 27 | use_memo(options, |options| { 28 | let options = options.clone(); 29 | 30 | let rc: Rc = Rc::new(move |reference, floating, update| { 31 | auto_update(reference, floating, update, options.clone()).into() 32 | }); 33 | 34 | rc 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /packages/dioxus/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /packages/dioxus/src/use_auto_update.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use dioxus::prelude::*; 4 | use floating_ui_dom::{AutoUpdateOptions, auto_update}; 5 | 6 | use crate::{ShallowRc, types::WhileElementsMountedFn}; 7 | 8 | /// Use [`auto_update`] with [`AutoUpdateOptions::default`]. 9 | /// 10 | /// Can be passed to [`UseFloatingOptions::while_elements_mounted`][crate::types::UseFloatingOptions::while_elements_mounted]. 11 | pub fn use_auto_update() -> Memo> { 12 | use_memo(|| { 13 | let rc: Rc = Rc::new(|reference, floating, update| { 14 | auto_update(reference, floating, update, AutoUpdateOptions::default()) 15 | }); 16 | 17 | rc.into() 18 | }) 19 | } 20 | 21 | /// Use [`auto_update`] with `options`. 22 | /// 23 | /// Can be passed to [`UseFloatingOptions::while_elements_mounted`][crate::types::UseFloatingOptions::while_elements_mounted]. 24 | pub fn use_auto_update_with_options( 25 | options: ReadSignal, 26 | ) -> Memo> { 27 | use_memo(move || { 28 | let options = options(); 29 | 30 | let rc: Rc = Rc::new(move |reference, floating, update| { 31 | auto_update(reference, floating, update, options.clone()) 32 | }); 33 | 34 | rc.into() 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /scripts/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fmt, num::NonZeroI32, process::ExitStatus}; 2 | 3 | use octocrab::models::repos::Ref; 4 | 5 | pub fn ref_sha(reference: Ref) -> Result> { 6 | match reference.object { 7 | octocrab::models::repos::Object::Commit { sha, .. } => Ok(sha), 8 | octocrab::models::repos::Object::Tag { sha, .. } => Ok(sha), 9 | _ => Err("Unknown reference object.".into()), 10 | } 11 | } 12 | 13 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 14 | pub struct ExitStatusError(Option); 15 | 16 | impl fmt::Display for ExitStatusError { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | if let Some(code) = self.0 { 19 | write!(f, "process exited unsuccessfully: {code}") 20 | } else { 21 | write!(f, "process exited unsuccessfully: unknown") 22 | } 23 | } 24 | } 25 | 26 | impl Error for ExitStatusError {} 27 | 28 | pub trait ExitStatusExt { 29 | fn stable_exit_ok(&self) -> Result<(), ExitStatusError>; 30 | } 31 | 32 | impl ExitStatusExt for ExitStatus { 33 | fn stable_exit_ok(&self) -> Result<(), ExitStatusError> { 34 | match self.code() { 35 | Some(code) => match NonZeroI32::try_from(code) { 36 | Ok(code) => Err(ExitStatusError(Some(code))), 37 | Err(_) => Ok(()), 38 | }, 39 | None => Err(ExitStatusError(None)), 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/core/src/test_utils.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::{Dimensions, ElementRects, Rect}; 2 | 3 | use crate::types::{GetClippingRectArgs, GetElementRectsArgs, Platform}; 4 | 5 | #[derive(Clone, Debug)] 6 | pub struct Element {} 7 | 8 | #[derive(Clone, Debug)] 9 | pub struct Window {} 10 | 11 | pub const REFERENCE: Element = Element {}; 12 | pub const FLOATING: Element = Element {}; 13 | pub const REFERENCE_RECT: Rect = Rect { 14 | x: 0.0, 15 | y: 0.0, 16 | width: 100.0, 17 | height: 100.0, 18 | }; 19 | pub const FLOATING_RECT: Rect = Rect { 20 | x: 0.0, 21 | y: 0.0, 22 | width: 50.0, 23 | height: 50.0, 24 | }; 25 | 26 | #[derive(Debug)] 27 | pub struct TestPlatform {} 28 | 29 | impl Platform for TestPlatform { 30 | fn get_element_rects(&self, _args: GetElementRectsArgs) -> ElementRects { 31 | ElementRects { 32 | reference: REFERENCE_RECT, 33 | floating: FLOATING_RECT, 34 | } 35 | } 36 | 37 | fn get_clipping_rect(&self, _args: GetClippingRectArgs) -> Rect { 38 | Rect { 39 | x: 0.0, 40 | y: 0.0, 41 | width: 1000.0, 42 | height: 1000.0, 43 | } 44 | } 45 | 46 | fn get_dimensions(&self, _element: &Element) -> Dimensions { 47 | Dimensions { 48 | width: 10.0, 49 | height: 10.0, 50 | } 51 | } 52 | } 53 | 54 | pub const PLATFORM: TestPlatform = TestPlatform {}; 55 | -------------------------------------------------------------------------------- /packages/leptos/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Rust port of [Floating UI](https://floating-ui.com/). 2 | //! 3 | //! This is the library to use Floating UI with Leptos. 4 | //! 5 | //! See [the Rust Floating UI book](https://floating-ui.rustforweb.org/frameworks/leptos.html) for more documenation. 6 | 7 | mod arrow; 8 | mod types; 9 | mod use_floating; 10 | mod utils; 11 | 12 | pub use arrow::*; 13 | pub use types::*; 14 | pub use use_floating::*; 15 | 16 | #[doc(no_inline)] 17 | pub use floating_ui_dom::{ 18 | ARROW_NAME, AUTO_PLACEMENT_NAME, AlignedPlacement, Alignment, ApplyState, ArrowData, 19 | AutoPlacement, AutoPlacementData, AutoPlacementDataOverflow, AutoPlacementOptions, 20 | AutoUpdateOptions, Axis, Boundary, ClientRectObject, ComputePositionConfig, 21 | ComputePositionReturn, Coords, CrossAxis, DefaultLimiter, DefaultVirtualElement, Derivable, 22 | DerivableFn, DetectOverflowOptions, Dimensions, ElementContext, ElementOrVirtual, ElementRects, 23 | FLIP_NAME, FallbackStrategy, Flip, FlipData, FlipDataOverflow, FlipOptions, HIDE_NAME, Hide, 24 | HideData, HideOptions, HideStrategy, INLINE_NAME, Inline, InlineOptions, Length, LimitShift, 25 | LimitShiftOffset, LimitShiftOffsetValues, LimitShiftOptions, Middleware, MiddlewareData, 26 | MiddlewareReturn, MiddlewareState, MiddlewareVec, MiddlewareWithOptions, OFFSET_NAME, Offset, 27 | OffsetData, OffsetOptions, OffsetOptionsValues, Padding, PartialSideObject, Placement, Rect, 28 | RootBoundary, SHIFT_NAME, SIZE_NAME, Shift, ShiftData, ShiftOptions, Side, Size, SizeOptions, 29 | Strategy, VirtualElement, auto_update, compute_position, dom, 30 | }; 31 | -------------------------------------------------------------------------------- /packages/dom/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Floating UI DOM Example 4 | 5 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /packages/yew/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Rust port of [Floating UI](https://floating-ui.com/). 2 | //! 3 | //! This is the library to use Floating UI with Yew. 4 | //! 5 | //! See [the Rust Floating UI book](https://floating-ui.rustforweb.org/frameworks/yew.html) for more documenation. 6 | 7 | mod arrow; 8 | mod types; 9 | mod use_auto_update; 10 | mod use_floating; 11 | mod utils; 12 | 13 | pub use arrow::*; 14 | pub use types::*; 15 | pub use use_auto_update::*; 16 | pub use use_floating::*; 17 | 18 | #[doc(no_inline)] 19 | pub use floating_ui_dom::{ 20 | ARROW_NAME, AUTO_PLACEMENT_NAME, AlignedPlacement, Alignment, ApplyState, ArrowData, 21 | AutoPlacement, AutoPlacementData, AutoPlacementDataOverflow, AutoPlacementOptions, 22 | AutoUpdateOptions, Axis, Boundary, ClientRectObject, ComputePositionConfig, 23 | ComputePositionReturn, Coords, DefaultLimiter, DefaultVirtualElement, Derivable, DerivableFn, 24 | DetectOverflowOptions, Dimensions, ElementContext, ElementOrVirtual, ElementRects, FLIP_NAME, 25 | FallbackStrategy, Flip, FlipData, FlipDataOverflow, FlipOptions, HIDE_NAME, Hide, HideData, 26 | HideOptions, HideStrategy, INLINE_NAME, Inline, InlineOptions, Length, LimitShift, 27 | LimitShiftOffset, LimitShiftOffsetValues, LimitShiftOptions, Middleware, MiddlewareData, 28 | MiddlewareReturn, MiddlewareState, MiddlewareVec, MiddlewareWithOptions, OFFSET_NAME, Offset, 29 | OffsetData, OffsetOptions, OffsetOptionsValues, Padding, Placement, Rect, RootBoundary, 30 | SHIFT_NAME, SIZE_NAME, Shift, ShiftData, ShiftOptions, Side, Size, SizeOptions, Strategy, 31 | VirtualElement, auto_update, compute_position, dom, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/dom/src/utils/get_document_rect.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::{ 2 | Rect, 3 | dom::{get_document_element, get_node_scroll}, 4 | }; 5 | use web_sys::Element; 6 | 7 | use crate::platform::is_rtl::is_rtl; 8 | 9 | use super::get_window_scroll_bar_x::get_window_scroll_bar_x; 10 | 11 | /// Gets the entire size of the scrollable document area, even extending outside of the `` and `` rect bounds if horizontally scrollable. 12 | pub fn get_document_rect(element: &Element) -> Rect { 13 | let html = get_document_element(Some(element.into())); 14 | let scroll = get_node_scroll(element.into()); 15 | let body = element 16 | .owner_document() 17 | .expect("Element should have owner document.") 18 | .body() 19 | .expect("Document should have body."); 20 | 21 | let width = [ 22 | html.scroll_width(), 23 | html.client_width(), 24 | body.scroll_width(), 25 | body.client_width(), 26 | ] 27 | .into_iter() 28 | .max() 29 | .expect("Iterator is not empty.") as f64; 30 | let height = [ 31 | html.scroll_height(), 32 | html.client_height(), 33 | body.scroll_height(), 34 | body.client_height(), 35 | ] 36 | .into_iter() 37 | .max() 38 | .expect("Iterator is not empty.") as f64; 39 | 40 | let mut x = -scroll.scroll_left + get_window_scroll_bar_x(element, None); 41 | let y = -scroll.scroll_top; 42 | 43 | if is_rtl(&body) { 44 | x += html.client_width().max(body.client_width()) as f64 - width; 45 | } 46 | 47 | Rect { 48 | x, 49 | y, 50 | width, 51 | height, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/dioxus/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Rust port of [Floating UI](https://floating-ui.com/). 2 | //! 3 | //! This is the library to use Floating UI with Dioxus. 4 | //! 5 | //! See [the Rust Floating UI book](https://floating-ui.rustforweb.org/frameworks/dioxus.html) for more documenation. 6 | 7 | mod arrow; 8 | mod types; 9 | mod use_auto_update; 10 | mod use_floating; 11 | mod utils; 12 | 13 | pub use arrow::*; 14 | pub use types::*; 15 | pub use use_auto_update::*; 16 | pub use use_floating::*; 17 | 18 | #[doc(no_inline)] 19 | pub use floating_ui_dom::{ 20 | ARROW_NAME, AUTO_PLACEMENT_NAME, AlignedPlacement, Alignment, ApplyState, ArrowData, 21 | AutoPlacement, AutoPlacementData, AutoPlacementDataOverflow, AutoPlacementOptions, 22 | AutoUpdateOptions, Axis, Boundary, ClientRectObject, ComputePositionConfig, 23 | ComputePositionReturn, Coords, CrossAxis, DefaultLimiter, DefaultVirtualElement, Derivable, 24 | DerivableFn, DetectOverflowOptions, Dimensions, ElementContext, ElementOrVirtual, ElementRects, 25 | FLIP_NAME, FallbackStrategy, Flip, FlipData, FlipDataOverflow, FlipOptions, HIDE_NAME, Hide, 26 | HideData, HideOptions, HideStrategy, INLINE_NAME, Inline, InlineOptions, Length, LimitShift, 27 | LimitShiftOffset, LimitShiftOffsetValues, LimitShiftOptions, Middleware, MiddlewareData, 28 | MiddlewareReturn, MiddlewareState, MiddlewareVec, MiddlewareWithOptions, OFFSET_NAME, Offset, 29 | OffsetData, OffsetOptions, OffsetOptionsValues, Padding, PartialSideObject, Placement, Rect, 30 | RootBoundary, SHIFT_NAME, SIZE_NAME, Shift, ShiftData, ShiftOptions, Side, Size, SizeOptions, 31 | Strategy, VirtualElement, auto_update, compute_position, dom, 32 | }; 33 | -------------------------------------------------------------------------------- /book/src/introduction.md: -------------------------------------------------------------------------------- 1 |

2 | Rust Floating UI Logo 3 |

4 | 5 | # Introduction 6 | 7 | Rust Floating UI is a Rust port of [Floating UI](https://floating-ui.com/). 8 | 9 | [Floating UI](https://floating-ui.com) is a library that helps you create “floating” elements such as tooltips, popovers, dropdowns, and more. 10 | 11 | It provides a toolkit of positioning features that let you robustly anchor an absolutely-positioned floating element next to a given reference element. For example, a popover floats next to and remains anchored to its triggering button, even while the page scrolls. 12 | 13 | It also provides features to avoid collisions with the viewport, as absolute positioning often leads to unwanted overflow depending on the location of the positioning reference. 14 | 15 | ## Frameworks 16 | 17 | Rust Floating UI is available for the following frameworks: 18 | 19 | - [DOM (`web-sys`)](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html) 20 | - [Dioxus](https://dioxuslabs.com/) 21 | - [Leptos](https://leptos.dev/) 22 | - [Yew](https://yew.rs/) 23 | 24 | See [Frameworks](./frameworks/index.md) for documentation for each framework. 25 | 26 | ## Examples 27 | 28 | See [Examples](./examples.md). 29 | 30 | ## License 31 | 32 | This project is available under the [MIT license](https://github.com/RustForWeb/floating-ui/blob/main/LICENSE.md). 33 | 34 | ## Rust for Web 35 | 36 | The Rust Floating UI project is part of [Rust for Web](https://github.com/RustForWeb). 37 | 38 | [Rust for Web](https://github.com/RustForWeb) creates and ports web libraries for Rust. All projects are free and open source. 39 | -------------------------------------------------------------------------------- /packages/dom/src/utils/get_css_dimensions.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::{ 2 | Dimensions, 3 | dom::{get_computed_style, is_html_element}, 4 | }; 5 | use web_sys::{Element, HtmlElement, wasm_bindgen::JsCast}; 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct CssDimensions { 9 | pub dimensions: Dimensions, 10 | pub should_fallback: bool, 11 | } 12 | 13 | pub fn get_css_dimensions(element: &Element) -> CssDimensions { 14 | let css = get_computed_style(element); 15 | 16 | let width = css 17 | .get_property_value("width") 18 | .expect("Computed style should have width.") 19 | .replace("px", "") 20 | .parse::() 21 | .unwrap_or(0.0); 22 | let height = css 23 | .get_property_value("height") 24 | .expect("Computed style should have height.") 25 | .replace("px", "") 26 | .parse::() 27 | .unwrap_or(0.0); 28 | 29 | let offset_width; 30 | let offset_height; 31 | if is_html_element(element) { 32 | let element = element.unchecked_ref::(); 33 | offset_width = element.offset_width() as f64; 34 | offset_height = element.offset_height() as f64; 35 | } else { 36 | offset_width = width; 37 | offset_height = height; 38 | } 39 | let should_fallback = width.round() != offset_width || height.round() != offset_height; 40 | 41 | CssDimensions { 42 | dimensions: if should_fallback { 43 | Dimensions { 44 | width: offset_width, 45 | height: offset_height, 46 | } 47 | } else { 48 | Dimensions { width, height } 49 | }, 50 | should_fallback, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /book-examples/src/components/grid_item.rs: -------------------------------------------------------------------------------- 1 | use leptos::prelude::*; 2 | use tailwind_fuse::tw_merge; 3 | 4 | #[component] 5 | pub fn GridItem( 6 | #[prop(into)] title: Signal, 7 | #[prop(into)] description: Signal, 8 | chrome: F, 9 | // #[prop(into)] demo_link: Signal, 10 | #[prop(default = false.into(), into)] hidden: Signal, 11 | ) -> impl IntoView 12 | where 13 | F: Fn() -> IV + 'static, 14 | IV: IntoView + 'static, 15 | { 16 | view! { 17 |
23 |
24 |

{title}

25 |

{description}

26 |
27 |
28 | {chrome()} 29 |
30 | // 36 | // CodeSandbox 37 | // 38 |
39 | } 40 | } 41 | -------------------------------------------------------------------------------- /book/theme/trunk.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Change active file of files. 3 | * 4 | * @param {Element} container 5 | * @param {string | null} name 6 | */ 7 | const changeTrunkFile = (container, name) => { 8 | for (const child of container.children) { 9 | if (!(child instanceof HTMLElement)) { 10 | continue; 11 | } 12 | 13 | if (child.classList.contains('mdbook-trunk-files')) { 14 | for (const file of child.children) { 15 | if (!(file instanceof HTMLElement)) { 16 | continue; 17 | } 18 | 19 | if (file.dataset.file === name) { 20 | file.classList.add('active'); 21 | } else { 22 | file.classList.remove('active'); 23 | } 24 | } 25 | } else if (child.classList.contains('mdbook-trunk-file-content')) { 26 | if (child.dataset.file === name) { 27 | child.classList.remove('hidden'); 28 | } else { 29 | child.classList.add('hidden'); 30 | } 31 | } 32 | } 33 | }; 34 | 35 | document.addEventListener('DOMContentLoaded', () => { 36 | const files = document.querySelectorAll('.mdbook-trunk-file'); 37 | for (const file of files) { 38 | file.addEventListener('click', () => { 39 | if (!(file instanceof HTMLElement)) { 40 | return; 41 | } 42 | 43 | if (!file.parentElement || !file.parentElement.parentElement) { 44 | return; 45 | } 46 | 47 | const container = file.parentElement.parentElement; 48 | const name = file.dataset.file; 49 | 50 | changeTrunkFile(container, file.classList.contains('active') ? null : name); 51 | }); 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /packages/leptos/tests/visual/src/utils/use_resize.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | rc::Rc, 3 | sync::{Arc, Mutex}, 4 | }; 5 | 6 | use leptos::{html::Div, prelude::*}; 7 | use send_wrapper::SendWrapper; 8 | use wasm_bindgen::{JsCast, prelude::Closure}; 9 | use web_sys::{ResizeObserver, ResizeObserverEntry}; 10 | 11 | pub fn use_resize(node_ref: NodeRef
, update: SendWrapper>) { 12 | type CleanupFn = dyn Fn(); 13 | let cleanup: Arc>>>> = Arc::new(Mutex::new(None)); 14 | 15 | Effect::new({ 16 | let cleanup = cleanup.clone(); 17 | 18 | move |_| { 19 | if let Some(cleanup) = cleanup.lock().expect("Lock should be acquired.").as_ref() { 20 | cleanup(); 21 | } 22 | 23 | if let Some(element) = node_ref.get() { 24 | let resize_closure: Closure)> = Closure::new({ 25 | let update = update.clone(); 26 | 27 | move |_entries: Vec| { 28 | update(); 29 | } 30 | }); 31 | 32 | let observer = ResizeObserver::new(resize_closure.into_js_value().unchecked_ref()) 33 | .expect("Resize observer should be created."); 34 | 35 | observer.observe(&element); 36 | 37 | *cleanup.lock().expect("Lock should be acquired.") = 38 | Some(SendWrapper::new(Box::new(move || { 39 | observer.unobserve(&element); 40 | }))); 41 | } 42 | } 43 | }); 44 | 45 | on_cleanup(move || { 46 | if let Some(cleanup) = cleanup.lock().expect("Lock should be acquired.").as_ref() { 47 | cleanup(); 48 | } 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "book-examples", 4 | "packages/*", 5 | "packages/*/example", 6 | "packages/*/tests/*", 7 | "scripts", 8 | ] 9 | resolver = "2" 10 | 11 | [workspace.package] 12 | authors = ["Rust for Web "] 13 | edition = "2024" 14 | license = "MIT" 15 | repository = "https://github.com/RustForWeb/floating-ui" 16 | version = "0.6.0" 17 | 18 | [workspace.dependencies] 19 | cfg-if = "1.0.0" 20 | console_error_panic_hook = "0.1.7" 21 | console_log = "1.0.0" 22 | dioxus = "0.7.0" 23 | dyn_derive = "0.3.4" 24 | dyn_std = "0.3.3" 25 | floating-ui-core = { path = "./packages/core", version = "0.6.0" } 26 | floating-ui-dioxus = { path = "./packages/dioxus", version = "0.6.0" } 27 | floating-ui-dom = { path = "./packages/dom", version = "0.6.0" } 28 | floating-ui-leptos = { path = "./packages/leptos", version = "0.6.0" } 29 | floating-ui-utils = { path = "./packages/utils", version = "0.6.0" } 30 | floating-ui-yew = { path = "./packages/yew", version = "0.6.0" } 31 | leptos = "0.8.0" 32 | leptos-node-ref = "0.2.0" 33 | leptos_router = "0.8.0" 34 | log = "0.4.22" 35 | send_wrapper = "0.6.0" 36 | serde = { version = "1.0.209", features = ["derive"] } 37 | serde_json = "1.0.127" 38 | wasm-bindgen = "0.2.93" 39 | wasm-bindgen-test = "0.3.43" 40 | yew = "0.22.0" 41 | yew-router = "0.19.0" 42 | 43 | [workspace.dependencies.web-sys] 44 | version = "0.3.70" 45 | features = [ 46 | "css", 47 | "AddEventListenerOptions", 48 | "CssStyleDeclaration", 49 | "Document", 50 | "DomRect", 51 | "DomRectList", 52 | "Element", 53 | "Event", 54 | "EventTarget", 55 | "HtmlElement", 56 | "HtmlSlotElement", 57 | "IntersectionObserver", 58 | "IntersectionObserverEntry", 59 | "IntersectionObserverInit", 60 | "Node", 61 | "Range", 62 | "ResizeObserver", 63 | "ResizeObserverEntry", 64 | "Selection", 65 | "ShadowRoot", 66 | "VisualViewport", 67 | "Window", 68 | ] 69 | -------------------------------------------------------------------------------- /book-examples/src/positioning/size.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_leptos::{ 2 | DetectOverflowOptions, MiddlewareVec, Offset, OffsetOptions, Padding, RootBoundary, Size, 3 | SizeOptions, 4 | }; 5 | use leptos::prelude::*; 6 | use send_wrapper::SendWrapper; 7 | 8 | use crate::components::{Chrome, Floating, GridItem, Reference, Scrollable}; 9 | 10 | #[component] 11 | pub fn SizeDemo() -> impl IntoView { 12 | view! { 13 | 23 | 39 | Dropdown 40 |
41 | } 42 | reference=move |node_ref| view! { 43 | 44 | } 45 | /> 46 | 47 | } 48 | /> 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/leptos/tests/visual/src/spec/virtual_element.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use floating_ui_leptos::{ 4 | DefaultVirtualElement, Strategy, UseFloatingOptions, UseFloatingReturn, VirtualElement, 5 | use_floating, 6 | }; 7 | use leptos::prelude::*; 8 | use leptos_node_ref::AnyNodeRef; 9 | 10 | use crate::utils::use_scroll::{UseScrollOptions, UseScrollReturn, use_scroll}; 11 | 12 | #[component] 13 | pub fn VirtualElement() -> impl IntoView { 14 | let reference_ref = AnyNodeRef::new(); 15 | let floating_ref = AnyNodeRef::new(); 16 | let virtual_element = MaybeProp::derive(move || { 17 | let context_element = reference_ref.get(); 18 | context_element.map(|context_element| { 19 | let element: &web_sys::Element = context_element.as_ref(); 20 | (Box::new( 21 | DefaultVirtualElement::new(Rc::new({ 22 | let context_element = context_element.clone(); 23 | 24 | move || context_element.get_bounding_client_rect().into() 25 | })) 26 | .context_element(element.clone()), 27 | ) as Box>) 28 | .into() 29 | }) 30 | }); 31 | 32 | let UseFloatingReturn { 33 | x, 34 | y, 35 | strategy, 36 | update, 37 | .. 38 | } = use_floating( 39 | virtual_element, 40 | floating_ref, 41 | UseFloatingOptions::default() 42 | .strategy(Strategy::Fixed) 43 | .while_elements_mounted_auto_update(), 44 | ); 45 | 46 | let UseScrollReturn { scroll_ref, .. } = use_scroll(UseScrollOptions { 47 | reference_ref, 48 | floating_ref, 49 | update, 50 | rtl: None::.into(), 51 | disable_ref_updates: None, 52 | }); 53 | 54 | view! { 55 |

Virtual Element

56 |

57 |
58 |
59 |
60 | Reference 61 |
62 |
63 |
64 | 65 |
72 | Floating 73 |
74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/yew/src/arrow.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_dom::{ 2 | ARROW_NAME, Arrow as CoreArrow, ArrowOptions as CoreArrowOptions, Middleware, MiddlewareReturn, 3 | MiddlewareState, Padding, 4 | }; 5 | use web_sys::wasm_bindgen::JsCast; 6 | use yew::NodeRef; 7 | 8 | /// Options for [`Arrow`]. 9 | #[derive(Clone, PartialEq)] 10 | pub struct ArrowOptions { 11 | /// The arrow element to be positioned. 12 | pub element: NodeRef, 13 | 14 | /// The padding between the arrow element and the floating element edges. 15 | /// Useful when the floating element has rounded corners. 16 | /// 17 | /// Defaults to `0` on all sides. 18 | pub padding: Option, 19 | } 20 | 21 | impl ArrowOptions { 22 | pub fn new(element: NodeRef) -> Self { 23 | ArrowOptions { 24 | element, 25 | padding: None, 26 | } 27 | } 28 | 29 | /// Set `element` option. 30 | pub fn element(mut self, value: NodeRef) -> Self { 31 | self.element = value; 32 | self 33 | } 34 | 35 | /// Set `padding` option. 36 | pub fn padding(mut self, value: Padding) -> Self { 37 | self.padding = Some(value); 38 | self 39 | } 40 | } 41 | 42 | /// Arrow middleware. 43 | /// 44 | /// Provides data to position an inner element of the floating element so that it appears centered to the reference element. 45 | /// 46 | /// See [the Rust Floating UI book](https://floating-ui.rustforweb.org/middleware/arrow.html) for more documentation. 47 | #[derive(Clone, PartialEq)] 48 | pub struct Arrow { 49 | options: ArrowOptions, 50 | } 51 | 52 | impl Arrow { 53 | pub fn new(options: ArrowOptions) -> Self { 54 | Arrow { options } 55 | } 56 | } 57 | 58 | impl Middleware for Arrow { 59 | fn name(&self) -> &'static str { 60 | ARROW_NAME 61 | } 62 | 63 | fn compute( 64 | &self, 65 | state: MiddlewareState, 66 | ) -> MiddlewareReturn { 67 | match self.options.element.get() { 68 | Some(element) => CoreArrow::new(CoreArrowOptions { 69 | element: element 70 | .dyn_into() 71 | .expect("Arrow element should be an Element."), 72 | padding: self.options.padding.clone(), 73 | }) 74 | .compute(state), 75 | _ => MiddlewareReturn { 76 | x: None, 77 | y: None, 78 | data: None, 79 | reset: None, 80 | }, 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /packages/dioxus/src/arrow.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use dioxus::{html::MountedData, signals::Signal, web::WebEventExt}; 4 | use floating_ui_dom::{ 5 | ARROW_NAME, Arrow as CoreArrow, ArrowOptions as CoreArrowOptions, Middleware, MiddlewareReturn, 6 | MiddlewareState, Padding, 7 | }; 8 | 9 | /// Options for [`Arrow`]. 10 | #[derive(Clone, PartialEq)] 11 | pub struct ArrowOptions { 12 | /// The arrow element to be positioned. 13 | pub element: Signal>>, 14 | 15 | /// The padding between the arrow element and the floating element edges. 16 | /// Useful when the floating element has rounded corners. 17 | /// 18 | /// Defaults to `0` on all sides. 19 | pub padding: Option, 20 | } 21 | 22 | impl ArrowOptions { 23 | pub fn new(element: Signal>>) -> Self { 24 | ArrowOptions { 25 | element, 26 | padding: None, 27 | } 28 | } 29 | 30 | /// Set `element` option. 31 | pub fn element(mut self, value: Signal>>) -> Self { 32 | self.element = value; 33 | self 34 | } 35 | 36 | /// Set `padding` option. 37 | pub fn padding(mut self, value: Padding) -> Self { 38 | self.padding = Some(value); 39 | self 40 | } 41 | } 42 | 43 | /// Arrow middleware. 44 | /// 45 | /// Provides data to position an inner element of the floating element so that it appears centered to the reference element. 46 | /// 47 | /// See [the Rust Floating UI book](https://floating-ui.rustforweb.org/middleware/arrow.html) for more documentation. 48 | #[derive(Clone, PartialEq)] 49 | pub struct Arrow { 50 | options: ArrowOptions, 51 | } 52 | 53 | impl Arrow { 54 | pub fn new(options: ArrowOptions) -> Self { 55 | Arrow { options } 56 | } 57 | } 58 | 59 | impl Middleware for Arrow { 60 | fn name(&self) -> &'static str { 61 | ARROW_NAME 62 | } 63 | 64 | fn compute( 65 | &self, 66 | state: MiddlewareState, 67 | ) -> MiddlewareReturn { 68 | match (self.options.element)().map(|element| element.as_web_event()) { 69 | Some(element) => CoreArrow::new(CoreArrowOptions { 70 | element, 71 | padding: self.options.padding.clone(), 72 | }) 73 | .compute(state), 74 | _ => MiddlewareReturn { 75 | x: None, 76 | y: None, 77 | data: None, 78 | reset: None, 79 | }, 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /book-examples/src/positioning/flip.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_leptos::{ 2 | DetectOverflowOptions, Flip, FlipOptions, MiddlewareVec, Offset, OffsetOptions, Placement, 3 | RootBoundary, 4 | }; 5 | use leptos::prelude::*; 6 | use leptos_node_ref::AnyNodeRef; 7 | use send_wrapper::SendWrapper; 8 | 9 | use crate::{ 10 | components::{Chrome, Floating, GridItem, Reference, Scrollable}, 11 | utils::rem_to_px, 12 | }; 13 | 14 | #[component] 15 | pub fn FlipDemo() -> impl IntoView { 16 | let boundary_ref = AnyNodeRef::new(); 17 | 18 | Effect::new(move |_| { 19 | if let Some(boundary) = boundary_ref.get() { 20 | boundary 21 | .first_element_child() 22 | .expect("First element child should exist.") 23 | .set_scroll_top(rem_to_px(275.0 / 16.0) as i32); 24 | } 25 | }); 26 | 27 | view! { 28 | 33 | 39 | 53 | Tooltip 54 | 55 | } 56 | reference=move |node_ref| view! { 57 | 58 | } 59 | /> 60 | 61 | 62 | } 63 | /> 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /book-examples/src/components/floating.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_leptos::{ 2 | MiddlewareVec, Placement, Strategy, UseFloatingOptions, UseFloatingReturn, use_floating, 3 | }; 4 | use leptos::prelude::*; 5 | use leptos_node_ref::AnyNodeRef; 6 | use send_wrapper::SendWrapper; 7 | use tailwind_fuse::tw_merge; 8 | 9 | #[component] 10 | pub fn Floating( 11 | #[prop(into, optional)] class: MaybeProp, 12 | #[prop(into, optional)] strategy: MaybeProp, 13 | #[prop(into, optional)] placement: MaybeProp, 14 | #[prop(into, optional)] middleware: MaybeProp>, 15 | #[prop(default = false.into(), into)] arrow: Signal, 16 | content: CF, 17 | reference: RF, 18 | ) -> impl IntoView 19 | where 20 | CF: Fn() -> CIV + 'static, 21 | CIV: IntoView + 'static, 22 | RF: Fn(AnyNodeRef) -> RIV + 'static, 23 | RIV: IntoView + 'static, 24 | { 25 | let floating_ref = AnyNodeRef::new(); 26 | let reference_ref = AnyNodeRef::new(); 27 | let arrow_ref = AnyNodeRef::new(); 28 | 29 | let UseFloatingReturn { 30 | floating_styles, .. 31 | } = use_floating( 32 | reference_ref, 33 | floating_ref, 34 | UseFloatingOptions::default() 35 | .while_elements_mounted_auto_update() 36 | .placement(placement) 37 | .strategy(strategy) 38 | .middleware(middleware), 39 | ); 40 | 41 | view! { 42 | {reference(reference_ref)} 43 | 44 |
61 |
{content()}
62 | 63 |
68 | 69 |
70 | } 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Rust Floating UI Logo 4 | 5 |

6 | 7 |

Rust Floating UI

8 | 9 | Rust port of [Floating UI](https://floating-ui.com). 10 | 11 | [Floating UI](https://floating-ui.com) is a library that helps you create "floating" elements such as tooltips, popovers, dropdowns, and more. 12 | 13 | ## Frameworks 14 | 15 | Rust Floating UI is available for these Rust frameworks: 16 | 17 | - [DOM](./packages/dom) ([`web-sys`](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html)) 18 | - [Dioxus](https://dioxuslabs.com/) 19 | - [Leptos](./packages/leptos) 20 | - [Yew](https://yew.rs/) 21 | 22 | ## Examples 23 | 24 | See [the Rust Floating UI book](https://floating-ui.rustforweb.org/) for examples. 25 | 26 | Each framework has an implementations of the [Floating UI tutorial](https://floating-ui.com/docs/tutorial) as an example: 27 | 28 | - [DOM](./packages/dom/example) 29 | - [Dioxus](./packages/dioxus/example) 30 | - [Leptos](./packages/leptos/example) 31 | - [Yew](./packages/yew/example) 32 | 33 | Additionally, implementations of [Floating UI tests](https://github.com/floating-ui/floating-ui/tree/master/packages/dom/test) are more complex examples: 34 | 35 | - [Dioxus](./packages/dioxus/tests) 36 | - [Leptos](./packages/leptos/tests) 37 | - [Yew](./packages/yew/tests) 38 | 39 | ## Documentation 40 | 41 | See [the Rust Floating UI book](https://floating-ui.rustforweb.org/) for documentation. 42 | 43 | Documentation for the crates is available on [Docs.rs](https://docs.rs/): 44 | 45 | - [`floating-ui-core`](https://docs.rs/floating-ui-core/latest/floating_ui_core/) 46 | - [`floating-ui-dioxus`](https://docs.rs/floating-ui-dioxus/latest/floating_ui_dioxus/) 47 | - [`floating-ui-dom`](https://docs.rs/floating-ui-dom/latest/floating_ui_dom/) 48 | - [`floating-ui-leptos`](https://docs.rs/floating-ui-leptos/latest/floating_ui_leptos/) 49 | - [`floating-ui-utils`](https://docs.rs/floating-ui-utils/latest/floating_ui_utils/) 50 | - [`floating-ui-yew`](https://docs.rs/floating-ui-yew/latest/floating_ui_yew/) 51 | 52 | ## Credits 53 | 54 | The logo is a combination of the [Floating UI logo](https://github.com/floating-ui/floating-ui#credits) and [Ferris the Rustacean](https://rustacean.net/). 55 | 56 | ## License 57 | 58 | This project is available under the [MIT license](LICENSE.md). 59 | 60 | ## Rust for Web 61 | 62 | The Rust Floating UI project is part of [Rust for Web](https://github.com/RustForWeb). 63 | 64 | [Rust for Web](https://github.com/RustForWeb) creates and ports web libraries for Rust. All projects are free and open source. 65 | -------------------------------------------------------------------------------- /book/theme/tabs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Change active tab of tabs. 3 | * 4 | * @param {Element} container 5 | * @param {string} name 6 | */ 7 | const changeTab = (container, name) => { 8 | for (const child of container.children) { 9 | if (!(child instanceof HTMLElement)) { 10 | continue; 11 | } 12 | 13 | if (child.classList.contains('mdbook-tabs')) { 14 | for (const tab of child.children) { 15 | if (!(tab instanceof HTMLElement)) { 16 | continue; 17 | } 18 | 19 | if (tab.dataset.tabname === name) { 20 | tab.classList.add('active'); 21 | } else { 22 | tab.classList.remove('active'); 23 | } 24 | } 25 | } else if (child.classList.contains('mdbook-tab-content')) { 26 | if (child.dataset.tabname === name) { 27 | child.classList.remove('hidden'); 28 | } else { 29 | child.classList.add('hidden'); 30 | } 31 | } 32 | } 33 | }; 34 | 35 | document.addEventListener('DOMContentLoaded', () => { 36 | const tabs = document.querySelectorAll('.mdbook-tab'); 37 | for (const tab of tabs) { 38 | tab.addEventListener('click', () => { 39 | if (!(tab instanceof HTMLElement)) { 40 | return; 41 | } 42 | 43 | if (!tab.parentElement || !tab.parentElement.parentElement) { 44 | return; 45 | } 46 | 47 | const container = tab.parentElement.parentElement; 48 | const name = tab.dataset.tabname; 49 | const global = container.dataset.tabglobal; 50 | 51 | changeTab(container, name); 52 | 53 | if (global) { 54 | localStorage.setItem(`mdbook-tabs-${global}`, name); 55 | 56 | const globalContainers = document.querySelectorAll( 57 | `.mdbook-tabs-container[data-tabglobal="${global}"]` 58 | ); 59 | for (const globalContainer of globalContainers) { 60 | changeTab(globalContainer, name); 61 | } 62 | } 63 | }); 64 | } 65 | 66 | const containers = document.querySelectorAll('.mdbook-tabs-container[data-tabglobal]'); 67 | for (const container of containers) { 68 | const global = container.dataset.tabglobal; 69 | 70 | const name = localStorage.getItem(`mdbook-tabs-${global}`); 71 | if (name && document.querySelector(`.mdbook-tab[data-tabname=${name}]`)) { 72 | changeTab(container, name); 73 | } 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: 7 | - main 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | lint: 14 | name: Lint 15 | runs-on: ubuntu-latest 16 | 17 | env: 18 | RUSTFLAGS: '-Dwarnings' 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v6 23 | 24 | - name: Set up Rust toolchain 25 | uses: actions-rust-lang/setup-rust-toolchain@v1 26 | with: 27 | components: clippy, rustfmt 28 | target: wasm32-unknown-unknown 29 | 30 | - name: Install Cargo Binary Install 31 | uses: cargo-bins/cargo-binstall@main 32 | 33 | - name: Install crates 34 | run: cargo binstall -y --force cargo-deny cargo-machete cargo-sort 35 | 36 | - name: Lint 37 | run: cargo clippy --all-features --locked 38 | 39 | - name: Check dependencies 40 | run: cargo deny check 41 | 42 | - name: Check unused dependencies 43 | run: cargo machete 44 | 45 | - name: Check manifest formatting 46 | run: cargo sort --workspace --check 47 | 48 | - name: Check formatting 49 | run: cargo fmt --all --check 50 | test: 51 | name: Test 52 | runs-on: ubuntu-latest 53 | 54 | steps: 55 | - name: Checkout 56 | uses: actions/checkout@v6 57 | 58 | - name: Set up Rust toolchain 59 | uses: actions-rust-lang/setup-rust-toolchain@v1 60 | with: 61 | components: clippy, rustfmt 62 | target: wasm32-unknown-unknown 63 | 64 | - name: Install Cargo Binary Install 65 | uses: cargo-bins/cargo-binstall@main 66 | 67 | - name: Install Trunk 68 | run: cargo binstall --force -y trunk 69 | 70 | - name: Set up Node.js 71 | uses: actions/setup-node@v6 72 | with: 73 | node-version: 'lts/*' 74 | 75 | - name: Set up pnpm 76 | uses: pnpm/action-setup@v4 77 | with: 78 | version: 'latest' 79 | 80 | - name: Test 81 | run: cargo test --all-features --locked --release 82 | 83 | - name: Upload visual snapshot diffs 84 | uses: actions/upload-artifact@v6 85 | if: always() 86 | with: 87 | name: visual-snapshots-diff 88 | path: target/tmp/floating-ui/packages/dom/test-results 89 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | bump: 7 | description: 'Bump version by semver keyword.' 8 | required: true 9 | type: choice 10 | options: 11 | - patch 12 | - minor 13 | - major 14 | 15 | permissions: 16 | contents: write 17 | 18 | jobs: 19 | release: 20 | name: Release 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - name: Generate GitHub App token 25 | id: app-token 26 | uses: getsentry/action-github-app-token@v3 27 | with: 28 | app_id: ${{ secrets.APP_ID }} 29 | private_key: ${{ secrets.APP_PRIVATE_KEY }} 30 | 31 | - name: Checkout 32 | uses: actions/checkout@v6 33 | 34 | - name: Set up Rust toolchain 35 | uses: actions-rust-lang/setup-rust-toolchain@v1 36 | with: 37 | target: wasm32-unknown-unknown 38 | 39 | - name: Install Cargo Binary Install 40 | uses: cargo-bins/cargo-binstall@main 41 | 42 | - name: Install crates 43 | run: cargo binstall --force -y cargo-workspaces toml-cli 44 | 45 | - name: Bump version 46 | run: cargo workspaces version --all --no-git-commit --yes ${{ inputs.bump }} 47 | 48 | - name: Extract version 49 | id: extract-version 50 | run: echo "VERSION=v$(toml get Cargo.toml workspace.package.version --raw)" >> "$GITHUB_OUTPUT" 51 | 52 | - name: Add changes 53 | run: git add . 54 | 55 | - name: Commit 56 | uses: dsanders11/github-app-commit-action@v1 57 | with: 58 | message: ${{ steps.extract-version.outputs.VERSION }} 59 | token: ${{ steps.app-token.outputs.token }} 60 | 61 | - name: Reset and pull 62 | run: git reset --hard && git pull 63 | 64 | - name: Tag 65 | uses: bruno-fs/repo-tagger@1.0.0 66 | with: 67 | tag: ${{ steps.extract-version.outputs.VERSION }} 68 | env: 69 | GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} 70 | 71 | - name: Release 72 | uses: softprops/action-gh-release@v2 73 | with: 74 | generate_release_notes: true 75 | make_latest: true 76 | tag_name: ${{ steps.extract-version.outputs.VERSION }} 77 | token: ${{ steps.app-token.outputs.token }} 78 | 79 | - name: Publish 80 | run: cargo workspaces publish --publish-as-is --token "${{ secrets.CRATES_IO_TOKEN }}" 81 | -------------------------------------------------------------------------------- /packages/leptos/src/arrow.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_dom::{ 2 | ARROW_NAME, Arrow as CoreArrow, ArrowOptions as CoreArrowOptions, Middleware, MiddlewareReturn, 3 | MiddlewareState, Padding, 4 | }; 5 | use leptos::prelude::*; 6 | use leptos_node_ref::AnyNodeRef; 7 | use web_sys::wasm_bindgen::JsCast; 8 | 9 | /// Options for [`Arrow`]. 10 | #[derive(Clone)] 11 | pub struct ArrowOptions { 12 | /// The arrow element to be positioned. 13 | pub element: AnyNodeRef, 14 | 15 | /// The padding between the arrow element and the floating element edges. 16 | /// Useful when the floating element has rounded corners. 17 | /// 18 | /// Defaults to `0` on all sides. 19 | pub padding: Option, 20 | } 21 | 22 | impl ArrowOptions { 23 | pub fn new(element: AnyNodeRef) -> Self { 24 | ArrowOptions { 25 | element, 26 | padding: None, 27 | } 28 | } 29 | 30 | /// Set `element` option. 31 | pub fn element(mut self, value: AnyNodeRef) -> Self { 32 | self.element = value; 33 | self 34 | } 35 | 36 | /// Set `padding` option. 37 | pub fn padding(mut self, value: Padding) -> Self { 38 | self.padding = Some(value); 39 | self 40 | } 41 | } 42 | 43 | impl PartialEq for ArrowOptions { 44 | fn eq(&self, other: &Self) -> bool { 45 | self.element.get_untracked() == other.element.get_untracked() 46 | && self.padding == other.padding 47 | } 48 | } 49 | 50 | /// Arrow middleware. 51 | /// 52 | /// Provides data to position an inner element of the floating element so that it appears centered to the reference element. 53 | /// 54 | /// See [the Rust Floating UI book](https://floating-ui.rustforweb.org/middleware/arrow.html) for more documentation. 55 | #[derive(Clone, PartialEq)] 56 | pub struct Arrow { 57 | options: ArrowOptions, 58 | } 59 | 60 | impl Arrow { 61 | pub fn new(options: ArrowOptions) -> Self { 62 | Arrow { options } 63 | } 64 | } 65 | 66 | impl Middleware for Arrow { 67 | fn name(&self) -> &'static str { 68 | ARROW_NAME 69 | } 70 | 71 | fn compute( 72 | &self, 73 | state: MiddlewareState, 74 | ) -> MiddlewareReturn { 75 | let element = self 76 | .options 77 | .element 78 | .get_untracked() 79 | .and_then(|element| element.dyn_into::().ok()); 80 | 81 | if let Some(element) = element { 82 | CoreArrow::new(CoreArrowOptions { 83 | element, 84 | padding: self.options.padding.clone(), 85 | }) 86 | .compute(state) 87 | } else { 88 | MiddlewareReturn { 89 | x: None, 90 | y: None, 91 | data: None, 92 | reset: None, 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /packages/dom/src/platform.rs: -------------------------------------------------------------------------------- 1 | pub mod convert_offset_parent_relative_rect_to_viewport_relative_rect; 2 | pub mod get_client_length; 3 | pub mod get_client_rects; 4 | pub mod get_clipping_rect; 5 | pub mod get_dimensions; 6 | pub mod get_element_rects; 7 | pub mod get_offset_parent; 8 | pub mod get_scale; 9 | pub mod is_rtl; 10 | 11 | use floating_ui_core::{ 12 | ConvertOffsetParentRelativeRectToViewportRelativeRectArgs, GetClippingRectArgs, 13 | GetElementRectsArgs, Platform as CorePlatform, 14 | }; 15 | use floating_ui_utils::dom::get_document_element; 16 | use floating_ui_utils::{ 17 | ClientRectObject, Coords, Dimensions, ElementRects, Length, OwnedElementOrWindow, Rect, 18 | }; 19 | use web_sys::{Element, Window}; 20 | 21 | use crate::types::ElementOrVirtual; 22 | 23 | use self::convert_offset_parent_relative_rect_to_viewport_relative_rect::convert_offset_parent_relative_rect_to_viewport_relative_rect; 24 | use self::get_client_length::get_client_length; 25 | use self::get_client_rects::get_client_rects; 26 | use self::get_clipping_rect::get_clipping_rect; 27 | use self::get_dimensions::get_dimensions; 28 | use self::get_element_rects::get_element_rects; 29 | use self::get_offset_parent::get_offset_parent; 30 | use self::get_scale::get_scale; 31 | use self::is_rtl::is_rtl; 32 | 33 | #[derive(Debug)] 34 | pub struct Platform {} 35 | 36 | impl CorePlatform for Platform { 37 | fn get_element_rects(&self, args: GetElementRectsArgs) -> ElementRects { 38 | get_element_rects(self, args) 39 | } 40 | 41 | fn get_clipping_rect(&self, args: GetClippingRectArgs) -> Rect { 42 | get_clipping_rect(self, args) 43 | } 44 | 45 | fn get_dimensions(&self, element: &Element) -> Dimensions { 46 | get_dimensions(element) 47 | } 48 | 49 | fn convert_offset_parent_relative_rect_to_viewport_relative_rect( 50 | &self, 51 | args: ConvertOffsetParentRelativeRectToViewportRelativeRectArgs, 52 | ) -> Option { 53 | Some(convert_offset_parent_relative_rect_to_viewport_relative_rect(args)) 54 | } 55 | 56 | fn get_offset_parent( 57 | &self, 58 | element: &Element, 59 | ) -> Option> { 60 | Some(get_offset_parent(element, None)) 61 | } 62 | 63 | fn get_document_element(&self, element: &Element) -> Option { 64 | Some(get_document_element(Some(element.into()))) 65 | } 66 | 67 | fn get_client_rects(&self, element: ElementOrVirtual) -> Option> { 68 | Some(get_client_rects(element)) 69 | } 70 | 71 | fn is_rtl(&self, element: &Element) -> Option { 72 | Some(is_rtl(element)) 73 | } 74 | 75 | fn get_scale(&self, element: &Element) -> Option { 76 | Some(get_scale(element.into())) 77 | } 78 | 79 | fn get_client_length(&self, element: &Element, length: Length) -> Option { 80 | Some(get_client_length(element, length)) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/dom/src/utils/get_viewport_rect.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::{ 2 | Rect, Strategy, 3 | dom::{get_computed_style, get_document_element, get_window, is_web_kit}, 4 | }; 5 | use web_sys::Element; 6 | 7 | use crate::utils::get_window_scroll_bar_x::get_window_scroll_bar_x; 8 | 9 | // Safety check: ensure the scrollbar space is reasonable in case this calculation is affected by unusual styles. 10 | // Most scrollbars leave 15-18px of space. 11 | const SCROLLBAR_MAX: f64 = 25.0; 12 | 13 | pub fn get_viewport_rect(element: &Element, strategy: Strategy) -> Rect { 14 | let window = get_window(Some(element)); 15 | let html = get_document_element(Some(element.into())); 16 | let visual_viewport = window.visual_viewport(); 17 | 18 | let mut x = 0.0; 19 | let mut y = 0.0; 20 | let mut width = html.client_width() as f64; 21 | let mut height = html.client_height() as f64; 22 | 23 | if let Some(visual_viewport) = visual_viewport { 24 | width = visual_viewport.width(); 25 | height = visual_viewport.height(); 26 | 27 | let visual_viewport_based = is_web_kit(); 28 | if !visual_viewport_based || strategy == Strategy::Fixed { 29 | x = visual_viewport.offset_left(); 30 | y = visual_viewport.offset_top(); 31 | } 32 | } 33 | 34 | let window_scrollbar_x = get_window_scroll_bar_x(&html, None); 35 | // `overflow: hidden` + `scrollbar-gutter: stable` reduces the visual width of the , 36 | // but this is not considered in the size of `html.client_width`. 37 | if window_scrollbar_x <= 0.0 { 38 | let doc = html 39 | .owner_document() 40 | .expect("Element should have owner document."); 41 | let body = doc.body().expect("Document should have body."); 42 | let body_styles = get_computed_style(&body); 43 | let body_margin_inline = if doc.compat_mode() == "CSS1Compat" { 44 | body_styles 45 | .get_property_value("margin-left") 46 | .expect("Computed style should have margin left.") 47 | .parse::() 48 | .unwrap_or(0.0) 49 | + body_styles 50 | .get_property_value("margin-right") 51 | .expect("Computed style should have margin right.") 52 | .parse::() 53 | .unwrap_or(0.0) 54 | } else { 55 | 0.0 56 | }; 57 | let clipping_stable_scrollbar_width = 58 | ((html.client_width() as f64) - (body.client_width() as f64) - body_margin_inline) 59 | .abs(); 60 | 61 | if clipping_stable_scrollbar_width <= SCROLLBAR_MAX { 62 | width -= clipping_stable_scrollbar_width; 63 | } 64 | } else if window_scrollbar_x <= SCROLLBAR_MAX { 65 | // If the scrollbar is on the left, the width needs to be extended 66 | // by the scrollbar amount so there isn't extra space on the right. 67 | width += window_scrollbar_x; 68 | } 69 | 70 | Rect { 71 | x, 72 | y, 73 | width, 74 | height, 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/dom/src/utils/get_rect_relative_to_offset_parent.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::{ 2 | Coords, Rect, Strategy, 3 | dom::{ 4 | DomElementOrWindow, NodeScroll, get_document_element, get_node_name, get_node_scroll, 5 | is_overflow_element, 6 | }, 7 | }; 8 | 9 | use crate::{ 10 | types::ElementOrVirtual, 11 | utils::{ 12 | get_bounding_client_rect::get_bounding_client_rect, get_html_offset::get_html_offset, 13 | get_window_scroll_bar_x::get_window_scroll_bar_x, 14 | }, 15 | }; 16 | 17 | pub fn get_rect_relative_to_offset_parent( 18 | element_or_virtual: ElementOrVirtual, 19 | offset_parent: DomElementOrWindow, 20 | strategy: Strategy, 21 | ) -> Rect { 22 | let is_offset_parent_an_element = matches!(offset_parent, DomElementOrWindow::Element(_)); 23 | let document_element = get_document_element(Some((&offset_parent).into())); 24 | let is_fixed = strategy == Strategy::Fixed; 25 | let rect = get_bounding_client_rect( 26 | element_or_virtual, 27 | true, 28 | is_fixed, 29 | Some(offset_parent.clone()), 30 | ); 31 | 32 | let mut scroll = NodeScroll::new(0.0); 33 | let mut offsets = Coords::new(0.0); 34 | 35 | // If the scrollbar appears on the left (e.g. RTL systems). 36 | // Use Firefox with layout.scrollbar.side = 3 in about:config to test this. 37 | let set_left_rtl_scrollbar_offset = |offsets: &mut Coords| { 38 | offsets.x = get_window_scroll_bar_x(&document_element, None); 39 | }; 40 | 41 | #[allow(clippy::nonminimal_bool)] 42 | if is_offset_parent_an_element || (!is_offset_parent_an_element && !is_fixed) { 43 | if get_node_name((&offset_parent).into()) != "body" 44 | || is_overflow_element(&document_element) 45 | { 46 | scroll = get_node_scroll(offset_parent.clone()); 47 | } 48 | 49 | match offset_parent { 50 | DomElementOrWindow::Element(offset_parent) => { 51 | let offset_rect = get_bounding_client_rect( 52 | offset_parent.into(), 53 | true, 54 | is_fixed, 55 | Some(offset_parent.into()), 56 | ); 57 | offsets.x = offset_rect.x + offset_parent.client_left() as f64; 58 | offsets.y = offset_rect.y + offset_parent.client_top() as f64; 59 | } 60 | DomElementOrWindow::Window(_) => { 61 | set_left_rtl_scrollbar_offset(&mut offsets); 62 | } 63 | } 64 | } 65 | 66 | if is_fixed && !is_offset_parent_an_element { 67 | set_left_rtl_scrollbar_offset(&mut offsets); 68 | } 69 | 70 | let html_offset = if !is_offset_parent_an_element && !is_fixed { 71 | get_html_offset(&document_element, &scroll) 72 | } else { 73 | Coords::new(0.0) 74 | }; 75 | 76 | let x = rect.left + scroll.scroll_left - offsets.x - html_offset.x; 77 | let y = rect.top + scroll.scroll_top - offsets.y - html_offset.y; 78 | 79 | Rect { 80 | x, 81 | y, 82 | width: rect.width, 83 | height: rect.height, 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /packages/dom/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Rust port of [Floating UI](https://floating-ui.com/). 2 | //! 3 | //! This is the library to use Floating UI on the web, wrapping [`floating_ui_core`] with DOM interface logic. 4 | //! 5 | //! See [the Rust Floating UI book](https://floating-ui.rustforweb.org/) for more documenation. 6 | //! 7 | //! See [@floating-ui/dom](https://www.npmjs.com/package/@floating-ui/dom) for the original package. 8 | 9 | mod auto_update; 10 | mod middleware; 11 | mod platform; 12 | mod types; 13 | mod utils; 14 | 15 | pub use self::platform::Platform; 16 | pub use crate::auto_update::*; 17 | pub use crate::middleware::*; 18 | pub use crate::types::*; 19 | pub use floating_ui_core::{ 20 | Boundary, ComputePositionReturn, Derivable, DerivableFn, DetectOverflowOptions, ElementContext, 21 | Middleware, MiddlewareData, MiddlewareReturn, MiddlewareState, MiddlewareWithOptions, 22 | RootBoundary, 23 | }; 24 | #[doc(no_inline)] 25 | pub use floating_ui_utils::{ 26 | AlignedPlacement, Alignment, Axis, ClientRectObject, Coords, Dimensions, ElementRects, Length, 27 | Padding, PartialSideObject, Placement, Rect, Side, SideObject, Strategy, VirtualElement, dom, 28 | }; 29 | 30 | use floating_ui_core::{ 31 | ComputePositionConfig as CoreComputePositionConfig, compute_position as compute_position_core, 32 | }; 33 | use web_sys::Element; 34 | 35 | const PLATFORM: Platform = Platform {}; 36 | 37 | /// Options for [`compute_position`]. 38 | #[derive(Clone, Default)] 39 | pub struct ComputePositionConfig { 40 | /// Where to place the floating element relative to the reference element. 41 | /// 42 | /// Defaults to [`Placement::Bottom`]. 43 | pub placement: Option, 44 | 45 | /// The strategy to use when positioning the floating element. 46 | /// 47 | /// Defaults to [`Strategy::Absolute`]. 48 | pub strategy: Option, 49 | 50 | /// Vector of middleware objects to modify the positioning or provide data for rendering. 51 | /// 52 | /// Defaults to an empty vector. 53 | pub middleware: Option, 54 | } 55 | 56 | impl ComputePositionConfig { 57 | /// Set `placement` option. 58 | pub fn placement(mut self, value: Placement) -> Self { 59 | self.placement = Some(value); 60 | self 61 | } 62 | 63 | /// Set `strategy` option. 64 | pub fn strategy(mut self, value: Strategy) -> Self { 65 | self.strategy = Some(value); 66 | self 67 | } 68 | 69 | /// Set `middleware` option. 70 | pub fn middleware(mut self, value: MiddlewareVec) -> Self { 71 | self.middleware = Some(value); 72 | self 73 | } 74 | } 75 | 76 | /// Computes the `x` and `y` coordinates that will place the floating element next to a given reference element. 77 | pub fn compute_position( 78 | reference: ElementOrVirtual, 79 | floating: &Element, 80 | config: ComputePositionConfig, 81 | ) -> ComputePositionReturn { 82 | // TODO: cache 83 | 84 | compute_position_core( 85 | reference, 86 | floating, 87 | CoreComputePositionConfig { 88 | platform: &PLATFORM, 89 | placement: config.placement, 90 | strategy: config.strategy, 91 | middleware: config.middleware, 92 | }, 93 | ) 94 | } 95 | -------------------------------------------------------------------------------- /packages/dom/src/platform/convert_offset_parent_relative_rect_to_viewport_relative_rect.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_core::ConvertOffsetParentRelativeRectToViewportRelativeRectArgs; 2 | use floating_ui_utils::{ 3 | Coords, ElementOrWindow, Rect, Strategy, 4 | dom::{ 5 | NodeScroll, get_document_element, get_node_name, get_node_scroll, is_overflow_element, 6 | is_top_layer, 7 | }, 8 | }; 9 | use web_sys::{Element, Window}; 10 | 11 | use crate::{ 12 | platform::get_scale::get_scale, 13 | utils::{get_bounding_client_rect::get_bounding_client_rect, get_html_offset::get_html_offset}, 14 | }; 15 | 16 | pub fn convert_offset_parent_relative_rect_to_viewport_relative_rect( 17 | ConvertOffsetParentRelativeRectToViewportRelativeRectArgs { 18 | elements, 19 | rect, 20 | offset_parent, 21 | strategy, 22 | }: ConvertOffsetParentRelativeRectToViewportRelativeRectArgs, 23 | ) -> Rect { 24 | let is_fixed = strategy == Strategy::Fixed; 25 | let document_element = get_document_element( 26 | offset_parent 27 | .as_ref() 28 | .map(|offset_parent| offset_parent.into()), 29 | ); 30 | let top_layer = elements.is_some_and(|elements| is_top_layer(elements.floating)); 31 | 32 | if offset_parent 33 | .as_ref() 34 | .is_some_and(|offset_parent| match offset_parent { 35 | ElementOrWindow::Element(element) => *element == &document_element, 36 | ElementOrWindow::Window(_) => false, 37 | }) 38 | || (top_layer && is_fixed) 39 | { 40 | return rect; 41 | } 42 | 43 | let mut scroll = NodeScroll::new(0.0); 44 | let mut scale = Coords::new(1.0); 45 | let mut offsets = Coords::new(0.0); 46 | let is_offset_parent_an_element = 47 | offset_parent 48 | .as_ref() 49 | .is_some_and(|offset_parent| match offset_parent { 50 | ElementOrWindow::Element(_) => true, 51 | ElementOrWindow::Window(_) => false, 52 | }); 53 | 54 | #[allow(clippy::nonminimal_bool)] 55 | if is_offset_parent_an_element || (!is_offset_parent_an_element && !is_fixed) { 56 | if let Some(offset_parent) = offset_parent.as_ref() 57 | && (get_node_name(offset_parent.into()) != "body" 58 | || is_overflow_element(&document_element)) 59 | { 60 | scroll = get_node_scroll(offset_parent.into()); 61 | } 62 | 63 | if let Some(ElementOrWindow::Element(offset_parent)) = offset_parent { 64 | let offset_rect = get_bounding_client_rect(offset_parent.into(), false, false, None); 65 | scale = get_scale(offset_parent.into()); 66 | offsets.x = offset_rect.x + offset_parent.client_left() as f64; 67 | offsets.y = offset_rect.y + offset_parent.client_top() as f64; 68 | } 69 | } 70 | 71 | let html_offset = if !is_offset_parent_an_element && !is_fixed { 72 | get_html_offset(&document_element, &scroll) 73 | } else { 74 | Coords::new(0.0) 75 | }; 76 | 77 | Rect { 78 | x: rect.x * scale.x - scroll.scroll_left * scale.x + offsets.x + html_offset.x, 79 | y: rect.y * scale.y - scroll.scroll_top * scale.y + offsets.y + html_offset.y, 80 | width: rect.width * scale.x, 81 | height: rect.height * scale.y, 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: Website 2 | on: 3 | pull_request: {} 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: read 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: false 14 | 15 | jobs: 16 | book-test: 17 | name: Test Book 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v6 22 | 23 | - name: Set up Rust toolchain 24 | uses: actions-rust-lang/setup-rust-toolchain@v1 25 | with: 26 | target: wasm32-unknown-unknown 27 | 28 | - name: Install Cargo Binary Install 29 | uses: cargo-bins/cargo-binstall@main 30 | 31 | - name: Install mdBook 32 | run: cargo binstall --force -y mdbook mdbook-tabs mdbook-trunk 33 | 34 | - name: Run tests 35 | run: mdbook test 36 | working-directory: book 37 | 38 | book-build: 39 | name: Build Book 40 | needs: book-test 41 | runs-on: ubuntu-latest 42 | 43 | steps: 44 | - uses: actions/checkout@v6 45 | with: 46 | fetch-depth: 0 47 | 48 | - name: Set up Rust toolchain 49 | uses: actions-rust-lang/setup-rust-toolchain@v1 50 | with: 51 | target: wasm32-unknown-unknown 52 | 53 | - name: Install Cargo Binary Install 54 | uses: cargo-bins/cargo-binstall@main 55 | 56 | - name: Install mdBook and Trunk 57 | run: cargo binstall --force -y mdbook mdbook-tabs mdbook-trunk trunk 58 | 59 | - name: Install Node.js dependencies 60 | run: npm ci 61 | 62 | - name: Build Book 63 | run: mdbook build 64 | working-directory: book 65 | 66 | - name: Combine Book Outputs 67 | run: mdbook-trunk combine 68 | working-directory: book 69 | 70 | - name: Upload artifact 71 | uses: actions/upload-artifact@v6 72 | with: 73 | name: book 74 | path: book/dist 75 | retention-days: 1 76 | if-no-files-found: error 77 | 78 | deploy: 79 | name: Deploy 80 | needs: book-build 81 | if: github.ref == 'refs/heads/main' 82 | runs-on: ubuntu-latest 83 | 84 | permissions: 85 | contents: read 86 | pages: write 87 | id-token: write 88 | 89 | steps: 90 | - uses: actions/checkout@v6 91 | with: 92 | fetch-depth: 0 93 | 94 | - name: Download artifacts 95 | uses: actions/download-artifact@v7 96 | with: 97 | path: dist 98 | merge-multiple: true 99 | 100 | - name: Setup Pages 101 | uses: actions/configure-pages@v5 102 | 103 | - name: Upload artifact 104 | uses: actions/upload-pages-artifact@v4 105 | with: 106 | path: dist 107 | 108 | - name: Deploy to GitHub Pages 109 | id: deployment 110 | uses: actions/deploy-pages@v4 111 | -------------------------------------------------------------------------------- /book-examples/src/positioning/shift.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_leptos::{ 2 | Boundary, DetectOverflowOptions, MiddlewareVec, Offset, OffsetOptions, Padding, 3 | PartialSideObject, Placement, RootBoundary, Shift, ShiftOptions, 4 | }; 5 | use leptos::prelude::*; 6 | use leptos_node_ref::AnyNodeRef; 7 | use send_wrapper::SendWrapper; 8 | 9 | use crate::{ 10 | components::{Chrome, Floating, GridItem, Reference, Scrollable}, 11 | utils::rem_to_px, 12 | }; 13 | 14 | #[component] 15 | pub fn ShiftDemo() -> impl IntoView { 16 | let boundary_ref = AnyNodeRef::new(); 17 | 18 | Effect::new(move |_| { 19 | if let Some(boundary) = boundary_ref.get() { 20 | boundary 21 | .first_element_child() 22 | .expect("First element child should exist.") 23 | .set_scroll_top(rem_to_px(200.0 / 16.0) as i32); 24 | } 25 | }); 26 | 27 | view! { 28 | 33 | 39 | 64 | Popover 65 |
66 | } 67 | reference=move |node_ref| view! { 68 | 69 | } 70 | /> 71 | 72 | 73 | } 74 | /> 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/rust,node 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=rust,node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | .pnpm-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # Snowpack dependency directory (https://snowpack.dev/) 50 | web_modules/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional stylelint cache 62 | .stylelintcache 63 | 64 | # Microbundle cache 65 | .rpt2_cache/ 66 | .rts2_cache_cjs/ 67 | .rts2_cache_es/ 68 | .rts2_cache_umd/ 69 | 70 | # Optional REPL history 71 | .node_repl_history 72 | 73 | # Output of 'npm pack' 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | .yarn-integrity 78 | 79 | # dotenv environment variable files 80 | .env 81 | .env.development.local 82 | .env.test.local 83 | .env.production.local 84 | .env.local 85 | 86 | # parcel-bundler cache (https://parceljs.org/) 87 | .cache 88 | .parcel-cache 89 | 90 | # Next.js build output 91 | .next 92 | out 93 | 94 | # Nuxt.js build / generate output 95 | .nuxt 96 | dist 97 | 98 | # Gatsby files 99 | .cache/ 100 | # Comment in the public line in if your project uses Gatsby and not Next.js 101 | # https://nextjs.org/blog/next-9-1#public-directory-support 102 | # public 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # vuepress v2.x temp and cache directory 108 | .temp 109 | 110 | # Docusaurus cache and generated files 111 | .docusaurus 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v2 129 | .yarn/cache 130 | .yarn/unplugged 131 | .yarn/build-state.yml 132 | .yarn/install-state.gz 133 | .pnp.* 134 | 135 | ### Node Patch ### 136 | # Serverless Webpack directories 137 | .webpack/ 138 | 139 | # Optional stylelint cache 140 | 141 | # SvelteKit build / generate output 142 | .svelte-kit 143 | 144 | ### Rust ### 145 | # Generated by Cargo 146 | # will have compiled files and executables 147 | debug/ 148 | target/ 149 | 150 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 151 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 152 | # Cargo.lock 153 | 154 | # These are backup files generated by rustfmt 155 | **/*.rs.bk 156 | 157 | # MSVC Windows builds of rustc generate these, which store debugging information 158 | *.pdb 159 | 160 | # End of https://www.toptal.com/developers/gitignore/api/rust,node 161 | 162 | # mdBook 163 | book/book/ 164 | 165 | # Tailwind CSS ouput 166 | tailwind.output.css 167 | -------------------------------------------------------------------------------- /packages/leptos/tests/visual/src/spec/placement.rs: -------------------------------------------------------------------------------- 1 | use convert_case::{Case, Casing}; 2 | use floating_ui_leptos::{Placement, UseFloatingOptions, UseFloatingReturn, use_floating}; 3 | use leptos::prelude::*; 4 | use leptos_node_ref::AnyNodeRef; 5 | 6 | use crate::utils::{all_placements::ALL_PLACEMENTS, use_size::use_size}; 7 | 8 | #[component] 9 | pub fn Placement() -> impl IntoView { 10 | let reference_ref = AnyNodeRef::new(); 11 | let floating_ref = AnyNodeRef::new(); 12 | 13 | let (rtl, set_rtl) = signal(false); 14 | let (placement, set_placement) = signal(Placement::Bottom); 15 | 16 | let UseFloatingReturn { 17 | floating_styles, 18 | update, 19 | .. 20 | } = use_floating( 21 | reference_ref, 22 | floating_ref, 23 | UseFloatingOptions::default() 24 | .placement(placement) 25 | .while_elements_mounted_auto_update(), 26 | ); 27 | 28 | let (size, set_size) = use_size(None, None); 29 | 30 | Effect::new(move || { 31 | _ = rtl.get(); 32 | update(); 33 | }); 34 | 35 | view! { 36 |

Placement

37 |

38 | The floating element should be correctly positioned when given each of the 12 placements. 39 |

40 |
45 |
46 | Reference 47 |
48 |
49 | Floating 50 |
51 |
52 | 53 |
54 | 55 | 65 |
66 | 67 |
68 | 81 | {format!("{local_placement:?}").to_case(Case::Kebab)} 82 | 83 | } 84 | /> 85 |
86 | 87 |

RTL

88 |
89 | 102 | {format!("{value}")} 103 | 104 | } 105 | /> 106 |
107 | } 108 | } 109 | -------------------------------------------------------------------------------- /packages/dom/src/middleware.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_core::middleware::{ 2 | Arrow as CoreArrow, AutoPlacement as CoreAutoPlacement, Flip as CoreFlip, Hide as CoreHide, 3 | Inline as CoreInline, Offset as CoreOffset, Shift as CoreShift, Size as CoreSize, 4 | }; 5 | use web_sys::{Element, Window}; 6 | 7 | pub use floating_ui_core::middleware::{ 8 | ARROW_NAME, AUTO_PLACEMENT_NAME, ApplyState, ArrowData, ArrowOptions, AutoPlacementData, 9 | AutoPlacementDataOverflow, AutoPlacementOptions, CrossAxis, DefaultLimiter, FLIP_NAME, 10 | FallbackStrategy, FlipData, FlipDataOverflow, FlipOptions, HIDE_NAME, HideData, HideOptions, 11 | HideStrategy, INLINE_NAME, InlineOptions, LimitShift, LimitShiftOffset, LimitShiftOffsetValues, 12 | LimitShiftOptions, OFFSET_NAME, OffsetData, OffsetOptions, OffsetOptionsValues, SHIFT_NAME, 13 | SIZE_NAME, ShiftData, ShiftOptions, SizeOptions, 14 | }; 15 | 16 | /// Arrow middleware. 17 | /// 18 | /// Provides data to position an inner element of the floating element so that it appears centered to the reference element. 19 | /// 20 | /// See [the Rust Floating UI book](https://floating-ui.rustforweb.org/middleware/arrow.html) for more documentation. 21 | pub type Arrow<'a> = CoreArrow<'a, Element, Window>; 22 | 23 | /// Auto placement middleware. 24 | /// 25 | /// Optimizes the visibility of the floating element by choosing the placement that has the most space available automatically, 26 | /// without needing to specify a preferred placement. 27 | /// 28 | /// Alternative to [`Flip`]. 29 | /// 30 | /// See [the Rust Floating UI book](https://floating-ui.rustforweb.org/middleware/auto-placement.html) for more documentation. 31 | pub type AutoPlacement<'a> = CoreAutoPlacement<'a, Element, Window>; 32 | 33 | /// Flip middleware. 34 | /// 35 | /// Optimizes the visibility of the floating element by flipping the `placement` in order to keep it in view when the preferred placement(s) will overflow the clipping boundary. 36 | /// Alternative to [`AutoPlacement`]. 37 | /// 38 | /// See [the Rust Floating UI book](https://floating-ui.rustforweb.org/middleware/flip.html) for more documentation. 39 | pub type Flip<'a> = CoreFlip<'a, Element, Window>; 40 | 41 | /// Hide middleware. 42 | /// 43 | /// Provides data to hide the floating element in applicable situations, 44 | /// such as when it is not in the same clipping context as the reference element. 45 | /// 46 | /// See [the Rust Floating UI book](https://floating-ui.rustforweb.org/middleware/hide.html) for more documentation. 47 | pub type Hide<'a> = CoreHide<'a, Element, Window>; 48 | 49 | /// Inline middleware. 50 | /// 51 | /// Provides improved positioning for inline reference elements that can span over multiple lines, such as hyperlinks or range selections. 52 | /// 53 | /// See [the Rust Floating UI book](https://floating-ui.rustforweb.org/middleware/inline.html) for more documentation. 54 | pub type Inline<'a> = CoreInline<'a, Element, Window>; 55 | 56 | /// Offset middleware. 57 | /// 58 | /// Modifies the placement by translating the floating element along the specified axes. 59 | /// 60 | /// See [the Rust Floating UI book](https://floating-ui.rustforweb.org/middleware/offset.html) for more documentation. 61 | pub type Offset<'a> = CoreOffset<'a, Element, Window>; 62 | 63 | /// Shift middleware. 64 | /// 65 | /// Optimizes the visibility of the floating element by shifting it in order to keep it in view when it will overflow the clipping boundary. 66 | /// 67 | /// See [the Rust Floating UI book](https://floating-ui.rustforweb.org/middleware/shift.html) for more documentation. 68 | pub type Shift<'a> = CoreShift<'a, Element, Window>; 69 | 70 | /// Size middleware. 71 | /// 72 | /// Provides data that allows you to change the size of the floating element - 73 | /// for instance, prevent it from overflowing the clipping boundary or match the width of the reference element. 74 | /// 75 | /// See [the Rust Floating UI book](https://floating-ui.rustforweb.org/middleware/size.html) for more documentation. 76 | pub type Size<'a> = CoreSize<'a, Element, Window>; 77 | -------------------------------------------------------------------------------- /book/src/virtual-elements.md: -------------------------------------------------------------------------------- 1 | # Virtual Elements 2 | 3 | Position a floating element relative to a custom reference area, useful for context menus, range selections, following the cursor, and more. 4 | 5 | ## Usage 6 | 7 | A virtual element must implement the `VirtualElement` trait. 8 | 9 | ```rust,ignore 10 | pub trait VirtualElement: Clone + PartialEq { 11 | fn get_bounding_client_rect(&self) -> ClientRectObject; 12 | 13 | fn get_client_rects(&self) -> Option>; 14 | 15 | fn context_element(&self) -> Option; 16 | } 17 | ``` 18 | 19 | A default implementation called `DefaultVirtualElement` is provided for convience. 20 | 21 | ```rust,ignore 22 | let virtual_el: Box> = Box::new( 23 | DefaultVirtualElement::new(get_bounding_client_rect) 24 | .get_client_rects(get_client_rects) 25 | .context_element(context_element), 26 | ); 27 | ``` 28 | 29 | {{#tabs global="package" }} 30 | {{#tab name="Core" }} 31 | 32 | ```rust,ignore 33 | compute_position(virtual_el.into(), floating_el, ComputePositionConfig::new(platform)) 34 | ``` 35 | 36 | {{#endtab }} 37 | {{#tab name="DOM" }} 38 | 39 | ```rust,ignore 40 | compute_position(virtual_el.into(), floating_el, ComputePositionConfig::default()) 41 | ``` 42 | 43 | {{#endtab }} 44 | {{#tab name="Dioxus" }} 45 | 46 | ```rust,ignore 47 | use_floating(virtual_el.into(), floating_el, UseFloatingOptions::default()) 48 | ``` 49 | 50 | {{#endtab }} 51 | {{#tab name="Leptos" }} 52 | 53 | ```rust,ignore 54 | use_floating(virtual_el.into(), floating_el, UseFloatingOptions::default()) 55 | ``` 56 | 57 | {{#endtab }} 58 | {{#tab name="Yew" }} 59 | 60 | ```rust,ignore 61 | use_floating(virtual_el.into(), floating_el, UseFloatingOptions::default()) 62 | ``` 63 | 64 | {{#endtab }} 65 | {{#endtabs }} 66 | 67 | ### `get_bounding_client_rect` 68 | 69 | The most basic virtual element is a plain object that has a `get_bounding_client_rect` method, which mimics a real element's one: 70 | 71 | ```rust,ignore 72 | // A virtual element that is 20 x 20 px starting from (0, 0) 73 | let virtual_el: Box> = Box::new( 74 | DefaultVirtualElement::new(Rc::new(|| { 75 | ClientRectObject { 76 | x: 0.0, 77 | y: 0.0, 78 | top: 0.0, 79 | left: 0.0, 80 | bottom: 20.0, 81 | right: 20.0, 82 | width: 20.0, 83 | height: 20.0, 84 | } 85 | })) 86 | ); 87 | ``` 88 | 89 | 90 | 91 | 92 | ### `context_element` 93 | 94 | This option is useful if your `get_bounding_client_rect` method is derived from a real element, to ensure clipping and position update detection works as expected. 95 | 96 | ```rust,ignore 97 | let virtual_el: Box> = Box::new( 98 | DefaultVirtualElement::new(get_bounding_client_rect) 99 | .context_element( 100 | web_sys::window() 101 | .expext("Window should exist.") 102 | .document() 103 | .expect("Document should exist.") 104 | .query_selector("#context") 105 | .expect("Document should be queried.") 106 | .expect("Element should exist."), 107 | ), 108 | ); 109 | ``` 110 | 111 | ### `get_client_rects` 112 | 113 | This option is useful when using [range selections](https://developer.mozilla.org/en-US/docs/Web/API/Range) and the `Inline` middleware. 114 | 115 | ```rust,ignore 116 | let virtual_el: Box> = Box::new( 117 | DefaultVirtualElement::new(|| range.get_bounding_client_rect().into()) 118 | .get_client_rects(|| ClientRectObject::from_dom_rect_list( 119 | range.get_client_rects().expect("Range should have client rects."), 120 | )), 121 | ); 122 | ``` 123 | 124 | ## See Also 125 | 126 | - [Floating UI documentation](https://floating-ui.com/docs/virtual-elements) 127 | -------------------------------------------------------------------------------- /packages/dom/src/platform/get_offset_parent.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_utils::OwnedElementOrWindow; 2 | use floating_ui_utils::dom::{ 3 | DomNodeOrWindow, get_computed_style, get_containing_block, get_document_element, 4 | get_parent_node, get_window, is_containing_block, is_element, is_html_element, 5 | is_last_traversable_node, is_table_element, is_top_layer, 6 | }; 7 | use web_sys::Window; 8 | use web_sys::{Element, HtmlElement, wasm_bindgen::JsCast}; 9 | 10 | use crate::utils::is_static_positioned::is_static_positioned; 11 | 12 | pub type Polyfill = Box Option>; 13 | 14 | pub fn get_true_offset_parent(element: &Element, polyfill: &Option) -> Option { 15 | if !is_html_element(element) 16 | || get_computed_style(element) 17 | .get_property_value("position") 18 | .expect("Computed style should have position.") 19 | == "fixed" 20 | { 21 | None 22 | } else { 23 | let element = element.unchecked_ref::(); 24 | 25 | if let Some(polyfill) = polyfill { 26 | polyfill(element) 27 | } else { 28 | let raw_offset_parent = element.offset_parent(); 29 | 30 | // Firefox returns the element as the offsetParent if it's non-static, while Chrome and Safari return the element. 31 | // The element must be used to perform the correct calculations even if the element is non-static. 32 | if let Some(raw_offset_parent) = raw_offset_parent.as_ref() 33 | && get_document_element(Some(DomNodeOrWindow::Node(raw_offset_parent))) 34 | == *raw_offset_parent 35 | { 36 | return Some( 37 | raw_offset_parent 38 | .owner_document() 39 | .expect("Element should have owner document.") 40 | .body() 41 | .expect("Document should have body.") 42 | .unchecked_into::(), 43 | ); 44 | } 45 | 46 | raw_offset_parent 47 | } 48 | } 49 | } 50 | 51 | /// Gets the closest ancestor positioned element. Handles some edge cases, such as table ancestors and cross browser bugs. 52 | pub fn get_offset_parent( 53 | element: &Element, 54 | polyfill: Option, 55 | ) -> OwnedElementOrWindow { 56 | let window = get_window(Some(element)); 57 | 58 | if is_top_layer(element) { 59 | return OwnedElementOrWindow::Window(window); 60 | } 61 | 62 | if !is_html_element(element) { 63 | let mut svg_offset_parent = Some(get_parent_node(element)); 64 | while let Some(parent) = svg_offset_parent.as_ref() { 65 | if is_last_traversable_node(parent) { 66 | break; 67 | } 68 | 69 | if is_element(parent) { 70 | let element = parent.unchecked_ref::(); 71 | if !is_static_positioned(element) { 72 | return OwnedElementOrWindow::Element(element.clone()); 73 | } 74 | } 75 | svg_offset_parent = Some(get_parent_node(parent)) 76 | } 77 | return OwnedElementOrWindow::Window(window); 78 | } 79 | 80 | let mut offset_parent = get_true_offset_parent(element, &polyfill); 81 | 82 | while let Some(parent) = offset_parent.as_ref() { 83 | if is_table_element(parent) && is_static_positioned(parent) { 84 | offset_parent = get_true_offset_parent(parent, &polyfill); 85 | } else { 86 | break; 87 | } 88 | } 89 | 90 | if let Some(parent) = offset_parent.as_ref() 91 | && is_last_traversable_node(parent) 92 | && is_static_positioned(parent) 93 | && !is_containing_block(parent.into()) 94 | { 95 | return OwnedElementOrWindow::Window(window); 96 | } 97 | 98 | offset_parent 99 | .map(OwnedElementOrWindow::Element) 100 | .or(get_containing_block(element) 101 | .map(|element| OwnedElementOrWindow::Element(element.into()))) 102 | .unwrap_or(OwnedElementOrWindow::Window(window)) 103 | } 104 | -------------------------------------------------------------------------------- /packages/leptos/example/src/app.rs: -------------------------------------------------------------------------------- 1 | use floating_ui_leptos::{ 2 | ARROW_NAME, Arrow, ArrowData, ArrowOptions, DetectOverflowOptions, Flip, FlipOptions, 3 | MiddlewareVec, Offset, OffsetOptions, Padding, Placement, Shift, ShiftOptions, Side, 4 | UseFloatingOptions, UseFloatingReturn, use_floating, 5 | }; 6 | use leptos::prelude::*; 7 | use leptos_node_ref::AnyNodeRef; 8 | use send_wrapper::SendWrapper; 9 | 10 | #[component] 11 | pub fn App() -> impl IntoView { 12 | let reference_ref = AnyNodeRef::new(); 13 | let floating_ref = AnyNodeRef::new(); 14 | let arrow_ref = AnyNodeRef::new(); 15 | 16 | let (open, set_open) = signal(false); 17 | 18 | let middleware: MiddlewareVec = vec![ 19 | Box::new(Offset::new(OffsetOptions::Value(6.0))), 20 | Box::new(Flip::new(FlipOptions::default())), 21 | Box::new(Shift::new(ShiftOptions::default().detect_overflow( 22 | DetectOverflowOptions::default().padding(Padding::All(5.0)), 23 | ))), 24 | Box::new(Arrow::new(ArrowOptions::new(arrow_ref))), 25 | ]; 26 | 27 | let UseFloatingReturn { 28 | placement, 29 | floating_styles, 30 | middleware_data, 31 | .. 32 | } = use_floating( 33 | reference_ref, 34 | floating_ref, 35 | UseFloatingOptions::default() 36 | .open(open) 37 | .placement(Placement::Top) 38 | .middleware(SendWrapper::new(middleware)) 39 | .while_elements_mounted_auto_update(), 40 | ); 41 | 42 | let static_side = Signal::derive(move || placement.get().side().opposite()); 43 | let arrow_data = 44 | Signal::derive(move || -> Option { middleware_data.get().get_as(ARROW_NAME) }); 45 | let arrow_x = Signal::derive(move || { 46 | arrow_data 47 | .get() 48 | .and_then(|arrow_data| arrow_data.x.map(|x| format!("{x}px"))) 49 | }); 50 | let arrow_y = Signal::derive(move || { 51 | arrow_data 52 | .get() 53 | .and_then(|arrow_data| arrow_data.y.map(|y| format!("{y}px"))) 54 | }); 55 | 56 | view! { 57 | 68 | 69 |