diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index b1e6e05..cb67468 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -23,20 +23,27 @@ jobs: components: rustfmt, clippy, rust-src - name: Cache uses: Swatinem/rust-cache@v2 + - name: Check function count badge run: python3 docs/generate_count_badge.py --check - name: Check version in docs run: python3 docs/add_version_to_docs.py --check + - name: Check formatting run: cargo fmt --check - name: Clippy run: cargo clippy --features docs,math --tests -- -D warnings + - name: Run tests (general) run: cargo test --features math,docs,ssr - - name: Run tests (axum) - run: cargo test --features math,docs,ssr,axum --doc use_cookie::use_cookie - - name: Run tests (actix) - run: cargo test --features math,docs,ssr,actix --doc use_cookie::use_cookie + - name: Run tests (axum) use_cookie + run: cargo test --features math,docs,ssr,axum --doc use_cookie + - name: Run tests (axum) use_locale + run: cargo test --features math,docs,ssr,axum --doc use_locale + - name: Run tests (actix) use_cookie + run: cargo test --features math,docs,ssr,actix --doc use_cookie + - name: Run tests (actix) use_locale + run: cargo test --features math,docs,ssr,actix --doc use_locale #### mdbook - name: Install mdbook I diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 68ee5cf..1ae502a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,5 +1,11 @@ on: pull_request: + branches: + - main + paths: + - "**" + - "!/*.md" + - "!/**.md" workflow_dispatch: name: Tests @@ -20,13 +26,18 @@ jobs: components: rustfmt, clippy, rust-src - name: Cache uses: Swatinem/rust-cache@v2 - - name: Check formatting - run: cargo fmt --check - - name: Clippy - run: cargo clippy --features docs,math --tests -- -D warnings + - name: Run tests (general) run: cargo test --features math,docs,ssr - - name: Run tests (axum) - run: cargo test --features math,docs,ssr,axum --doc use_cookie::use_cookie - - name: Run tests (actix) - run: cargo test --features math,docs,ssr,actix --doc use_cookie::use_cookie + + - name: Run tests (axum) use_cookie + run: cargo test --features math,docs,ssr,axum --doc use_cookie + + - name: Run tests (axum) use_locale + run: cargo test --features math,docs,ssr,axum --doc use_locale + + - name: Run tests (actix) use_cookie + run: cargo test --features math,docs,ssr,actix --doc use_cookie + + - name: Run tests (actix) use_locale + run: cargo test --features math,docs,ssr,actix --doc use_locale diff --git a/.idea/leptos-use.iml b/.idea/leptos-use.iml index b65a8a2..a9c24d6 100644 --- a/.idea/leptos-use.iml +++ b/.idea/leptos-use.iml @@ -78,6 +78,8 @@ + + diff --git a/CHANGELOG.md b/CHANGELOG.md index cba1230..1dea579 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,56 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] - + +### New Functions 🚀 + +- `use_prefers_reduced_motion` + +## [0.12.0] - 2024-08-14 + +> Make sure you also update `cargo-leptos` to the latest version if you use that. + +### Breaking Changes 🛠 + +- Updated to web_sys 0.3.70 which unfortunately is breaking some things. +- `use_clipboard` doesn't need the unstable flags anymore. +- `use_locale` now uses `unic_langid::LanguageIdentifier` and proper locale matching (thanks to @mondeja). +- Removed `UseMouseEventExtractorDefault` and reworked `UseMouseCoordType` (thanks to @carloskiki) +- `use_preferred_dark` and `use_color_mode` now try to read the `Sec-CH-Prefers-Color-Scheme` header in SSR. This brings + the necessity to enable an additional feature for them (`axum` / `actix` / `spin`). + +### Fixes 🍕 + +- Fixed the codec chapter in the book to refer to crate `codee`. + +## [0.11.4] - 2024-08-12 + +### New Features 🚀 + +- `use_web_notification` now supports the options `renotify`, `silent` and `image` (thanks to @hcandelaria). +- `sync_signal` no supports the options `assign_ltr` and `assign_rtl`. + +## [0.11.3] - 2024-07-31 + +### Fix 🍕 + +- Made `use_timeout_fn` SSR-safe + +## [0.11.2] - 2024-07-30 + +### Change 🔥 + +- `use_locale` has now a supported locale list. + +## (yanked) [0.11.1] - 2024-07-28 + +### New Functions 🚀 + +- `use_locale` (thanks to @BrandonDyer64) +- `use_locales` (thanks to @BrandonDyer64) +- `header` – Standard implementations for reading a header on the server. + ## [0.11.0] - 2024-07-27 ### Breaking Changes 🛠 @@ -37,7 +87,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 the DOM controlled by a value from storage. This leads to hydration errors which can be fixed by setting this new option to `true`. - `cookie::SameSite` is now re-exported -- Changing the signal returned by `use_cookie` now tries and changes the headers during SSR. +- Changing the signal returned by `use_cookie` now tries and changes the headers during SSR. - New book chapter about codecs - The macro `use_derive_signal!` is now exported (thanks to @mscofield0). @@ -72,7 +122,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The new `UseWebSocketOptions::on_message` takes a `&T`. - `UseWebSocketOptions::on_error` now takes a `UseWebSocketError` instead of a `web_sys::Event`. - `use_storage` now always saves the default value to storage if the key doesn't exist yet. -- Renamed `BreakpointsSematic` to `BreakpointsSemantic` and `breakpoints_sematic` to `breakpoints_semantic` +- Renamed `BreakpointsSematic` to `BreakpointsSemantic` and `breakpoints_sematic` to `breakpoints_semantic` (note the `n`) (thanks to @mondeja). ### Fixes 🍕 diff --git a/Cargo.toml b/Cargo.toml index 619a4a7..55093b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "leptos-use" -version = "0.11.0" +version = "0.12.0" edition = "2021" authors = ["Marc-Stefan Cassola"] categories = ["gui", "web-programming"] -description = "Collection of essential Leptos utilities inspired by SolidJS USE / VueUse" +description = "Collection of essential Leptos utilities inspired by React-Use / VueUse / SolidJS-USE" exclude = ["examples/", "tests/"] keywords = ["leptos", "utilities"] license = "MIT OR Apache-2.0" @@ -14,14 +14,14 @@ homepage = "https://leptos-use.rs" [dependencies] actix-web = { version = "4", optional = true, default-features = false } -async-trait = "0.1" +async-trait = { version = "0.1", optional = true } cfg-if = "1" -codee = "0.1" -cookie = { version = "0.18", features = ["percent-encode"] } +codee = { version = "0.1", optional = true } +cookie = { version = "0.18", features = ["percent-encode"], optional = true } default-struct-builder = "0.5" -futures-util = "0.3" -gloo-timers = { version = "0.3", features = ["futures"] } -gloo-utils = { version = "0.2" } +futures-util = { version = "0.3", optional = true } +gloo-timers = { version = "0.3", optional = true, features = ["futures"] } +gloo-utils = { version = "0.2", optional = true } http1 = { version = "1", optional = true, package = "http" } http0_2 = { version = "0.2", optional = true, package = "http" } js-sys = "0.3" @@ -34,116 +34,317 @@ num = { version = "0.4", optional = true } paste = "1" send_wrapper = "0.6.0" thiserror = "1" -wasm-bindgen = "0.2.92" +unic-langid = { version = "0.9", optional = true } +wasm-bindgen = "=0.2.93" wasm-bindgen-futures = "0.4" - -[dependencies.web-sys] -version = "0.3" -features = [ - "AddEventListenerOptions", - "BinaryType", - "BroadcastChannel", - "Coordinates", - "Clipboard", - "CloseEvent", - "CssStyleDeclaration", - "CustomEvent", - "CustomEventInit", - "DisplayMediaStreamConstraints", - "DomRect", - "DomRectReadOnly", - "DataTransfer", - "DragEvent", - "Element", - "EventListener", - "EventListenerOptions", - "EventSource", - "EventSourceInit", - "EventTarget", - "File", - "FileList", - "Geolocation", - "HtmlDocument", - "HtmlElement", - "HtmlLinkElement", - "HtmlStyleElement", - "IntersectionObserver", - "IntersectionObserverInit", - "IntersectionObserverEntry", - "Location", - "MediaDevices", - "MediaQueryList", - "MediaStream", - "MediaStreamConstraints", - "MediaStreamTrack", - "MessageEvent", - "MouseEvent", - "MutationObserver", - "MutationObserverInit", - "MutationRecord", - "Navigator", - "NodeList", - "Notification", - "NotificationDirection", - "NotificationOptions", - "NotificationPermission", - "Permissions", - "PermissionState", - "PermissionStatus", - "PointerEvent", - "Position", - "PositionError", - "PositionOptions", - "ReadableStream", - "ReadableStreamDefaultReader", - "ReadableStreamGetReaderOptions", - "ReadableStreamReaderMode", - "ResizeObserver", - "ResizeObserverBoxOptions", - "ResizeObserverEntry", - "ResizeObserverOptions", - "ResizeObserverSize", - "ScrollBehavior", - "ScrollToOptions", - "ServiceWorker", - "ServiceWorkerContainer", - "ServiceWorkerRegistration", - "ServiceWorkerState", - "Storage", - "StorageEvent", - "Touch", - "TouchEvent", - "TouchList", - "Url", - "UrlSearchParams", - "VisibilityState", - "WebSocket", - "WebTransport", - "WebTransportOptions", - "WebTransportDatagramDuplexStream", - "WebTransportBidirectionalStream", - "Window", - "WebTransportReceiveStream", - "WebTransportSendStream", - "WritableStream", - "WritableStreamDefaultWriter", -] +web-sys = { version = "=0.3.70", optional = true } [dev-dependencies] +codee = { version = "0.1", features = ["json_serde", "msgpack_serde", "base64", "prost"] } getrandom = { version = "0.2", features = ["js"] } leptos_meta = { git = "https://github.com/leptos-rs/leptos" } rand = "0.8" -codee = { version = "0.1", features = ["json_serde", "msgpack_serde", "base64", "prost"] } serde = { version = "1", features = ["derive"] } +unic-langid = { version = "0.9", features = ["macros"] } [features] +default = [ + "is_err", + "is_none", + "is_ok", + "is_some", + "on_click_outside", + "signal_debounced", + "signal_throttled", + "storage", + "sync_signal", + "use_active_element", + "use_breakpoints", + "use_broadcast_channel", + "use_clipboard", + "use_color_mode", + "use_cookie", + "use_css_var", + "use_cycle_list", + "use_debounce_fn", + "use_device_orientation", + "use_device_pixel_ratio", + "use_display_media", + "use_document", + "use_document_visibility", + "use_draggable", + "use_drop_zone", + "use_element_bounding", + "use_element_hover", + "use_element_size", + "use_element_visibility", + "use_event_listener", + "use_event_source", + "use_favicon", + "use_geolocation", + "use_idle", + "use_infinite_scroll", + "use_intersection_observer", + "use_interval", + "use_interval_fn", + "use_intl_number_format", + "use_locale", + "use_locales", + "use_media_query", + "use_mouse", + "use_mouse_in_element", + "use_mutation_observer", + "use_permission", + "use_preferred_contrast", + "use_preferred_dark", + "use_prefers_reduced_motion", + "use_raf_fn", + "use_resize_observer", + "use_scroll", + "use_service_worker", + "use_sorted", + "use_supported", + "use_throttle_fn", + "use_timeout_fn", + "use_timestamp", + "use_to_string", + "use_user_media", + "use_web_notification", + "use_websocket", + "use_window", + "use_window_focus", + "use_window_scroll", + "watch_debounced", + "watch_pausable", + "watch_throttled", + "watch_with_options", + "whenever" +] actix = ["dep:actix-web", "dep:leptos_actix", "dep:http0_2"] axum = ["dep:leptos_axum", "dep:http1"] -docs = [] +docs = ["dep:web-sys"] +element = ["use_document", "use_window", "dep:web-sys", "web-sys/EventTarget"] +is = ["use_window"] +is_err = [] +is_none = [] +is_ok = [] +is_some = [] math = ["num"] +on_click_outside = ["use_event_listener", "is"] +signal_debounced = ["use_debounce_fn"] +signal_throttled = ["use_throttle_fn"] spin = ["dep:leptos-spin", "dep:http1"] ssr = [] +storage = [ + "use_event_listener", + "use_window", + "watch_with_options", + "dep:web-sys", + "dep:codee", + "web-sys/CustomEventInit", + "web-sys/Storage" +] +sync_signal = [] +use_active_element = ["use_event_listener"] +use_breakpoints = ["use_media_query"] +use_broadcast_channel = [ + "use_event_listener", + "use_supported", + "dep:codee", + "web-sys/BroadcastChannel", +] +use_clipboard = [ + "use_event_listener", + "use_permission", + "use_supported", + "use_timeout_fn", + "web-sys/Clipboard", +] +use_color_mode = [ + "use_cookie", + "use_cycle_list", + "use_preferred_dark", + "storage", + "sync_signal" +] +use_cookie = [ + "use_broadcast_channel", + "watch_pausable", + "dep:cookie", + "web-sys/HtmlDocument", +] +use_css_var = [ + "use_mutation_observer", + "watch_with_options", +] +use_cycle_list = [] +use_debounce_fn = [] +use_device_orientation = ["use_event_listener", "use_supported"] +use_device_pixel_ratio = ["use_event_listener", "web-sys/MediaQueryList"] +use_display_media = [ + "use_window", + "web-sys/DisplayMediaStreamConstraints", + "web-sys/MediaDevices", + "web-sys/MediaStream", + "web-sys/MediaStreamTrack", +] +use_document = [ + "dep:web-sys", + "web-sys/VisibilityState", +] +use_document_visibility = ["use_event_listener", "web-sys/VisibilityState"] +use_draggable = ["use_event_listener", "web-sys/DomRect"] +use_drop_zone = [ + "use_event_listener", + "web-sys/DataTransfer", + "web-sys/File", + "web-sys/FileList" +] +use_element_bounding = [ + "use_event_listener", + "use_resize_observer", + "web-sys/DomRect", +] +use_element_hover = ["use_event_listener"] +use_element_size = [ + "use_resize_observer", + "watch_with_options", + "web-sys/ResizeObserverSize", +] +use_element_visibility = [ + "use_intersection_observer", + "web-sys/DomRect", +] +use_event_listener = [ + "element", + "watch_with_options", + "dep:web-sys", + "web-sys/EventTarget", + "web-sys/EventListenerOptions" +] +use_event_source = [ + "use_event_listener", + "web-sys/EventSource", + "web-sys/EventSourceInit", + "dep:codee", +] +use_favicon = [] +use_geolocation = [ + "use_window", + "web-sys/Coordinates", + "web-sys/Geolocation", + "web-sys/Position", + "web-sys/PositionError", + "web-sys/PositionOptions", +] +use_idle = [ + "use_event_listener", + "use_document", + "use_timestamp", +] +use_infinite_scroll = [ + "use_element_visibility", + "use_scroll", + "dep:gloo-timers", + "dep:futures-util", +] +use_intersection_observer = [ + "element", + "watch_with_options", + "web-sys/IntersectionObserver", + "web-sys/IntersectionObserverEntry", + "web-sys/IntersectionObserverInit", +] +use_interval = ["use_interval_fn"] +use_interval_fn = [] +use_intl_number_format = [] +use_locale = ["use_locales", "dep:unic-langid"] +use_locales = ["use_event_listener", "use_window"] +use_media_query = ["use_event_listener"] +use_mouse = [ + "element", + "use_event_listener", + "use_window", + "web-sys/Touch", + "web-sys/TouchList", +] +use_mouse_in_element = [ + "use_mouse", + "web-sys/DomRect", +] +use_mutation_observer = [ + "element", + "use_supported", + "web-sys/MutationObserver", + "web-sys/MutationObserverInit", + "web-sys/MutationRecord", +] +use_permission = [ + "use_event_listener", + "web-sys/Permissions", + "web-sys/PermissionState", + "web-sys/PermissionStatus", +] +use_preferred_contrast = ["use_media_query"] +use_preferred_dark = ["use_media_query"] +use_prefers_reduced_motion = ["use_media_query"] +use_raf_fn = [] +use_resize_observer = [ + "element", + "use_supported", + "web-sys/DomRectReadOnly", + "web-sys/ResizeObserver", + "web-sys/ResizeObserverBoxOptions", + "web-sys/ResizeObserverEntry", + "web-sys/ResizeObserverOptions", +] +use_scroll = [ + "element", + "use_event_listener", + "use_debounce_fn", + "use_throttle_fn", + "web-sys/ScrollBehavior", + "web-sys/ScrollToOptions", +] +use_service_worker = [ + "use_window", + "web-sys/ServiceWorker", + "web-sys/ServiceWorkerContainer", + "web-sys/ServiceWorkerRegistration" +] +use_sorted = [] +use_supported = [] +use_throttle_fn = [] +use_timeout_fn = [] +use_timestamp = ["use_interval_fn", "use_raf_fn"] +use_to_string = [] +use_user_media = [ + "use_window", + "web-sys/MediaDevices", + "web-sys/MediaStream", + "web-sys/MediaStreamConstraints", + "web-sys/MediaStreamTrack", +] +use_web_notification = [ + "use_supported", + "use_window", + "use_event_listener", + "web-sys/Notification", + "web-sys/NotificationOptions", + "web-sys/NotificationPermission", + "web-sys/NotificationDirection", + "web-sys/VisibilityState" +] +use_websocket = ["dep:codee"] +use_window = ["use_document", "dep:web-sys", "web-sys/Navigator", "web-sys/MediaQueryList"] +use_window_focus = ["use_event_listener"] +use_window_scroll = ["use_event_listener", "use_window"] wasm_ssr = [] +watch_debounced = ["watch_with_options"] +watch_pausable = ["watch_with_options"] +watch_throttled = ["watch_with_options"] +watch_with_options = [] +whenever = [] [package.metadata.docs.rs] features = ["math", "docs", "ssr"] diff --git a/README.md b/README.md index b432e0f..de776b8 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Crates.io SSR Docs & Demos - 79 Functions + 82 Functions


@@ -87,9 +87,9 @@ This will create the function file in the src directory, scaffold an example dir ## Leptos compatibility -| Crate version | Compatible Leptos version | -|---------------|---------------------------| -| <= 0.3 | 0.3 | -| 0.4, 0.5, 0.6 | 0.4 | -| 0.7, 0.8, 0.9 | 0.5 | -| 0.10, 0.11 | 0.6 | +| Crate version | Compatible Leptos version | +|------------------|---------------------------| +| <= 0.3 | 0.3 | +| 0.4, 0.5, 0.6 | 0.4 | +| 0.7, 0.8, 0.9 | 0.5 | +| 0.10, 0.11, 0.12 | 0.6 | diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index 502a1a6..ebbb19f 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -48,6 +48,7 @@ - [use_permission](browser/use_permission.md) - [use_preferred_contrast](browser/use_preferred_contrast.md) - [use_preferred_dark](browser/use_preferred_dark.md) +- [use_prefers_reduced_motion](browser/use_prefers_reduced_motion.md) - [use_service_worker](browser/use_service_worker.md) - [use_user_media](browser/use_user_media.md) - [use_web_notification](browser/use_web_notification.md) @@ -99,6 +100,7 @@ # Utilities +- [header](utilities/header.md) - [is_err](utilities/is_err.md) - [is_none](utilities/is_none.md) - [is_ok](utilities/is_ok.md) @@ -113,6 +115,8 @@ # Intl - [use_intl_number_format](intl/use_intl_number_format.md) +- [use_locale](intl/use_locale.md) +- [use_locales](intl/use_locales.md) # @Math diff --git a/docs/book/src/browser/use_prefers_reduced_motion.md b/docs/book/src/browser/use_prefers_reduced_motion.md new file mode 100644 index 0000000..d6aa3ae --- /dev/null +++ b/docs/book/src/browser/use_prefers_reduced_motion.md @@ -0,0 +1,3 @@ +# use_prefers_reduced_motion + + diff --git a/docs/book/src/codecs.md b/docs/book/src/codecs.md index 2e54fa2..45b2219 100644 --- a/docs/book/src/codecs.md +++ b/docs/book/src/codecs.md @@ -1,9 +1,9 @@ # Encoding and Decoding Data Several functions encode and decode data for storing it and/or sending it over the network. To do this, codecs -located at [`src/utils/codecs`](https://github.com/Synphonyte/leptos-use/tree/main/src/utils/codecs) are used. They -implement the traits [`Encoder`](https://github.com/Synphonyte/leptos-use/blob/main/src/utils/codecs/mod.rs#L9) with the -method `encode` and [`Decoder`](https://github.com/Synphonyte/leptos-use/blob/main/src/utils/codecs/mod.rs#L17) with the +from the crate [`codee`](https://docs.rs/codee/latest/codee/) are used. They +implement the traits [`Encoder`](https://docs.rs/codee/latest/codee/trait.Encoder.html) with the +method `encode` and [`Decoder`](https://docs.rs/codee/latest/codee/trait.Decoder.html) with the method `decode`. There are two types of codecs: One that encodes as binary data (`Vec[u8]`) and another type that encodes as @@ -11,26 +11,8 @@ strings (`String`). There is also an adapter [`Base64`](https://github.com/Synphonyte/leptos-use/blob/main/src/utils/codecs/string/base64.rs) that can be used to wrap a binary codec and make it a string codec by representing the binary data as a base64 string. -## Available Codecs - -### String Codecs - -- [**`FromToStringCodec` - **](https://github.com/Synphonyte/leptos-use/blob/main/src/utils/codecs/string/from_to_string.rs) -- [**`JsonSerdeCodec`**](https://github.com/Synphonyte/leptos-use/blob/main/src/utils/codecs/string/json_serde.rs)** - -### Binary Codecs - -- [**`FromToBytesCodec`**](https://github.com/Synphonyte/leptos-use/blob/main/src/utils/codecs/binary/from_to_bytes.rs) -- [**`BincodeSerdeCodec`**](https://github.com/Synphonyte/leptos-use/blob/main/src/utils/codecs/binary/bincode_serde.rs) -- [**`MsgpackSerdeCodec`**](https://github.com/Synphonyte/leptos-use/blob/main/src/utils/codecs/binary/msgpack_serde.rs) - -### Adapters - -- [**`Base64`**](https://github.com/Synphonyte/leptos-use/blob/main/src/utils/codecs/string/base64.rs) — - Wraps a binary codec and make it a string codec by representing the binary data as a base64 string. -- [**`OptionCodec`**](https://github.com/Synphonyte/leptos-use/blob/main/src/utils/codecs/option.rs) — - Wraps a string codec that encodes `T` to create a codec that encodes `Option`. +Please check the documentation of [`codee`](https://docs.rs/codee/latest/codee/) for more details and a list of all +available codecs. ## Example @@ -41,6 +23,7 @@ format. Since cookies can only store strings, we have to use string codecs here. # use leptos::*; # use leptos_use::use_cookie; # use serde::{Deserialize, Serialize}; +# use codee::string::JsonCodec; # #[component] # pub fn App(cx: Scope) -> impl IntoView { @@ -57,100 +40,13 @@ let (cookie, set_cookie) = use_cookie::("my-state-cookie"); ## Custom Codecs -If you don't find a suitable codecs for your needs, you can implement your own; it's straightforward! If you want to -create a string codec, you can look -at [`JsonSerdeCodec`](https://github.com/Synphonyte/leptos-use/blob/main/src/utils/codecs/string/json_serde.rs). -In case it's a binary codec, have a look -at [`BincodeSerdeCodec`](https://github.com/Synphonyte/leptos-use/blob/main/src/utils/codecs/binary/bincode_serde.rs). +If you don't find a suitable codec for your needs, you can implement your own; it's straightforward! +If you want to create a string codec, you can look at +[`JsonSerdeCodec`](https://docs.rs/codee/latest/src/codee/string/json_serde.rs.html). +In case it's a binary codec, have a look at +[`BincodeSerdeCodec`](https://docs.rs/codee/latest/src/codee/binary/bincode_serde.rs.html). ## Versioning -Versioning is the process of handling long-term data that can outlive our code. - -For example, we could have a settings struct whose members change over time. We might eventually -add timezone support, and we might then remove support for a thousands separator for numbers. -Each change results in a new possible version of the stored data. If we stored these settings -in browser storage, we would need to handle all possible versions of the data format that can -occur. If we don't offer versioning, then all settings could revert to the default every time we -encounter an old format. - -How best to handle versioning depends on the codec involved: - -- The `FromToStringCodec` can avoid versioning entirely by keeping - to primitive types. In our example above, we could have decomposed the settings struct into - separate timezone and number separator fields. These would be encoded as strings and stored as - two separate key-value fields in the browser rather than a single field. If a field is missing, - then the value intentionally would fall back to the default without interfering with the other - field. - -- The `ProstCodec` uses [Protocol buffers](https://protobuf.dev/overview/) - designed to solve the problem of long-term storage. It provides semantics for versioning that - are not present in JSON or other formats. - -- The codecs that use serde under the hood can rely on serde or by - providing their own manual version handling. See the next sections for more details. - -### Rely on `serde` - -A simple way to avoid complex versioning is to rely on serde's [field attributes](https://serde.rs/field-attrs.html) -such as [`serde(default)`](https://serde.rs/field-attrs.html#default) -and [`serde(rename = "...")`](https://serde.rs/field-attrs.html#rename). - -### Manual Version Handling - -We look at the example of the `JsonSerdeCodec` in this section. - -To implement version handling, we parse the JSON generically then transform the -resulting `JsValue` before decoding it into our struct again. - -Let's look at an example. - - ```rust,noplayground - # use leptos::*; - # use leptos_use::storage::{StorageType, use_local_storage, use_session_storage, use_storage, UseStorageOptions}; - # use serde::{Deserialize, Serialize}; - # use serde_json::json; - # use leptos_use::utils::{Encoder, Decoder}; - # - # pub fn Demo() -> impl IntoView { - #[derive(Serialize, Deserialize, Clone, Default, PartialEq)] - pub struct MyState { - pub hello: String, - // This field was added in a later version - pub greeting: String, - } - - pub struct MyStateCodec; - - impl Encoder for MyStateCodec { - type Error = serde_json::Error; - type Encoded = String; - - fn encode(val: &MyState) -> Result { - serde_json::to_string(val) - } - } - - impl Decoder for MyStateCodec { - type Error = serde_json::Error; - type Encoded = str; - - fn decode(stored_value: &Self::Encoded) -> Result { - let mut val: serde_json::Value = serde_json::from_str(stored_value)?; - // add "greeting": "Hello" to the object if it's missing - if let Some(obj) = val.as_object_mut() { - if !obj.contains_key("greeting") { - obj.insert("greeting".to_string(), json!("Hello")); - } - serde_json::from_value(val) - } else { - Ok(MyState::default()) - } - } - } - - // Then use it like the following just as any other codec. - let (get, set, remove) = use_local_storage::("my-struct-key"); - # view! { } - # } - ``` +For a discussion on how to implement versioning please refer to the +[relevant section in the docs for `codee`](https://docs.rs/codee/latest/codee/index.html#versioning). \ No newline at end of file diff --git a/docs/book/src/intl/use_locale.md b/docs/book/src/intl/use_locale.md new file mode 100644 index 0000000..56b32d7 --- /dev/null +++ b/docs/book/src/intl/use_locale.md @@ -0,0 +1,3 @@ +# use_locale + + diff --git a/docs/book/src/intl/use_locales.md b/docs/book/src/intl/use_locales.md new file mode 100644 index 0000000..084b0fd --- /dev/null +++ b/docs/book/src/intl/use_locales.md @@ -0,0 +1,3 @@ +# use_locales + + diff --git a/docs/book/src/introduction.md b/docs/book/src/introduction.md index cf0098a..8bcc1c1 100644 --- a/docs/book/src/introduction.md +++ b/docs/book/src/introduction.md @@ -12,6 +12,6 @@ Crates.io SSR Docs & Demos - 79 Functions + 82 Functions

\ No newline at end of file diff --git a/docs/book/src/utilities/header.md b/docs/book/src/utilities/header.md new file mode 100644 index 0000000..2bd2d1f --- /dev/null +++ b/docs/book/src/utilities/header.md @@ -0,0 +1,3 @@ +# header + + diff --git a/examples/Cargo.toml b/examples/Cargo.toml index f428959..187bcf3 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -38,6 +38,8 @@ members = [ "use_interval", "use_interval_fn", "use_intl_number_format", + "use_locale", + "use_locales", "use_media_query", "use_mouse", "use_mouse_in_element", @@ -45,6 +47,7 @@ members = [ "use_not", "use_or", "use_permission", + "use_prefers_reduced_motion", "use_raf_fn", "use_resize_observer", "use_round", diff --git a/examples/on_click_outside/Cargo.toml b/examples/on_click_outside/Cargo.toml index 3313bf8..c6d6279 100644 --- a/examples/on_click_outside/Cargo.toml +++ b/examples/on_click_outside/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["on_click_outside", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/signal_debounced/Cargo.toml b/examples/signal_debounced/Cargo.toml index 6f85a28..9411da3 100644 --- a/examples/signal_debounced/Cargo.toml +++ b/examples/signal_debounced/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["signal_debounced", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/signal_throttled/Cargo.toml b/examples/signal_throttled/Cargo.toml index 73b2b91..2a14d8a 100644 --- a/examples/signal_throttled/Cargo.toml +++ b/examples/signal_throttled/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["signal_throttled", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/ssr/Cargo.toml b/examples/ssr/Cargo.toml index 5d71626..33e9575 100644 --- a/examples/ssr/Cargo.toml +++ b/examples/ssr/Cargo.toml @@ -16,17 +16,31 @@ leptos = { version = "0.6", features = ["nightly"] } leptos_axum = { version = "0.6", optional = true } leptos_meta = { version = "0.6", features = ["nightly"] } leptos_router = { version = "0.6", features = ["nightly"] } -leptos-use = { path = "../.." } log = "0.4" simple_logger = "4" tokio = { version = "1", features = ["full"], optional = true } tower = { version = "0.4", optional = true } +tower-default-headers = { git = "https://github.com/banool/tower-default-headers-rs" } tower-http = { version = "0.5", features = ["fs"], optional = true } -wasm-bindgen = "0.2.92" +wasm-bindgen = "=0.2.93" thiserror = "1.0.38" tracing = { version = "0.1.37", optional = true } http = "1" +[dependencies.leptos-use] +path = "../.." +features = [ + "use_cookie", + "use_color_mode", + "use_debounce_fn", + "use_event_listener", + "use_interval", + "use_intl_number_format", + "use_locales", + "use_timestamp", + "storage" +] + [features] hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] ssr = [ diff --git a/examples/ssr/src/app.rs b/examples/ssr/src/app.rs index fed3766..e0380d0 100644 --- a/examples/ssr/src/app.rs +++ b/examples/ssr/src/app.rs @@ -1,15 +1,15 @@ use crate::error_template::{AppError, ErrorTemplate}; +use codee::string::FromToStringCodec; use leptos::ev::{keypress, KeyboardEvent}; use leptos::prelude::*; use leptos_meta::*; use leptos_router::*; use leptos_use::storage::{use_local_storage, use_local_storage_with_options, UseStorageOptions}; -use codee::string::FromToStringCodec; use leptos_use::{ use_color_mode_with_options, use_cookie_with_options, use_debounce_fn, use_event_listener, - use_interval, use_intl_number_format, use_preferred_dark, use_timestamp, use_window, ColorMode, - UseColorModeOptions, UseColorModeReturn, UseCookieOptions, UseIntervalReturn, - UseIntlNumberFormatOptions, + use_interval, use_intl_number_format, use_locales, use_preferred_dark, use_timestamp, + use_window, ColorMode, UseColorModeOptions, UseColorModeReturn, UseCookieOptions, + UseIntervalReturn, UseIntlNumberFormatOptions, }; #[component] @@ -83,12 +83,13 @@ fn HomePage() -> impl IntoView { .default_value(Some("Bogus string".to_owned())), ); + let locales = use_locales(); + view! {

Leptos-Use SSR Example

-

Locale zh-Hans-CN-u-nu-hanidec: {zh_count}

Press any key: {key}

Debounced called: {debounce_value}

Color mode: {move || format!("{:?}", mode.get())}

@@ -99,7 +100,10 @@ fn HomePage() -> impl IntoView {

Dark preferred: {is_dark_preferred}

Test cookie: {move || test_cookie().unwrap_or("".to_string())}

+
{move || format!("Locales:\n    {}", locales().join("\n    "))}
+

Locale zh-Hans-CN-u-nu-hanidec: {zh_count}

+ 0 }>
Greater than 0
diff --git a/examples/ssr/src/main.rs b/examples/ssr/src/main.rs index 911c6d4..5fd9c6e 100644 --- a/examples/ssr/src/main.rs +++ b/examples/ssr/src/main.rs @@ -2,11 +2,13 @@ #[tokio::main] async fn main() { use axum::{routing::post, Router}; + use http::{HeaderMap, HeaderName, HeaderValue}; use leptos::logging::log; use leptos::prelude::*; use leptos_axum::{generate_route_list, LeptosRoutes}; use leptos_use_ssr::app::*; use leptos_use_ssr::fileserv::file_and_error_handler; + use tower_default_headers::DefaultHeadersLayer; simple_logger::init_with_level(log::Level::Info).expect("couldn't initialize logging"); @@ -20,12 +22,19 @@ async fn main() { let addr = leptos_options.site_addr; let routes = generate_route_list(|| view! { }); + let mut default_headers = HeaderMap::new(); + let color_header = HeaderValue::from_static("Sec-CH-Prefers-Color-Scheme"); + default_headers.insert(HeaderName::from_static("accept-ch"), color_header.clone()); + default_headers.insert(HeaderName::from_static("vary"), color_header.clone()); + default_headers.insert(HeaderName::from_static("critical-ch"), color_header); + // build our application with a route let app = Router::new() .route("/api/*fn_name", post(leptos_axum::handle_server_fns)) .leptos_routes(&leptos_options, routes, || view! { }) .fallback(file_and_error_handler) - .with_state(leptos_options); + .with_state(leptos_options) + .layer(DefaultHeadersLayer::new(default_headers)); let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); log!("listening on http://{}", &addr); diff --git a/examples/sync_signal/Cargo.toml b/examples/sync_signal/Cargo.toml index 0eae2b1..c072a2b 100644 --- a/examples/sync_signal/Cargo.toml +++ b/examples/sync_signal/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["sync_signal", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_active_element/Cargo.toml b/examples/use_active_element/Cargo.toml index c19c400..e5c31a5 100644 --- a/examples/use_active_element/Cargo.toml +++ b/examples/use_active_element/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_active_element", "docs"] } web-sys = { version = "0.3", features = ["HtmlElement", "DomStringMap"] } [dev-dependencies] diff --git a/examples/use_breakpoints/Cargo.toml b/examples/use_breakpoints/Cargo.toml index 22b894b..b022945 100644 --- a/examples/use_breakpoints/Cargo.toml +++ b/examples/use_breakpoints/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_breakpoints", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_broadcast_channel/Cargo.toml b/examples/use_broadcast_channel/Cargo.toml index a7bf756..3cfcd3f 100644 --- a/examples/use_broadcast_channel/Cargo.toml +++ b/examples/use_broadcast_channel/Cargo.toml @@ -9,7 +9,7 @@ codee = "0.1" console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_broadcast_channel", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_clipboard/.cargo/config.toml b/examples/use_clipboard/.cargo/config.toml deleted file mode 100644 index 8467175..0000000 --- a/examples/use_clipboard/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build] -rustflags = ["--cfg=web_sys_unstable_apis"] diff --git a/examples/use_clipboard/Cargo.toml b/examples/use_clipboard/Cargo.toml index d85ad83..43cba10 100644 --- a/examples/use_clipboard/Cargo.toml +++ b/examples/use_clipboard/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_clipboard", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_color_mode/Cargo.toml b/examples/use_color_mode/Cargo.toml index ead2ef6..26874f0 100644 --- a/examples/use_color_mode/Cargo.toml +++ b/examples/use_color_mode/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_color_mode", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_cookie/Cargo.toml b/examples/use_cookie/Cargo.toml index c645550..2974155 100644 --- a/examples/use_cookie/Cargo.toml +++ b/examples/use_cookie/Cargo.toml @@ -9,7 +9,7 @@ codee = "0.1" console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_cookie", "docs"] } rand = "0.8" getrandom = { version = "0.2", features = ["js"] } web-sys = "0.3" diff --git a/examples/use_css_var/Cargo.toml b/examples/use_css_var/Cargo.toml index 267c40e..97d6fa6 100644 --- a/examples/use_css_var/Cargo.toml +++ b/examples/use_css_var/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_css_var", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_cycle_list/Cargo.toml b/examples/use_cycle_list/Cargo.toml index c59b61c..77f6c25 100644 --- a/examples/use_cycle_list/Cargo.toml +++ b/examples/use_cycle_list/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_cycle_list", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_debounce_fn/Cargo.toml b/examples/use_debounce_fn/Cargo.toml index d5f4621..c3f7b9e 100644 --- a/examples/use_debounce_fn/Cargo.toml +++ b/examples/use_debounce_fn/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_debounce_fn", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_device_orientation/Cargo.toml b/examples/use_device_orientation/Cargo.toml index 9a4c64a..5fec238 100644 --- a/examples/use_device_orientation/Cargo.toml +++ b/examples/use_device_orientation/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_device_orientation", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_device_pixel_ratio/Cargo.toml b/examples/use_device_pixel_ratio/Cargo.toml index 5874c65..98efdde 100644 --- a/examples/use_device_pixel_ratio/Cargo.toml +++ b/examples/use_device_pixel_ratio/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_device_pixel_ratio", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_display_media/Cargo.toml b/examples/use_display_media/Cargo.toml index c7ffe6a..f5f4b79 100644 --- a/examples/use_display_media/Cargo.toml +++ b/examples/use_display_media/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_display_media", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_document_visibility/Cargo.toml b/examples/use_document_visibility/Cargo.toml index cb9b8f1..8d5b019 100644 --- a/examples/use_document_visibility/Cargo.toml +++ b/examples/use_document_visibility/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_document_visibility", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_draggable/Cargo.toml b/examples/use_draggable/Cargo.toml index e2e9a61..8427afb 100644 --- a/examples/use_draggable/Cargo.toml +++ b/examples/use_draggable/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_draggable", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_drop_zone/Cargo.toml b/examples/use_drop_zone/Cargo.toml index 115dc9d..aa05fca 100644 --- a/examples/use_drop_zone/Cargo.toml +++ b/examples/use_drop_zone/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_drop_zone", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_element_bounding/Cargo.toml b/examples/use_element_bounding/Cargo.toml index 7620bcd..8ef72c3 100644 --- a/examples/use_element_bounding/Cargo.toml +++ b/examples/use_element_bounding/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_element_bounding", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_element_hover/Cargo.toml b/examples/use_element_hover/Cargo.toml index 9d90e62..9be576c 100644 --- a/examples/use_element_hover/Cargo.toml +++ b/examples/use_element_hover/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_element_hover", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_element_size/Cargo.toml b/examples/use_element_size/Cargo.toml index daf02f0..4e71f62 100644 --- a/examples/use_element_size/Cargo.toml +++ b/examples/use_element_size/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_element_size", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_element_visibility/Cargo.toml b/examples/use_element_visibility/Cargo.toml index 50ba543..3506775 100644 --- a/examples/use_element_visibility/Cargo.toml +++ b/examples/use_element_visibility/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_element_visibility", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_event_listener/Cargo.toml b/examples/use_event_listener/Cargo.toml index 653bb76..d0e75c1 100644 --- a/examples/use_event_listener/Cargo.toml +++ b/examples/use_event_listener/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../.." } +leptos-use = { path = "../..", features = ["use_event_listener"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_favicon/Cargo.toml b/examples/use_favicon/Cargo.toml index 260413a..38d4f05 100644 --- a/examples/use_favicon/Cargo.toml +++ b/examples/use_favicon/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_favicon", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_geolocation/Cargo.toml b/examples/use_geolocation/Cargo.toml index bb7c93e..4ded1d2 100644 --- a/examples/use_geolocation/Cargo.toml +++ b/examples/use_geolocation/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_geolocation", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_idle/Cargo.toml b/examples/use_idle/Cargo.toml index 1b412b4..0e8d92a 100644 --- a/examples/use_idle/Cargo.toml +++ b/examples/use_idle/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_idle", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_infinite_scroll/Cargo.toml b/examples/use_infinite_scroll/Cargo.toml index e55811a..52cafcb 100644 --- a/examples/use_infinite_scroll/Cargo.toml +++ b/examples/use_infinite_scroll/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_infinite_scroll", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_intersection_observer/Cargo.toml b/examples/use_intersection_observer/Cargo.toml index b401615..b8c5d1a 100644 --- a/examples/use_intersection_observer/Cargo.toml +++ b/examples/use_intersection_observer/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_intersection_observer", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_interval/Cargo.toml b/examples/use_interval/Cargo.toml index a49f0cd..cd02ec5 100644 --- a/examples/use_interval/Cargo.toml +++ b/examples/use_interval/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_interval", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_interval_fn/Cargo.toml b/examples/use_interval_fn/Cargo.toml index 86bc8b2..96de322 100644 --- a/examples/use_interval_fn/Cargo.toml +++ b/examples/use_interval_fn/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs", "math"] } +leptos-use = { path = "../..", features = ["use_interval_fn", "docs", "math"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_intl_number_format/Cargo.toml b/examples/use_intl_number_format/Cargo.toml index 7eadd70..5ed97da 100644 --- a/examples/use_intl_number_format/Cargo.toml +++ b/examples/use_intl_number_format/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_intl_number_format", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_locale/Cargo.toml b/examples/use_locale/Cargo.toml new file mode 100644 index 0000000..1c592fe --- /dev/null +++ b/examples/use_locale/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "use_locale" +version = "0.1.0" +edition = "2021" + +[dependencies] +leptos = { version = "0.6", features = ["nightly", "csr"] } +console_error_panic_hook = "0.1" +console_log = "1" +log = "0.4" +leptos-use = { path = "../..", features = ["use_locale", "docs"] } +unic-langid = { version = "0.9", features = ["macros"] } +web-sys = "0.3" + +[dev-dependencies] +wasm-bindgen = "0.2" +wasm-bindgen-test = "0.3.0" diff --git a/examples/use_locale/README.md b/examples/use_locale/README.md new file mode 100644 index 0000000..2398484 --- /dev/null +++ b/examples/use_locale/README.md @@ -0,0 +1,23 @@ +A simple example for `use_locale`. + +If you don't have it installed already, install [Trunk](https://trunkrs.dev/) and [Tailwind](https://tailwindcss.com/docs/installation) +as well as the nightly toolchain for Rust and the wasm32-unknown-unknown target: + +```bash +cargo install trunk +npm install -D tailwindcss @tailwindcss/forms +rustup toolchain install nightly +rustup target add wasm32-unknown-unknown +``` + +Then, open two terminals. In the first one, run: + +``` +npx tailwindcss -i ./input.css -o ./style/output.css --watch +``` + +In the second one, run: + +```bash +trunk serve --open +``` \ No newline at end of file diff --git a/examples/use_locale/Trunk.toml b/examples/use_locale/Trunk.toml new file mode 100644 index 0000000..3e4be08 --- /dev/null +++ b/examples/use_locale/Trunk.toml @@ -0,0 +1,2 @@ +[build] +public_url = "/demo/" \ No newline at end of file diff --git a/examples/use_locale/index.html b/examples/use_locale/index.html new file mode 100644 index 0000000..ae249a6 --- /dev/null +++ b/examples/use_locale/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/use_locale/input.css b/examples/use_locale/input.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/examples/use_locale/input.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/examples/use_locale/rust-toolchain.toml b/examples/use_locale/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/examples/use_locale/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/examples/use_locale/src/main.rs b/examples/use_locale/src/main.rs new file mode 100644 index 0000000..a0fe2a6 --- /dev/null +++ b/examples/use_locale/src/main.rs @@ -0,0 +1,22 @@ +use leptos::*; +use leptos_use::docs::demo_or_body; +use leptos_use::use_locale; +use unic_langid::langid_slice; + +#[component] +fn Demo() -> impl IntoView { + let locale = use_locale(langid_slice!["en", "de", "fr"]); + + view! { +

Locale: {move || locale.get().to_string()}

+ } +} + +fn main() { + _ = console_log::init_with_level(log::Level::Debug); + console_error_panic_hook::set_once(); + + mount_to(demo_or_body(), || { + view! { } + }) +} diff --git a/examples/use_locale/style/output.css b/examples/use_locale/style/output.css new file mode 100644 index 0000000..6063b89 --- /dev/null +++ b/examples/use_locale/style/output.css @@ -0,0 +1,330 @@ +[type='text'],input:where(:not([type])),[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #fff; + border-color: #6b7280; + border-width: 1px; + border-radius: 0px; + padding-top: 0.5rem; + padding-right: 0.75rem; + padding-bottom: 0.5rem; + padding-left: 0.75rem; + font-size: 1rem; + line-height: 1.5rem; + --tw-shadow: 0 0 #0000; +} + +[type='text']:focus, input:where(:not([type])):focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #2563eb; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + border-color: #2563eb; +} + +input::-moz-placeholder, textarea::-moz-placeholder { + color: #6b7280; + opacity: 1; +} + +input::placeholder,textarea::placeholder { + color: #6b7280; + opacity: 1; +} + +::-webkit-datetime-edit-fields-wrapper { + padding: 0; +} + +::-webkit-date-and-time-value { + min-height: 1.5em; + text-align: inherit; +} + +::-webkit-datetime-edit { + display: inline-flex; +} + +::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field { + padding-top: 0; + padding-bottom: 0; +} + +select { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); + background-position: right 0.5rem center; + background-repeat: no-repeat; + background-size: 1.5em 1.5em; + padding-right: 2.5rem; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} + +[multiple],[size]:where(select:not([size="1"])) { + background-image: initial; + background-position: initial; + background-repeat: unset; + background-size: initial; + padding-right: 0.75rem; + -webkit-print-color-adjust: unset; + print-color-adjust: unset; +} + +[type='checkbox'],[type='radio'] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + padding: 0; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + display: inline-block; + vertical-align: middle; + background-origin: border-box; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + flex-shrink: 0; + height: 1rem; + width: 1rem; + color: #2563eb; + background-color: #fff; + border-color: #6b7280; + border-width: 1px; + --tw-shadow: 0 0 #0000; +} + +[type='checkbox'] { + border-radius: 0px; +} + +[type='radio'] { + border-radius: 100%; +} + +[type='checkbox']:focus,[type='radio']:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 2px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #2563eb; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); +} + +[type='checkbox']:checked,[type='radio']:checked { + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; +} + +[type='checkbox']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); +} + +@media (forced-colors: active) { + [type='checkbox']:checked { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + +[type='radio']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); +} + +@media (forced-colors: active) { + [type='radio']:checked { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + +[type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus { + border-color: transparent; + background-color: currentColor; +} + +[type='checkbox']:indeterminate { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e"); + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; +} + +@media (forced-colors: active) { + [type='checkbox']:indeterminate { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + +[type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus { + border-color: transparent; + background-color: currentColor; +} + +[type='file'] { + background: unset; + border-color: inherit; + border-width: 0; + border-radius: 0; + padding: 0; + font-size: unset; + line-height: inherit; +} + +[type='file']:focus { + outline: 1px solid ButtonText; + outline: 1px auto -webkit-focus-ring-color; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +.static { + position: static; +} + +.font-bold { + font-weight: 700; +} + +.text-\[--brand-color\] { + color: var(--brand-color); +} + +.text-green-600 { + --tw-text-opacity: 1; + color: rgb(22 163 74 / var(--tw-text-opacity)); +} + +.opacity-75 { + opacity: 0.75; +} + +@media (prefers-color-scheme: dark) { + .dark\:text-green-500 { + --tw-text-opacity: 1; + color: rgb(34 197 94 / var(--tw-text-opacity)); + } +} \ No newline at end of file diff --git a/examples/use_locale/tailwind.config.js b/examples/use_locale/tailwind.config.js new file mode 100644 index 0000000..bc09f5e --- /dev/null +++ b/examples/use_locale/tailwind.config.js @@ -0,0 +1,15 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: { + files: ["*.html", "./src/**/*.rs", "../../src/docs/**/*.rs"], + }, + theme: { + extend: {}, + }, + corePlugins: { + preflight: false, + }, + plugins: [ + require('@tailwindcss/forms'), + ], +} \ No newline at end of file diff --git a/examples/use_locales/Cargo.toml b/examples/use_locales/Cargo.toml new file mode 100644 index 0000000..c98524c --- /dev/null +++ b/examples/use_locales/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "use_locales" +version = "0.1.0" +edition = "2021" + +[dependencies] +leptos = { version = "0.6", features = ["nightly", "csr"] } +console_error_panic_hook = "0.1" +console_log = "1" +log = "0.4" +leptos-use = { path = "../..", features = ["use_locales", "docs"] } +web-sys = "0.3" + +[dev-dependencies] +wasm-bindgen = "0.2" +wasm-bindgen-test = "0.3.0" diff --git a/examples/use_locales/README.md b/examples/use_locales/README.md new file mode 100644 index 0000000..f1827be --- /dev/null +++ b/examples/use_locales/README.md @@ -0,0 +1,23 @@ +A simple example for `use_locales`. + +If you don't have it installed already, install [Trunk](https://trunkrs.dev/) and [Tailwind](https://tailwindcss.com/docs/installation) +as well as the nightly toolchain for Rust and the wasm32-unknown-unknown target: + +```bash +cargo install trunk +npm install -D tailwindcss @tailwindcss/forms +rustup toolchain install nightly +rustup target add wasm32-unknown-unknown +``` + +Then, open two terminals. In the first one, run: + +``` +npx tailwindcss -i ./input.css -o ./style/output.css --watch +``` + +In the second one, run: + +```bash +trunk serve --open +``` \ No newline at end of file diff --git a/examples/use_locales/Trunk.toml b/examples/use_locales/Trunk.toml new file mode 100644 index 0000000..3e4be08 --- /dev/null +++ b/examples/use_locales/Trunk.toml @@ -0,0 +1,2 @@ +[build] +public_url = "/demo/" \ No newline at end of file diff --git a/examples/use_locales/index.html b/examples/use_locales/index.html new file mode 100644 index 0000000..ae249a6 --- /dev/null +++ b/examples/use_locales/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/use_locales/input.css b/examples/use_locales/input.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/examples/use_locales/input.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/examples/use_locales/rust-toolchain.toml b/examples/use_locales/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/examples/use_locales/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/examples/use_locales/src/main.rs b/examples/use_locales/src/main.rs new file mode 100644 index 0000000..dfa206b --- /dev/null +++ b/examples/use_locales/src/main.rs @@ -0,0 +1,21 @@ +use leptos::*; +use leptos_use::docs::demo_or_body; +use leptos_use::use_locales; + +#[component] +fn Demo() -> impl IntoView { + let locales = use_locales(); + + view! { +
{move || format!("Locales:\n    {}", locales().join("\n    "))}
+ } +} + +fn main() { + _ = console_log::init_with_level(log::Level::Debug); + console_error_panic_hook::set_once(); + + mount_to(demo_or_body(), || { + view! { } + }) +} diff --git a/examples/use_locales/style/output.css b/examples/use_locales/style/output.css new file mode 100644 index 0000000..ab5191f --- /dev/null +++ b/examples/use_locales/style/output.css @@ -0,0 +1,289 @@ +[type='text'],[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #fff; + border-color: #6b7280; + border-width: 1px; + border-radius: 0px; + padding-top: 0.5rem; + padding-right: 0.75rem; + padding-bottom: 0.5rem; + padding-left: 0.75rem; + font-size: 1rem; + line-height: 1.5rem; + --tw-shadow: 0 0 #0000; +} + +[type='text']:focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #2563eb; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + border-color: #2563eb; +} + +input::-moz-placeholder, textarea::-moz-placeholder { + color: #6b7280; + opacity: 1; +} + +input::placeholder,textarea::placeholder { + color: #6b7280; + opacity: 1; +} + +::-webkit-datetime-edit-fields-wrapper { + padding: 0; +} + +::-webkit-date-and-time-value { + min-height: 1.5em; +} + +::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field { + padding-top: 0; + padding-bottom: 0; +} + +select { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); + background-position: right 0.5rem center; + background-repeat: no-repeat; + background-size: 1.5em 1.5em; + padding-right: 2.5rem; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} + +[multiple] { + background-image: initial; + background-position: initial; + background-repeat: unset; + background-size: initial; + padding-right: 0.75rem; + -webkit-print-color-adjust: unset; + print-color-adjust: unset; +} + +[type='checkbox'],[type='radio'] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + padding: 0; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + display: inline-block; + vertical-align: middle; + background-origin: border-box; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + flex-shrink: 0; + height: 1rem; + width: 1rem; + color: #2563eb; + background-color: #fff; + border-color: #6b7280; + border-width: 1px; + --tw-shadow: 0 0 #0000; +} + +[type='checkbox'] { + border-radius: 0px; +} + +[type='radio'] { + border-radius: 100%; +} + +[type='checkbox']:focus,[type='radio']:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 2px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #2563eb; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); +} + +[type='checkbox']:checked,[type='radio']:checked { + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; +} + +[type='checkbox']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); +} + +[type='radio']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); +} + +[type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus { + border-color: transparent; + background-color: currentColor; +} + +[type='checkbox']:indeterminate { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e"); + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; +} + +[type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus { + border-color: transparent; + background-color: currentColor; +} + +[type='file'] { + background: unset; + border-color: inherit; + border-width: 0; + border-radius: 0; + padding: 0; + font-size: unset; + line-height: inherit; +} + +[type='file']:focus { + outline: 1px solid ButtonText; + outline: 1px auto -webkit-focus-ring-color; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +.block { + display: block; +} + +.text-\[--brand-color\] { + color: var(--brand-color); +} + +.text-green-600 { + --tw-text-opacity: 1; + color: rgb(22 163 74 / var(--tw-text-opacity)); +} + +.opacity-75 { + opacity: 0.75; +} + +@media (prefers-color-scheme: dark) { + .dark\:text-green-500 { + --tw-text-opacity: 1; + color: rgb(34 197 94 / var(--tw-text-opacity)); + } +} \ No newline at end of file diff --git a/examples/use_locales/tailwind.config.js b/examples/use_locales/tailwind.config.js new file mode 100644 index 0000000..bc09f5e --- /dev/null +++ b/examples/use_locales/tailwind.config.js @@ -0,0 +1,15 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: { + files: ["*.html", "./src/**/*.rs", "../../src/docs/**/*.rs"], + }, + theme: { + extend: {}, + }, + corePlugins: { + preflight: false, + }, + plugins: [ + require('@tailwindcss/forms'), + ], +} \ No newline at end of file diff --git a/examples/use_media_query/Cargo.toml b/examples/use_media_query/Cargo.toml index 345aa87..a57d7fe 100644 --- a/examples/use_media_query/Cargo.toml +++ b/examples/use_media_query/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_media_query", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_mouse/Cargo.toml b/examples/use_mouse/Cargo.toml index 1c91a04..cb2dea3 100644 --- a/examples/use_mouse/Cargo.toml +++ b/examples/use_mouse/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_mouse", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_mouse_in_element/Cargo.toml b/examples/use_mouse_in_element/Cargo.toml index ac23964..2823591 100644 --- a/examples/use_mouse_in_element/Cargo.toml +++ b/examples/use_mouse_in_element/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_mouse_in_element", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_mutation_observer/Cargo.toml b/examples/use_mutation_observer/Cargo.toml index fcf12fd..c85533a 100644 --- a/examples/use_mutation_observer/Cargo.toml +++ b/examples/use_mutation_observer/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_mutation_observer", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_permission/Cargo.toml b/examples/use_permission/Cargo.toml index 17f2388..f03e633 100644 --- a/examples/use_permission/Cargo.toml +++ b/examples/use_permission/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_permission", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_prefers_reduced_motion/Cargo.toml b/examples/use_prefers_reduced_motion/Cargo.toml new file mode 100644 index 0000000..3cdeb8d --- /dev/null +++ b/examples/use_prefers_reduced_motion/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "use_prefers_reduced_motion" +version = "0.1.0" +edition = "2021" + +[dependencies] +leptos = { version = "0.6", features = ["nightly", "csr"] } +console_error_panic_hook = "0.1" +console_log = "1" +log = "0.4" +leptos-use = { path = "../..", features = ["use_prefers_reduced_motion", "docs"] } +web-sys = "0.3" + +[dev-dependencies] +wasm-bindgen = "0.2" +wasm-bindgen-test = "0.3.0" diff --git a/examples/use_prefers_reduced_motion/README.md b/examples/use_prefers_reduced_motion/README.md new file mode 100644 index 0000000..95c5eea --- /dev/null +++ b/examples/use_prefers_reduced_motion/README.md @@ -0,0 +1,23 @@ +A simple example for `use_prefers_reduced_motion`. + +If you don't have it installed already, install [Trunk](https://trunkrs.dev/) and [Tailwind](https://tailwindcss.com/docs/installation) +as well as the nightly toolchain for Rust and the wasm32-unknown-unknown target: + +```bash +cargo install trunk +npm install -D tailwindcss @tailwindcss/forms +rustup toolchain install nightly +rustup target add wasm32-unknown-unknown +``` + +Then, open two terminals. In the first one, run: + +``` +npx tailwindcss -i ./input.css -o ./style/output.css --watch +``` + +In the second one, run: + +```bash +trunk serve --open +``` \ No newline at end of file diff --git a/examples/use_prefers_reduced_motion/Trunk.toml b/examples/use_prefers_reduced_motion/Trunk.toml new file mode 100644 index 0000000..3e4be08 --- /dev/null +++ b/examples/use_prefers_reduced_motion/Trunk.toml @@ -0,0 +1,2 @@ +[build] +public_url = "/demo/" \ No newline at end of file diff --git a/examples/use_prefers_reduced_motion/index.html b/examples/use_prefers_reduced_motion/index.html new file mode 100644 index 0000000..ae249a6 --- /dev/null +++ b/examples/use_prefers_reduced_motion/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/use_prefers_reduced_motion/input.css b/examples/use_prefers_reduced_motion/input.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/examples/use_prefers_reduced_motion/input.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/examples/use_prefers_reduced_motion/rust-toolchain.toml b/examples/use_prefers_reduced_motion/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/examples/use_prefers_reduced_motion/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/examples/use_prefers_reduced_motion/src/main.rs b/examples/use_prefers_reduced_motion/src/main.rs new file mode 100644 index 0000000..2a6adfc --- /dev/null +++ b/examples/use_prefers_reduced_motion/src/main.rs @@ -0,0 +1,29 @@ +use leptos::*; +use leptos_use::docs::{demo_or_body, BooleanDisplay}; +use leptos_use::use_prefers_reduced_motion; + +#[component] +fn Demo() -> impl IntoView { + let is_reduced_motion_preferred = use_prefers_reduced_motion(); + + view! { +
+

Prefers reduced motions:

+

+ Update reduce motion preference + + documentation. + +

+
+ } +} + +fn main() { + _ = console_log::init_with_level(log::Level::Debug); + console_error_panic_hook::set_once(); + + mount_to(demo_or_body(), || { + view! { } + }) +} diff --git a/examples/use_prefers_reduced_motion/style/output.css b/examples/use_prefers_reduced_motion/style/output.css new file mode 100644 index 0000000..7db8e42 --- /dev/null +++ b/examples/use_prefers_reduced_motion/style/output.css @@ -0,0 +1,326 @@ +[type='text'],input:where(:not([type])),[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #fff; + border-color: #6b7280; + border-width: 1px; + border-radius: 0px; + padding-top: 0.5rem; + padding-right: 0.75rem; + padding-bottom: 0.5rem; + padding-left: 0.75rem; + font-size: 1rem; + line-height: 1.5rem; + --tw-shadow: 0 0 #0000; +} + +[type='text']:focus, input:where(:not([type])):focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #2563eb; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + border-color: #2563eb; +} + +input::-moz-placeholder, textarea::-moz-placeholder { + color: #6b7280; + opacity: 1; +} + +input::placeholder,textarea::placeholder { + color: #6b7280; + opacity: 1; +} + +::-webkit-datetime-edit-fields-wrapper { + padding: 0; +} + +::-webkit-date-and-time-value { + min-height: 1.5em; + text-align: inherit; +} + +::-webkit-datetime-edit { + display: inline-flex; +} + +::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field { + padding-top: 0; + padding-bottom: 0; +} + +select { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); + background-position: right 0.5rem center; + background-repeat: no-repeat; + background-size: 1.5em 1.5em; + padding-right: 2.5rem; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} + +[multiple],[size]:where(select:not([size="1"])) { + background-image: initial; + background-position: initial; + background-repeat: unset; + background-size: initial; + padding-right: 0.75rem; + -webkit-print-color-adjust: unset; + print-color-adjust: unset; +} + +[type='checkbox'],[type='radio'] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + padding: 0; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + display: inline-block; + vertical-align: middle; + background-origin: border-box; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + flex-shrink: 0; + height: 1rem; + width: 1rem; + color: #2563eb; + background-color: #fff; + border-color: #6b7280; + border-width: 1px; + --tw-shadow: 0 0 #0000; +} + +[type='checkbox'] { + border-radius: 0px; +} + +[type='radio'] { + border-radius: 100%; +} + +[type='checkbox']:focus,[type='radio']:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 2px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #2563eb; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); +} + +[type='checkbox']:checked,[type='radio']:checked { + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; +} + +[type='checkbox']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); +} + +@media (forced-colors: active) { + [type='checkbox']:checked { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + +[type='radio']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); +} + +@media (forced-colors: active) { + [type='radio']:checked { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + +[type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus { + border-color: transparent; + background-color: currentColor; +} + +[type='checkbox']:indeterminate { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e"); + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; +} + +@media (forced-colors: active) { + [type='checkbox']:indeterminate { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + +[type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus { + border-color: transparent; + background-color: currentColor; +} + +[type='file'] { + background: unset; + border-color: inherit; + border-width: 0; + border-radius: 0; + padding: 0; + font-size: unset; + line-height: inherit; +} + +[type='file']:focus { + outline: 1px solid ButtonText; + outline: 1px auto -webkit-focus-ring-color; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +.static { + position: static; +} + +.text-\[--brand-color\] { + color: var(--brand-color); +} + +.text-green-600 { + --tw-text-opacity: 1; + color: rgb(22 163 74 / var(--tw-text-opacity)); +} + +.opacity-75 { + opacity: 0.75; +} + +@media (prefers-color-scheme: dark) { + .dark\:text-green-500 { + --tw-text-opacity: 1; + color: rgb(34 197 94 / var(--tw-text-opacity)); + } +} diff --git a/examples/use_prefers_reduced_motion/tailwind.config.js b/examples/use_prefers_reduced_motion/tailwind.config.js new file mode 100644 index 0000000..bc09f5e --- /dev/null +++ b/examples/use_prefers_reduced_motion/tailwind.config.js @@ -0,0 +1,15 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: { + files: ["*.html", "./src/**/*.rs", "../../src/docs/**/*.rs"], + }, + theme: { + extend: {}, + }, + corePlugins: { + preflight: false, + }, + plugins: [ + require('@tailwindcss/forms'), + ], +} \ No newline at end of file diff --git a/examples/use_raf_fn/Cargo.toml b/examples/use_raf_fn/Cargo.toml index 48b8cd9..e9c4c57 100644 --- a/examples/use_raf_fn/Cargo.toml +++ b/examples/use_raf_fn/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_raf_fn", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_resize_observer/Cargo.toml b/examples/use_resize_observer/Cargo.toml index 0ce7f4c..8ba4ed2 100644 --- a/examples/use_resize_observer/Cargo.toml +++ b/examples/use_resize_observer/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_resize_observer", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_scroll/Cargo.toml b/examples/use_scroll/Cargo.toml index ccb133a..25874cd 100644 --- a/examples/use_scroll/Cargo.toml +++ b/examples/use_scroll/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_scroll", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_service_worker/Cargo.toml b/examples/use_service_worker/Cargo.toml index 8e3b8c7..f074dd0 100644 --- a/examples/use_service_worker/Cargo.toml +++ b/examples/use_service_worker/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_service_worker", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_sorted/Cargo.toml b/examples/use_sorted/Cargo.toml index 05462ac..8ff335f 100644 --- a/examples/use_sorted/Cargo.toml +++ b/examples/use_sorted/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_sorted", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_storage/Cargo.toml b/examples/use_storage/Cargo.toml index 5174789..b8b2285 100644 --- a/examples/use_storage/Cargo.toml +++ b/examples/use_storage/Cargo.toml @@ -8,7 +8,7 @@ codee = { version = "0.1", features = ["json_serde"] } console_error_panic_hook = "0.1" console_log = "1" leptos = { version = "0.6", features = ["nightly", "csr"] } -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["storage", "docs"] } log = "0.4" serde = "1.0.163" web-sys = "0.3" diff --git a/examples/use_throttle_fn/Cargo.toml b/examples/use_throttle_fn/Cargo.toml index 04d6006..d39fc8d 100644 --- a/examples/use_throttle_fn/Cargo.toml +++ b/examples/use_throttle_fn/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_throttle_fn", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_timeout_fn/Cargo.toml b/examples/use_timeout_fn/Cargo.toml index 9b0cfa4..608afac 100644 --- a/examples/use_timeout_fn/Cargo.toml +++ b/examples/use_timeout_fn/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_timeout_fn", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_timestamp/Cargo.toml b/examples/use_timestamp/Cargo.toml index a065eb4..5e0a16f 100644 --- a/examples/use_timestamp/Cargo.toml +++ b/examples/use_timestamp/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_timestamp", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_user_media/Cargo.toml b/examples/use_user_media/Cargo.toml index ffd38b8..170c4af 100644 --- a/examples/use_user_media/Cargo.toml +++ b/examples/use_user_media/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_user_media", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_web_notification/Cargo.toml b/examples/use_web_notification/Cargo.toml index 216f31e..f499927 100644 --- a/examples/use_web_notification/Cargo.toml +++ b/examples/use_web_notification/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_web_notification", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_web_notification/src/main.rs b/examples/use_web_notification/src/main.rs index cf2f310..241ce18 100644 --- a/examples/use_web_notification/src/main.rs +++ b/examples/use_web_notification/src/main.rs @@ -14,7 +14,7 @@ fn Demo() -> impl IntoView { .title("Hello World from leptos-use") .direction(NotificationDirection::Auto) .language("en") - // .renotify(true) + .renotify(true) .tag("test"), ); diff --git a/examples/use_websocket/Cargo.toml b/examples/use_websocket/Cargo.toml index 2eb5095..26ed9a6 100644 --- a/examples/use_websocket/Cargo.toml +++ b/examples/use_websocket/Cargo.toml @@ -9,7 +9,7 @@ codee = { version = "0.1", features = ["msgpack_serde"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_websocket", "docs"] } serde = { version = "1", features = ["derive"] } web-sys = "0.3" diff --git a/examples/use_webtransport/Cargo.toml b/examples/use_webtransport/Cargo.toml index 5291647..680dc93 100644 --- a/examples/use_webtransport/Cargo.toml +++ b/examples/use_webtransport/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_web_notification", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_window_focus/Cargo.toml b/examples/use_window_focus/Cargo.toml index d157e75..ff9224a 100644 --- a/examples/use_window_focus/Cargo.toml +++ b/examples/use_window_focus/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_window_focus", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/use_window_scroll/Cargo.toml b/examples/use_window_scroll/Cargo.toml index d8a1c48..43092e2 100644 --- a/examples/use_window_scroll/Cargo.toml +++ b/examples/use_window_scroll/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["use_window_scroll", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/watch_debounced/Cargo.toml b/examples/watch_debounced/Cargo.toml index 8af3c77..b7d1872 100644 --- a/examples/watch_debounced/Cargo.toml +++ b/examples/watch_debounced/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["watch_debounced", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/watch_pausable/Cargo.toml b/examples/watch_pausable/Cargo.toml index a14897d..548f01a 100644 --- a/examples/watch_pausable/Cargo.toml +++ b/examples/watch_pausable/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["watch_pausable", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/examples/watch_throttled/Cargo.toml b/examples/watch_throttled/Cargo.toml index e711c4f..d69a67a 100644 --- a/examples/watch_throttled/Cargo.toml +++ b/examples/watch_throttled/Cargo.toml @@ -8,7 +8,7 @@ leptos = { version = "0.6", features = ["nightly", "csr"] } console_error_panic_hook = "0.1" console_log = "1" log = "0.4" -leptos-use = { path = "../..", features = ["docs"] } +leptos-use = { path = "../..", features = ["watch_throttled", "docs"] } web-sys = "0.3" [dev-dependencies] diff --git a/src/core/mod.rs b/src/core/mod.rs index 3fe5ca3..0b51baa 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,26 +1,32 @@ mod connection_ready_state; mod datetime; mod direction; +#[cfg(feature = "element")] mod element_maybe_signal; +#[cfg(feature = "element")] mod elements_maybe_signal; mod maybe_rw_signal; mod pointer_type; mod position; +mod reconnect_limit; mod size; mod ssr_safe_method; -mod storage; +#[cfg(feature = "use_color_mode")] pub(crate) mod url; mod use_rw_signal; pub use connection_ready_state::*; pub(crate) use datetime::*; pub use direction::*; +#[cfg(feature = "element")] pub use element_maybe_signal::*; +#[cfg(feature = "element")] pub use elements_maybe_signal::*; pub use maybe_rw_signal::*; pub use pointer_type::*; pub use position::*; +pub use reconnect_limit::*; pub use size::*; +#[allow(unused_imports)] pub(crate) use ssr_safe_method::*; -pub use storage::*; pub use use_rw_signal::*; diff --git a/src/core/reconnect_limit.rs b/src/core/reconnect_limit.rs new file mode 100644 index 0000000..5d28b2c --- /dev/null +++ b/src/core/reconnect_limit.rs @@ -0,0 +1,20 @@ +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ReconnectLimit { + Infinite, + Limited(u64), +} + +impl Default for ReconnectLimit { + fn default() -> Self { + ReconnectLimit::Limited(3) + } +} + +impl ReconnectLimit { + pub fn is_exceeded_by(self, times: u64) -> bool { + match self { + ReconnectLimit::Infinite => false, + ReconnectLimit::Limited(limit) => times >= limit, + } + } +} diff --git a/src/core/ssr_safe_method.rs b/src/core/ssr_safe_method.rs index 9ffa513..da46452 100644 --- a/src/core/ssr_safe_method.rs +++ b/src/core/ssr_safe_method.rs @@ -1,3 +1,5 @@ +#![allow(unused_macros, unused_imports)] + macro_rules! impl_ssr_safe_method { ( $(#[$attr:meta])* diff --git a/src/core/storage.rs b/src/core/storage.rs deleted file mode 100644 index e78e3f9..0000000 --- a/src/core/storage.rs +++ /dev/null @@ -1,21 +0,0 @@ -use leptos::prelude::*; -use wasm_bindgen::JsValue; - -/// Local or session storage or a custom store that is a `web_sys::Storage`. -#[derive(Default)] -pub enum StorageType { - #[default] - Local, - Session, - Custom(web_sys::Storage), -} - -impl StorageType { - pub fn into_storage(self) -> Result, JsValue> { - match self { - StorageType::Local => window().local_storage(), - StorageType::Session => window().session_storage(), - StorageType::Custom(storage) => Ok(Some(storage)), - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 6e85934..a8c46b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,146 +7,291 @@ pub mod core; pub mod docs; #[cfg(feature = "math")] pub mod math; +#[cfg(feature = "storage")] pub mod storage; pub mod utils; +pub use core::ReconnectLimit; + // #[cfg(web_sys_unstable_apis)] // mod use_webtransport; // #[cfg(web_sys_unstable_apis)] // pub use use_webtransport::*; -#[cfg(web_sys_unstable_apis)] -mod use_clipboard; -#[cfg(web_sys_unstable_apis)] -pub use use_clipboard::*; - +#[cfg(feature = "is_err")] mod is_err; +#[cfg(feature = "is_none")] mod is_none; +#[cfg(feature = "is_ok")] mod is_ok; +#[cfg(feature = "is_some")] mod is_some; +#[cfg(feature = "on_click_outside")] mod on_click_outside; +#[cfg(feature = "signal_debounced")] mod signal_debounced; +#[cfg(feature = "signal_throttled")] mod signal_throttled; +#[cfg(feature = "sync_signal")] mod sync_signal; +#[cfg(feature = "use_active_element")] // mod use_active_element; +#[cfg(feature = "use_breakpoints")] mod use_breakpoints; +#[cfg(feature = "use_broadcast_channel")] mod use_broadcast_channel; +#[cfg(feature = "use_clipboard")] +mod use_clipboard; +#[cfg(feature = "use_color_mode")] mod use_color_mode; +#[cfg(feature = "use_cookie")] mod use_cookie; +#[cfg(feature = "use_css_var")] mod use_css_var; +#[cfg(feature = "use_cycle_list")] mod use_cycle_list; +#[cfg(feature = "use_debounce_fn")] mod use_debounce_fn; +#[cfg(feature = "use_device_orientation")] mod use_device_orientation; +#[cfg(feature = "use_device_pixel_ratio")] mod use_device_pixel_ratio; +#[cfg(feature = "use_display_media")] mod use_display_media; +#[cfg(feature = "use_document")] mod use_document; +#[cfg(feature = "use_document_visibility")] mod use_document_visibility; +#[cfg(feature = "use_draggable")] mod use_draggable; +#[cfg(feature = "use_drop_zone")] mod use_drop_zone; +#[cfg(feature = "use_element_bounding")] mod use_element_bounding; +#[cfg(feature = "use_element_hover")] mod use_element_hover; +#[cfg(feature = "use_element_size")] mod use_element_size; +#[cfg(feature = "use_element_visibility")] mod use_element_visibility; +#[cfg(feature = "use_event_listener")] mod use_event_listener; +#[cfg(feature = "use_event_source")] mod use_event_source; +#[cfg(feature = "use_favicon")] mod use_favicon; +#[cfg(feature = "use_geolocation")] mod use_geolocation; +#[cfg(feature = "use_idle")] mod use_idle; +#[cfg(feature = "use_infinite_scroll")] mod use_infinite_scroll; +#[cfg(feature = "use_intersection_observer")] mod use_intersection_observer; +#[cfg(feature = "use_interval")] mod use_interval; +#[cfg(feature = "use_interval_fn")] mod use_interval_fn; +#[cfg(feature = "use_intl_number_format")] mod use_intl_number_format; +#[cfg(feature = "use_locale")] +mod use_locale; +#[cfg(feature = "use_locales")] +mod use_locales; +#[cfg(feature = "use_media_query")] mod use_media_query; +#[cfg(feature = "use_mouse")] mod use_mouse; +#[cfg(feature = "use_mouse_in_element")] mod use_mouse_in_element; +#[cfg(feature = "use_mutation_observer")] mod use_mutation_observer; +#[cfg(feature = "use_permission")] mod use_permission; +#[cfg(feature = "use_preferred_contrast")] mod use_preferred_contrast; +#[cfg(feature = "use_preferred_dark")] mod use_preferred_dark; +#[cfg(feature = "use_prefers_reduced_motion")] +mod use_prefers_reduced_motion; +#[cfg(feature = "use_raf_fn")] mod use_raf_fn; +#[cfg(feature = "use_resize_observer")] mod use_resize_observer; +#[cfg(feature = "use_scroll")] mod use_scroll; +#[cfg(feature = "use_service_worker")] mod use_service_worker; +#[cfg(feature = "use_sorted")] mod use_sorted; +#[cfg(feature = "use_supported")] mod use_supported; +#[cfg(feature = "use_throttle_fn")] mod use_throttle_fn; +#[cfg(feature = "use_timeout_fn")] mod use_timeout_fn; +#[cfg(feature = "use_timestamp")] mod use_timestamp; +#[cfg(feature = "use_to_string")] mod use_to_string; +#[cfg(feature = "use_user_media")] mod use_user_media; +#[cfg(feature = "use_web_notification")] mod use_web_notification; +#[cfg(feature = "use_websocket")] mod use_websocket; +#[cfg(feature = "use_window")] mod use_window; +#[cfg(feature = "use_window_focus")] mod use_window_focus; +#[cfg(feature = "use_window_scroll")] mod use_window_scroll; +#[cfg(feature = "watch_debounced")] mod watch_debounced; +#[cfg(feature = "watch_pausable")] mod watch_pausable; +#[cfg(feature = "watch_throttled")] mod watch_throttled; +#[cfg(feature = "watch_with_options")] mod watch_with_options; +#[cfg(feature = "whenever")] mod whenever; +#[cfg(feature = "is_err")] pub use is_err::*; +#[cfg(feature = "is_none")] pub use is_none::*; +#[cfg(feature = "is_ok")] pub use is_ok::*; +#[cfg(feature = "is_some")] pub use is_some::*; +#[cfg(feature = "on_click_outside")] pub use on_click_outside::*; +#[cfg(feature = "signal_debounced")] pub use signal_debounced::*; +#[cfg(feature = "signal_throttled")] pub use signal_throttled::*; +#[cfg(feature = "sync_signal")] pub use sync_signal::*; +#[cfg(feature = "use_active_element")] // pub use use_active_element::*; +#[cfg(feature = "use_breakpoints")] pub use use_breakpoints::*; +#[cfg(feature = "use_broadcast_channel")] pub use use_broadcast_channel::*; +#[cfg(feature = "use_clipboard")] +pub use use_clipboard::*; +#[cfg(feature = "use_color_mode")] pub use use_color_mode::*; +#[cfg(feature = "use_cookie")] pub use use_cookie::*; +#[cfg(feature = "use_css_var")] pub use use_css_var::*; +#[cfg(feature = "use_cycle_list")] pub use use_cycle_list::*; +#[cfg(feature = "use_debounce_fn")] pub use use_debounce_fn::*; +#[cfg(feature = "use_device_orientation")] pub use use_device_orientation::*; +#[cfg(feature = "use_device_pixel_ratio")] pub use use_device_pixel_ratio::*; +#[cfg(feature = "use_display_media")] pub use use_display_media::*; +#[cfg(feature = "use_document")] pub use use_document::*; +#[cfg(feature = "use_document_visibility")] pub use use_document_visibility::*; +#[cfg(feature = "use_draggable")] pub use use_draggable::*; +#[cfg(feature = "use_drop_zone")] pub use use_drop_zone::*; +#[cfg(feature = "use_element_bounding")] pub use use_element_bounding::*; +#[cfg(feature = "use_element_hover")] pub use use_element_hover::*; +#[cfg(feature = "use_element_size")] pub use use_element_size::*; +#[cfg(feature = "use_element_visibility")] pub use use_element_visibility::*; +#[cfg(feature = "use_event_listener")] pub use use_event_listener::*; +#[cfg(feature = "use_event_source")] pub use use_event_source::*; +#[cfg(feature = "use_favicon")] pub use use_favicon::*; +#[cfg(feature = "use_geolocation")] pub use use_geolocation::*; +#[cfg(feature = "use_idle")] pub use use_idle::*; +#[cfg(feature = "use_infinite_scroll")] pub use use_infinite_scroll::*; +#[cfg(feature = "use_intersection_observer")] pub use use_intersection_observer::*; +#[cfg(feature = "use_interval")] pub use use_interval::*; +#[cfg(feature = "use_interval_fn")] pub use use_interval_fn::*; +#[cfg(feature = "use_intl_number_format")] pub use use_intl_number_format::*; +#[cfg(feature = "use_locale")] +pub use use_locale::*; +#[cfg(feature = "use_locales")] +pub use use_locales::*; +#[cfg(feature = "use_media_query")] pub use use_media_query::*; +#[cfg(feature = "use_mouse")] pub use use_mouse::*; +#[cfg(feature = "use_mouse_in_element")] pub use use_mouse_in_element::*; +#[cfg(feature = "use_mutation_observer")] pub use use_mutation_observer::*; +#[cfg(feature = "use_permission")] pub use use_permission::*; +#[cfg(feature = "use_preferred_contrast")] pub use use_preferred_contrast::*; +#[cfg(feature = "use_preferred_dark")] pub use use_preferred_dark::*; +#[cfg(feature = "use_prefers_reduced_motion")] +pub use use_prefers_reduced_motion::*; +#[cfg(feature = "use_raf_fn")] pub use use_raf_fn::*; +#[cfg(feature = "use_resize_observer")] pub use use_resize_observer::*; +#[cfg(feature = "use_scroll")] pub use use_scroll::*; +#[cfg(feature = "use_service_worker")] pub use use_service_worker::*; +#[cfg(feature = "use_sorted")] pub use use_sorted::*; +#[cfg(feature = "use_supported")] pub use use_supported::*; +#[cfg(feature = "use_throttle_fn")] pub use use_throttle_fn::*; +#[cfg(feature = "use_timeout_fn")] pub use use_timeout_fn::*; +#[cfg(feature = "use_timestamp")] pub use use_timestamp::*; +#[cfg(feature = "use_to_string")] pub use use_to_string::*; +#[cfg(feature = "use_user_media")] +pub use use_user_media::*; +#[cfg(feature = "use_web_notification")] pub use use_web_notification::*; +#[cfg(feature = "use_websocket")] pub use use_websocket::*; +#[cfg(feature = "use_window")] pub use use_window::*; +#[cfg(feature = "use_window_focus")] pub use use_window_focus::*; +#[cfg(feature = "use_window_scroll")] pub use use_window_scroll::*; +#[cfg(feature = "watch_debounced")] pub use watch_debounced::*; +#[cfg(feature = "watch_pausable")] pub use watch_pausable::*; +#[cfg(feature = "watch_throttled")] pub use watch_throttled::*; +#[cfg(feature = "watch_with_options")] pub use watch_with_options::*; +#[cfg(feature = "whenever")] pub use whenever::*; diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 54f79db..5930028 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -2,7 +2,28 @@ mod use_local_storage; mod use_session_storage; mod use_storage; -pub use crate::core::StorageType; pub use use_local_storage::*; pub use use_session_storage::*; pub use use_storage::*; + +use leptos::window; +use wasm_bindgen::JsValue; + +/// Local or session storage or a custom store that is a `web_sys::Storage`. +#[derive(Default)] +pub enum StorageType { + #[default] + Local, + Session, + Custom(web_sys::Storage), +} + +impl StorageType { + pub fn into_storage(self) -> Result, JsValue> { + match self { + StorageType::Local => window().local_storage(), + StorageType::Session => window().session_storage(), + StorageType::Custom(storage) => Ok(Some(storage)), + } + } +} diff --git a/src/storage/use_storage.rs b/src/storage/use_storage.rs index a542494..58378c0 100644 --- a/src/storage/use_storage.rs +++ b/src/storage/use_storage.rs @@ -1,7 +1,4 @@ -use crate::{ - core::{MaybeRwSignal, StorageType}, - utils::FilterOptions, -}; +use crate::{core::MaybeRwSignal, storage::StorageType, utils::FilterOptions}; use codee::{CodecError, Decoder, Encoder}; use default_struct_builder::DefaultBuilder; use leptos::prelude::wrappers::read::Signal; @@ -217,8 +214,8 @@ where queue_microtask(move || { // TODO : better to use a BroadcastChannel (use_broadcast_channel)? // Note: we cannot construct a full StorageEvent so we _must_ rely on a custom event - let mut custom = web_sys::CustomEventInit::new(); - custom.detail(&JsValue::from_str(&key)); + let custom = web_sys::CustomEventInit::new(); + custom.set_detail(&JsValue::from_str(&key)); let result = window() .dispatch_event( &web_sys::CustomEvent::new_with_event_init_dict( diff --git a/src/sync_signal.rs b/src/sync_signal.rs index c22c0ee..0984308 100644 --- a/src/sync_signal.rs +++ b/src/sync_signal.rs @@ -168,6 +168,8 @@ where direction, transform_ltr, transform_rtl, + assign_ltr, + assign_rtl, } = options; let left = left.into(); @@ -183,7 +185,7 @@ where let new_value = (*transform_ltr)(new_value); if right.with_untracked(|right| right != &new_value) { - right.update(|right| *right = new_value); + right.update(|right| assign_ltr(right, new_value)); } }, immediate, @@ -197,7 +199,7 @@ where let new_value = (*transform_rtl)(new_value); if left.with_untracked(|left| left != &new_value) { - left.update(|left| *left = new_value); + left.update(|left| assign_rtl(left, new_value)); } }, immediate, @@ -221,6 +223,8 @@ pub enum SyncDirection { Both, } +pub type AssignFn = Rc; + /// Options for [`sync_signal_with_options`]. #[derive(DefaultBuilder)] pub struct SyncSignalOptions { @@ -241,6 +245,16 @@ pub struct SyncSignalOptions { /// Defaults to identity. #[builder(skip)] transform_rtl: Rc L>, + + /// Assigns the left signal to the right signal. + /// Defaults to `*r = l`. + #[builder(skip)] + assign_ltr: AssignFn, + + /// Assigns the right signal to the left signal. + /// Defaults to `*l = r`. + #[builder(skip)] + assign_rtl: AssignFn, } impl SyncSignalOptions { @@ -262,6 +276,23 @@ impl SyncSignalOptions { } } + /// Assigns the left signal to the right signal. + /// Defaults to `*r = l`. + pub fn assign_ltr(self, assign_ltr: impl Fn(&mut R, R) + 'static) -> Self { + Self { + assign_ltr: Rc::new(assign_ltr), + ..self + } + } + + /// Assigns the right signal to the left signal. + /// Defaults to `*l = r`. + pub fn assign_rtl(self, assign_rtl: impl Fn(&mut L, L) + 'static) -> Self { + Self { + assign_rtl: Rc::new(assign_rtl), + ..self + } + } /// Initializes options with transforms pub fn with_transforms( transform_ltr: impl Fn(&L) -> R + 'static, @@ -272,6 +303,8 @@ impl SyncSignalOptions { direction: SyncDirection::Both, transform_ltr: Rc::new(transform_ltr), transform_rtl: Rc::new(transform_rtl), + assign_ltr: Rc::new(|right, left| *right = left), + assign_rtl: Rc::new(|left, right| *left = right), } } } @@ -287,6 +320,8 @@ where direction: SyncDirection::Both, transform_ltr: Rc::new(|x| x.clone().into()), transform_rtl: Rc::new(|x| x.clone().into()), + assign_ltr: Rc::new(|right, left| *right = left), + assign_rtl: Rc::new(|left, right| *left = right), } } } diff --git a/src/use_clipboard.rs b/src/use_clipboard.rs index 56cf77a..3469391 100644 --- a/src/use_clipboard.rs +++ b/src/use_clipboard.rs @@ -11,9 +11,6 @@ use leptos::prelude::*; /// [Permissions API](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API). /// Without user permission, reading or altering the clipboard contents is not permitted. /// -/// > This function requires `--cfg=web_sys_unstable_apis` to be activated as -/// > [described in the wasm-bindgen guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html). -/// /// ## Demo /// /// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_clipboard) @@ -80,10 +77,9 @@ pub fn use_clipboard_with_options( let update_text = move |_| { if is_supported.get() { leptos::spawn::spawn_local(async move { - if let Some(clipboard) = window().navigator().clipboard() { - if let Ok(text) = js_fut!(clipboard.read_text()).await { - set_text.set(text.as_string()); - } + let clipboard = window().navigator().clipboard(); + if let Ok(text) = js_fut!(clipboard.read_text()).await { + set_text.set(text.as_string()); } }) } @@ -103,12 +99,11 @@ pub fn use_clipboard_with_options( let value = value.to_owned(); leptos::spawn::spawn_local(async move { - if let Some(clipboard) = window().navigator().clipboard() { - if js_fut!(clipboard.write_text(&value)).await.is_ok() { - set_text.set(Some(value)); - set_copied.set(true); - start(()); - } + let clipboard = window().navigator().clipboard(); + if js_fut!(clipboard.write_text(&value)).await.is_ok() { + set_text.set(Some(value)); + set_copied.set(true); + start(()); } }); } diff --git a/src/use_color_mode.rs b/src/use_color_mode.rs index 04bf944..8d32466 100644 --- a/src/use_color_mode.rs +++ b/src/use_color_mode.rs @@ -1,8 +1,11 @@ use crate::core::url; -use crate::core::StorageType; use crate::core::{ElementMaybeSignal, MaybeRwSignal}; -use crate::storage::{use_storage_with_options, UseStorageOptions}; -use crate::{sync_signal_with_options, use_cookie, use_preferred_dark, SyncSignalOptions}; +use crate::storage::{use_storage_with_options, StorageType, UseStorageOptions}; +use crate::utils::get_header; +use crate::{ + sync_signal_with_options, use_cookie, use_preferred_dark_with_options, SyncSignalOptions, + UsePreferredDarkOptions, +}; use codee::string::FromToStringCodec; use default_struct_builder::DefaultBuilder; use leptos::prelude::wrappers::read::Signal; @@ -84,7 +87,7 @@ use wasm_bindgen::JsCast; /// # } /// ``` /// -/// ### Cookies +/// ### Cookie /// /// To persist color mode in a cookie, use `use_cookie_with_options` and specify `.cookie_enabled(true)`. /// @@ -113,9 +116,24 @@ use wasm_bindgen::JsCast; /// /// ## Server-Side Rendering /// -/// On the server this will by default return `ColorMode::Light`. Persistence with storage is disabled. +/// On the server this will try to read the +/// [`Sec-CH-Prefers-Color-Scheme` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-Prefers-Color-Scheme) +/// to determine the color mode. If the header is not present it will return `ColorMode::Light`. +/// Please have a look at the linked documentation above for that header to see browser support +/// as well as potential server requirements. /// -/// If `cookie_enabled` is set to `true`, cookies will be used and if present this value will be used +/// > If you're using `axum` you have to enable the `"axum"` feature in your Cargo.toml. +/// > In case it's `actix-web` enable the feature `"actix"`, for `spin` enable `"spin"`. +/// +/// ### Bring your own header +/// +/// In case you're neither using Axum, Actix nor Spin, or the default implementation is not to your liking, +/// you can provide your own way of reading the color scheme header value using the option +/// [`crate::UseColorModeOptions::ssr_color_header_getter`]. +/// +/// ### Cookie +/// +/// If `cookie_enabled` is set to `true`, a cookie will be used and if present this value will be used /// on the server as well as on the client. Please note that you have to add the `axum` or `actix` /// feature as described in [`fn@crate::use_cookie`]. /// @@ -152,6 +170,7 @@ where emit_auto, transition_enabled, listen_to_storage_changes, + ssr_color_header_getter, _marker, } = options; @@ -163,7 +182,9 @@ where ]) .collect(); - let preferred_dark = use_preferred_dark(); + let preferred_dark = use_preferred_dark_with_options(UsePreferredDarkOptions { + ssr_color_header_getter, + }); let system = Signal::derive(move || { if preferred_dark.get() { @@ -472,6 +493,14 @@ where /// Defaults to true. listen_to_storage_changes: bool, + /// Getter function to return the string value of the + /// [`Sec-CH-Prefers-Color-Scheme`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-Prefers-Color-Scheme) + /// header. + /// When you use one of the features `"axum"`, `"actix"` or `"spin"` there's a valid default + /// implementation provided. + #[allow(dead_code)] + ssr_color_header_getter: Rc Option>, + #[builder(skip)] _marker: PhantomData, } @@ -497,6 +526,13 @@ impl Default for UseColorModeOptions<&'static str, web_sys::Element> { emit_auto: false, transition_enabled: false, listen_to_storage_changes: true, + ssr_color_header_getter: Rc::new(move || { + get_header!( + HeaderName::from_static("sec-ch-prefers-color-scheme"), + use_locale, + ssr_color_header_getter + ) + }), _marker: PhantomData, } } diff --git a/src/use_cookie.rs b/src/use_cookie.rs index ca33237..ff46a2c 100644 --- a/src/use_cookie.rs +++ b/src/use_cookie.rs @@ -1,6 +1,7 @@ #![allow(clippy::too_many_arguments)] use crate::core::now; +use crate::utils::get_header; use codee::{CodecError, Decoder, Encoder}; use cookie::time::{Duration, OffsetDateTime}; pub use cookie::SameSite; @@ -99,7 +100,7 @@ use std::sync::Arc; /// /// ### Bring your own header /// -/// In case you're neither using Axum nor Actix, or the default implementation is not to your liking, +/// In case you're neither using Axum, Actix nor Spin, or the default implementation is not to your liking, /// you can provide your own way of reading and writing the cookie header value. /// /// ``` @@ -494,79 +495,8 @@ impl Default for UseCookieOptions { domain: None, path: None, same_site: None, - ssr_cookies_header_getter: Arc::new(move || { - #[cfg(feature = "ssr")] - { - #[cfg(all(feature = "actix", feature = "axum"))] - compile_error!("You can only enable one of features \"actix\" and \"axum\" at the same time"); - - #[cfg(all(feature = "actix", feature = "spin"))] - compile_error!("You can only enable one of features \"actix\" and \"spin\" at the same time"); - - #[cfg(all(feature = "axum", feature = "spin"))] - compile_error!("You can only enable one of features \"axum\" and \"spin\" at the same time"); - - #[cfg(feature = "actix")] - const COOKIE: http0_2::HeaderName = http0_2::header::COOKIE; - #[cfg(any(feature = "axum", feature = "spin"))] - const COOKIE: http1::HeaderName = http1::header::COOKIE; - - #[cfg(feature = "actix")] - type HeaderValue = http0_2::HeaderValue; - #[cfg(feature = "axum")] - type HeaderValue = http1::HeaderValue; - - #[cfg(any(feature = "axum", feature = "actix", feature = "spin"))] - let headers; - #[cfg(feature = "actix")] - { - headers = use_context::() - .map(|req| req.headers().clone()); - } - #[cfg(feature = "axum")] - { - headers = use_context::().map(|parts| parts.headers); - } - #[cfg(feature = "spin")] - { - headers = use_context::() - .map(|parts| parts.headers().clone()); - } - - #[cfg(all( - not(feature = "axum"), - not(feature = "actix"), - not(feature = "spin") - ))] - { - warn!("If you're using use_cookie without the feature `axum`, `actix` or `spin` enabled, you should provide the option `ssr_cookies_header_getter`"); - None - } - - #[cfg(any(feature = "axum", feature = "actix"))] - { - headers.map(|headers| { - headers - .get(COOKIE) - .cloned() - .unwrap_or_else(|| HeaderValue::from_static("")) - .to_str() - .unwrap_or_default() - .to_owned() - }) - } - #[cfg(feature = "spin")] - { - headers.and_then(|headers| { - headers - .iter() - .find(|(key, _)| **key == COOKIE) - .and_then(|(_, value)| String::from_utf8(value.to_vec()).ok()) - }) - } - } - #[cfg(not(feature = "ssr"))] - None + ssr_cookies_header_getter: Rc::new(move || { + get_header!(COOKIE, use_cookie, ssr_cookies_header_getter) }), ssr_set_cookie: Arc::new(|cookie: &Cookie| { #[cfg(feature = "ssr")] diff --git a/src/use_display_media.rs b/src/use_display_media.rs index 2ff5a2a..e84326f 100644 --- a/src/use_display_media.rs +++ b/src/use_display_media.rs @@ -132,9 +132,9 @@ async fn create_media(audio: bool) -> Result { .ok_or_else(|| JsValue::from_str("Failed to access window.navigator")) .and_then(|n| n.media_devices())?; - let mut constraints = web_sys::DisplayMediaStreamConstraints::new(); + let constraints = web_sys::DisplayMediaStreamConstraints::new(); if audio { - constraints.audio(&JsValue::from(true)); + constraints.set_audio(&JsValue::from(true)); } let promise = media.get_display_media_with_constraints(&constraints)?; diff --git a/src/use_document.rs b/src/use_document.rs index ac1d379..c6b2fe7 100644 --- a/src/use_document.rs +++ b/src/use_document.rs @@ -1,11 +1,15 @@ use cfg_if::cfg_if; +use js_sys::Function; use std::ops::Deref; use crate::core::impl_ssr_safe_method; #[cfg(not(feature = "ssr"))] use leptos::prelude::*; use wasm_bindgen::JsValue; -use web_sys::NodeList; +use web_sys::{ + Document, Element, HtmlCollection, HtmlElement, HtmlHeadElement, Location, NodeList, + VisibilityState, Window, +}; /// SSR safe `document()`. /// This returns just a new-type wrapper around `Option`. @@ -39,10 +43,10 @@ pub fn use_document() -> UseDocument { /// Return type of [`use_document`]. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct UseDocument(Option); +pub struct UseDocument(Option); impl Deref for UseDocument { - type Target = Option; + type Target = Option; fn deref(&self) -> &Self::Target { &self.0 } @@ -51,22 +55,205 @@ impl Deref for UseDocument { impl UseDocument { impl_ssr_safe_method!( /// Returns `Some(Document)` in the Browser. `None` otherwise. - body(&self) -> Option; + body(&self) -> Option; .unwrap_or_default() ); impl_ssr_safe_method!( /// Returns the active (focused) `Some(web_sys::Element)` in the Browser. `None` otherwise. - active_element(&self) -> Option; + active_element(&self) -> Option; .unwrap_or_default() ); impl_ssr_safe_method!( - query_selector(&self, selector: &str) -> Result, JsValue>; + query_selector(&self, selector: &str) -> Result, JsValue>; .unwrap_or(Ok(None)) ); impl_ssr_safe_method!( query_selector_all(&self, selectors: &str) -> Option> ); + + impl_ssr_safe_method!( + url(&self) -> Option> + ); + + impl_ssr_safe_method!( + document_uri(&self) -> Option> + ); + + impl_ssr_safe_method!( + compat_mode(&self) -> Option + ); + + impl_ssr_safe_method!( + character_set(&self) -> Option + ); + + impl_ssr_safe_method!( + charset(&self) -> Option + ); + + impl_ssr_safe_method!( + input_encoding(&self) -> Option + ); + + impl_ssr_safe_method!( + content_type(&self) -> Option + ); + + impl_ssr_safe_method!( + document_element(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + location(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + referrer(&self) -> Option + ); + + impl_ssr_safe_method!( + last_modified(&self) -> Option + ); + + impl_ssr_safe_method!( + ready_state(&self) -> Option + ); + + impl_ssr_safe_method!( + title(&self) -> Option + ); + + impl_ssr_safe_method!( + dir(&self) -> Option + ); + + impl_ssr_safe_method!( + head(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + images(&self) -> Option + ); + + impl_ssr_safe_method!( + embeds(&self) -> Option + ); + + impl_ssr_safe_method!( + plugins(&self) -> Option + ); + + impl_ssr_safe_method!( + links(&self) -> Option + ); + + impl_ssr_safe_method!( + forms(&self) -> Option + ); + + impl_ssr_safe_method!( + scripts(&self) -> Option + ); + + impl_ssr_safe_method!( + default_view(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + onreadystatechange(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + onbeforescriptexecute(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + onafterscriptexecute(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + onselectionchange(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + current_script(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + anchors(&self) -> Option + ); + + impl_ssr_safe_method!( + applets(&self) -> Option + ); + + impl_ssr_safe_method!( + fullscreen(&self) -> Option + ); + + impl_ssr_safe_method!( + fullscreen_enabled(&self) -> Option + ); + + impl_ssr_safe_method!( + onfullscreenchange(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + onfullscreenerror(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + onpointerlockchange(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + onpointerlockerror(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + /// Hides on server by default + hidden(&self) -> bool; + .unwrap_or(true) + ); + + impl_ssr_safe_method!( + visibility_state(&self) -> Option + ); + + impl_ssr_safe_method!( + onvisibilitychange(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + selected_style_sheet_set(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + last_style_sheet_set(&self) -> Option; + .unwrap_or_default() + ); + + impl_ssr_safe_method!( + preferred_style_sheet_set(&self) -> Option; + .unwrap_or_default() + ); } diff --git a/src/use_event_listener.rs b/src/use_event_listener.rs index 463cc76..c4020de 100644 --- a/src/use_event_listener.rs +++ b/src/use_event_listener.rs @@ -237,11 +237,11 @@ impl UseEventListenerOptions { passive, } = self; - let mut options = web_sys::AddEventListenerOptions::new(); - options.capture(*capture); - options.once(*once); + let options = web_sys::AddEventListenerOptions::new(); + options.set_capture(*capture); + options.set_once(*once); if let Some(passive) = passive { - options.passive(*passive); + options.set_passive(*passive); } options diff --git a/src/use_event_source.rs b/src/use_event_source.rs index 438602d..4366372 100644 --- a/src/use_event_source.rs +++ b/src/use_event_source.rs @@ -186,8 +186,8 @@ where return; } - let mut event_src_opts = web_sys::EventSourceInit::new(); - event_src_opts.with_credentials(with_credentials); + let event_src_opts = web_sys::EventSourceInit::new(); + event_src_opts.set_with_credentials(with_credentials); let es = web_sys::EventSource::new_with_event_source_init_dict(&url, &event_src_opts) .unwrap_throw(); diff --git a/src/use_geolocation.rs b/src/use_geolocation.rs index 8b6ff05..a92be6b 100644 --- a/src/use_geolocation.rs +++ b/src/use_geolocation.rs @@ -185,10 +185,10 @@ impl UseGeolocationOptions { .. } = self; - let mut options = web_sys::PositionOptions::new(); - options.enable_high_accuracy(*enable_high_accuracy); - options.maximum_age(*maximum_age); - options.timeout(*timeout); + let options = web_sys::PositionOptions::new(); + options.set_enable_high_accuracy(*enable_high_accuracy); + options.set_maximum_age(*maximum_age); + options.set_timeout(*timeout); options } diff --git a/src/use_intersection_observer.rs b/src/use_intersection_observer.rs index 7a724b7..9aebe3a 100644 --- a/src/use_intersection_observer.rs +++ b/src/use_intersection_observer.rs @@ -155,8 +155,9 @@ where return; } - let mut options = web_sys::IntersectionObserverInit::new(); - options.root_margin(&root_margin).threshold( + let options = web_sys::IntersectionObserverInit::new(); + options.set_root_margin(&root_margin); + options.set_threshold( &thresholds .iter() .copied() @@ -166,7 +167,7 @@ where if let Some(Some(root)) = root { let root: web_sys::Element = root.clone().into(); - options.root(Some(&root)); + options.set_root(Some(&root)); } let obs = web_sys::IntersectionObserver::new_with_options( diff --git a/src/use_locale.rs b/src/use_locale.rs new file mode 100644 index 0000000..76b1274 --- /dev/null +++ b/src/use_locale.rs @@ -0,0 +1,91 @@ +use crate::{use_locales_with_options, UseLocalesOptions}; +use leptos::*; +use unic_langid::LanguageIdentifier; + +/// Reactive locale matching. +/// +/// Returns the first matching locale given by [`fn@crate::use_locales`] that is also found in +/// the `supported` list. In case there is no match, then the first locale in `supported` will be +/// returned. +/// +/// > If `supported` is empty, this function will panic! +/// +/// Matching is done by using the [`fn@unic_langid::LanguageIdentifier::matches`] method. +/// +/// ## Demo +/// +/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_locale) +/// +/// ## Usage +/// +/// ``` +/// # use leptos::*; +/// # use leptos_use::use_locale; +/// use unic_langid::langid_slice; +/// # +/// # #[component] +/// # fn Demo() -> impl IntoView { +/// let locale = use_locale(langid_slice!["en", "de", "fr"]); +/// # +/// # view! { } +/// # } +/// ``` +/// +/// ## Server-Side Rendering +/// +/// See [`fn@crate::use_locales`] +pub fn use_locale(supported: S) -> Signal +where + S: IntoIterator, + S::Item: AsRef, +{ + use_locale_with_options(supported, UseLocaleOptions::default()) +} + +/// Version of [`fn@crate::use_locale`] that takes a `UseLocaleOptions`. See [`fn@crate::use_locale`] for how to use. +pub fn use_locale_with_options( + supported: S, + options: UseLocaleOptions, +) -> Signal +where + S: IntoIterator, + S::Item: AsRef, +{ + let client_locales = use_locales_with_options(options); + + let supported = supported + .into_iter() + .map(|l| l.as_ref().clone()) + .collect::>(); + + const EMPTY_ERR_MSG: &str = "Empty supported list. You have to provide at least one locale in the `supported` parameter"; + + assert!(!supported.is_empty(), "{}", EMPTY_ERR_MSG); + + Signal::derive(move || { + let supported = supported.clone(); + + client_locales.with(|client_locales| { + let mut first_supported = None; + + for s in supported { + if first_supported.is_none() { + first_supported = Some(s.clone()); + } + + for client_locale in client_locales { + let client_locale: LanguageIdentifier = client_locale + .parse() + .expect("Client should provide a list of valid unicode locales"); + if client_locale.matches(&s, true, true) { + return s; + } + } + } + + unreachable!("{}", EMPTY_ERR_MSG); + }) + }) +} + +pub type UseLocaleOptions = UseLocalesOptions; diff --git a/src/use_locales.rs b/src/use_locales.rs new file mode 100644 index 0000000..8762dc5 --- /dev/null +++ b/src/use_locales.rs @@ -0,0 +1,114 @@ +use crate::utils::get_header; +use default_struct_builder::DefaultBuilder; +use leptos::*; +use std::rc::Rc; + +/// Reactive locales. +/// +/// If called on the client-side this function returns the value of +/// [`navigator.languages`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/languages) +/// and listens for changes to that property. +/// +/// See "Server-Side Rendering" below. +/// +/// ## Demo +/// +/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_locales) +/// +/// ## Usage +/// +/// ``` +/// # use leptos::*; +/// # use leptos_use::use_locales; +/// # +/// # #[component] +/// # fn Demo() -> impl IntoView { +/// let locales = use_locales(); +/// # +/// # view! { } +/// # } +/// ``` +/// +/// ## Server-Side Rendering +/// +/// On the server this returns the parsed value of the `accept-language` header. +/// +/// > If you're using `axum` you have to enable the `"axum"` feature in your Cargo.toml. +/// > In case it's `actix-web` enable the feature `"actix"`, for `spin` enable `"spin"`. +/// +/// ### Bring your own header +/// +/// In case you're neither using Axum, Actix nor Spin, or the default implementation is not to your liking, +/// you can provide your own way of reading the language header value using the option +/// [`crate::UseLocalesOptions::ssr_lang_header_getter`]. +pub fn use_locales() -> Signal> { + use_locales_with_options(UseLocalesOptions::default()) +} + +/// Version of [`fn@crate::use_locales`] that takes a `UseLocalesOptions`. See [`fn@crate::use_locales`] for how to use. +pub fn use_locales_with_options(options: UseLocalesOptions) -> Signal> { + #[cfg(not(feature = "ssr"))] + { + let _ = options; + + let read_navigator_languages = || { + let window = crate::use_window(); + let navigator = window.navigator(); + navigator + .map(|navigator| navigator.languages().to_vec()) + .unwrap_or_default() + .into_iter() + .filter_map(|x| x.as_string()) + .collect::>() + }; + + let (locales, set_locales) = create_signal(read_navigator_languages()); + + let _ = crate::use_event_listener(crate::use_window(), ev::languagechange, move |_| { + set_locales.update(|locales| *locales = read_navigator_languages()); + }); + + locales.into() + } + + #[cfg(feature = "ssr")] + { + let UseLocalesOptions { + ssr_lang_header_getter, + } = options; + + let accept_language = ssr_lang_header_getter().unwrap_or_default(); + + let locales = accept_language + .split(',') + .map(|locale| { + locale + .split_once(';') + .map(|x| x.0) + .unwrap_or(locale) + .to_owned() + }) + .collect::>(); + + Signal::derive(move || locales.clone()) + } +} + +/// Options for [`fn@crate::use_locales_with_options`]. +#[derive(DefaultBuilder)] +pub struct UseLocalesOptions { + /// Getter function to return the string value of the accept languange header. + /// When you use one of the features `"axum"`, `"actix"` or `"spin"` there's a valid default implementation provided. + #[allow(dead_code)] + ssr_lang_header_getter: Rc Option>, +} + +impl Default for UseLocalesOptions { + fn default() -> Self { + Self { + ssr_lang_header_getter: Rc::new(move || { + get_header!(ACCEPT_LANGUAGE, use_locale, ssr_lang_header_getter) + }), + } + } +} diff --git a/src/use_media_query.rs b/src/use_media_query.rs index 7ddf1f0..0bfee2e 100644 --- a/src/use_media_query.rs +++ b/src/use_media_query.rs @@ -39,6 +39,7 @@ use std::rc::Rc; /// /// * [`fn@crate::use_preferred_dark`] /// * [`fn@crate::use_preferred_contrast`] +/// * [`fn@crate::use_prefers_reduced_motion`] pub fn use_media_query(query: impl Into>) -> Signal { let query = query.into(); diff --git a/src/use_mouse.rs b/src/use_mouse.rs index faf58eb..92768ca 100644 --- a/src/use_mouse.rs +++ b/src/use_mouse.rs @@ -5,8 +5,8 @@ use crate::{use_event_listener_with_options, use_window, UseEventListenerOptions use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; use leptos::ev::{dragover, mousemove, touchend, touchmove, touchstart}; -use leptos::prelude::wrappers::read::Signal; use leptos::prelude::*; +use std::convert::Infallible; use std::marker::PhantomData; use wasm_bindgen::{JsCast, JsValue}; @@ -231,10 +231,10 @@ where _marker: PhantomData, } -impl Default for UseMouseOptions { +impl Default for UseMouseOptions { fn default() -> Self { Self { - coord_type: UseMouseCoordType::::default(), + coord_type: UseMouseCoordType::default(), target: use_window(), touch: true, reset_on_touch_ends: false, @@ -254,7 +254,7 @@ pub enum UseMouseCoordType { Custom(E), } -impl Default for UseMouseCoordType { +impl Default for UseMouseCoordType { fn default() -> Self { Self::Page } @@ -298,10 +298,15 @@ impl UseMouseEventExtractor for UseMouseCoord } } -#[derive(Clone)] -pub struct UseMouseEventExtractorDefault; +impl UseMouseEventExtractor for Infallible { + fn extract_mouse_coords(&self, _: &web_sys::MouseEvent) -> Option<(f64, f64)> { + unreachable!() + } -impl UseMouseEventExtractor for UseMouseEventExtractorDefault {} + fn extract_touch_coords(&self, _: &web_sys::Touch) -> Option<(f64, f64)> { + unreachable!() + } +} /// Return type of [`use_mouse`]. pub struct UseMouseReturn { diff --git a/src/use_mouse_in_element.rs b/src/use_mouse_in_element.rs index 94121cd..e01f994 100644 --- a/src/use_mouse_in_element.rs +++ b/src/use_mouse_in_element.rs @@ -1,12 +1,12 @@ use crate::core::{ElementMaybeSignal, Position}; use crate::{ - use_mouse_with_options, use_window, UseMouseCoordType, UseMouseEventExtractor, - UseMouseEventExtractorDefault, UseMouseOptions, UseMouseReturn, UseMouseSourceType, UseWindow, + use_mouse_with_options, use_window, UseMouseCoordType, UseMouseEventExtractor, UseMouseOptions, + UseMouseReturn, UseMouseSourceType, UseWindow, }; use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; -use leptos::prelude::wrappers::read::Signal; use leptos::prelude::*; +use std::convert::Infallible; use std::marker::PhantomData; /// Reactive mouse position related to an element. @@ -196,12 +196,10 @@ where _marker: PhantomData, } -impl Default - for UseMouseInElementOptions -{ +impl Default for UseMouseInElementOptions { fn default() -> Self { Self { - coord_type: UseMouseCoordType::::default(), + coord_type: UseMouseCoordType::default(), target: use_window(), touch: true, reset_on_touch_ends: false, diff --git a/src/use_mutation_observer.rs b/src/use_mutation_observer.rs index 2333399..abf97f6 100644 --- a/src/use_mutation_observer.rs +++ b/src/use_mutation_observer.rs @@ -216,20 +216,20 @@ impl From for web_sys::MutationObserverInit { character_data_old_value, } = val; - let mut init = Self::new(); + let init = Self::new(); - init.subtree(subtree) - .child_list(child_list) - .attributes(attributes) - .attribute_old_value(attribute_old_value) - .character_data_old_value(character_data_old_value); + init.set_subtree(subtree); + init.set_child_list(child_list); + init.set_attributes(attributes); + init.set_attribute_old_value(attribute_old_value); + init.set_character_data_old_value(character_data_old_value); if let Some(attribute_filter) = attribute_filter { let array = js_sys::Array::from_iter(attribute_filter.into_iter().map(JsValue::from)); - init.attribute_filter(array.unchecked_ref()); + init.set_attribute_filter(array.unchecked_ref()); } if let Some(character_data) = character_data { - init.character_data(character_data); + init.set_character_data(character_data); } init diff --git a/src/use_preferred_contrast.rs b/src/use_preferred_contrast.rs index 3d979b5..feb2856 100644 --- a/src/use_preferred_contrast.rs +++ b/src/use_preferred_contrast.rs @@ -28,6 +28,7 @@ use std::fmt::Display; /// /// * [`fn@crate::use_media_query`] /// * [`fn@crate::use_preferred_dark`] +/// * [`fn@crate::use_prefers_reduced_motion`] pub fn use_preferred_contrast() -> Signal { let is_more = use_media_query("(prefers-contrast: more)"); let is_less = use_media_query("(prefers-contrast: less)"); @@ -55,7 +56,6 @@ pub enum PreferredContrast { #[default] NoPreference, } - impl Display for PreferredContrast { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/src/use_preferred_dark.rs b/src/use_preferred_dark.rs index 0db93fa..0a42bad 100644 --- a/src/use_preferred_dark.rs +++ b/src/use_preferred_dark.rs @@ -1,5 +1,7 @@ -use crate::use_media_query; +use crate::utils::get_header; +use default_struct_builder::DefaultBuilder; use leptos::prelude::*; +use std::rc::Rc; /// Reactive [dark theme preference](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme). /// @@ -20,12 +22,67 @@ use leptos::prelude::*; /// /// ## Server-Side Rendering /// -/// On the server this functions returns a Signal that is always `false`. +/// On the server this will try to read the +/// [`Sec-CH-Prefers-Color-Scheme` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-Prefers-Color-Scheme) +/// to determine the color mode. If the header is not present it will return `ColorMode::Light`. +/// Please have a look at the linked documentation above for that header to see browser support +/// as well as potential server requirements. +/// +/// > If you're using `axum` you have to enable the `"axum"` feature in your Cargo.toml. +/// > In case it's `actix-web` enable the feature `"actix"`, for `spin` enable `"spin"`. +/// +/// ### Bring your own header +/// +/// In case you're neither using Axum, Actix nor Spin, or the default implementation is not to your liking, +/// you can provide your own way of reading the color scheme header value using the option +/// [`crate::UsePreferredDarkOptions::ssr_color_header_getter`]. /// /// ## See also /// /// * [`fn@crate::use_media_query`] /// * [`fn@crate::use_preferred_contrast`] +/// * [`fn@crate::use_prefers_reduced_motion`] pub fn use_preferred_dark() -> Signal { - use_media_query("(prefers-color-scheme: dark)") + use_preferred_dark_with_options(Default::default()) +} + +/// Version of [`fn@crate::use_preferred_dark`] that accepts a `UsePreferredDarkOptions`. +pub fn use_preferred_dark_with_options(options: UsePreferredDarkOptions) -> Signal { + #[cfg(not(feature = "ssr"))] + { + let _ = options; + + crate::use_media_query("(prefers-color-scheme: dark)") + } + + #[cfg(feature = "ssr")] + { + Signal::derive(move || (options.ssr_color_header_getter)() == Some("dark".to_string())) + } +} + +/// Options for [`fn@crate::use_preferred_dark_with_options`]. +#[derive(DefaultBuilder)] +pub struct UsePreferredDarkOptions { + /// Getter function to return the string value of the + /// [`Sec-CH-Prefers-Color-Scheme`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-Prefers-Color-Scheme) + /// header. + /// When you use one of the features `"axum"`, `"actix"` or `"spin"` there's a valid default + /// implementation provided. + #[allow(dead_code)] + pub(crate) ssr_color_header_getter: Rc Option>, +} + +impl Default for UsePreferredDarkOptions { + fn default() -> Self { + Self { + ssr_color_header_getter: Rc::new(move || { + get_header!( + HeaderName::from_static("sec-ch-prefers-color-scheme"), + use_locale, + ssr_color_header_getter + ) + }), + } + } } diff --git a/src/use_prefers_reduced_motion.rs b/src/use_prefers_reduced_motion.rs new file mode 100644 index 0000000..b1afd55 --- /dev/null +++ b/src/use_prefers_reduced_motion.rs @@ -0,0 +1,102 @@ +use crate::utils::get_header; +use default_struct_builder::DefaultBuilder; +use leptos::*; +use std::rc::Rc; + +/// Reactive [reduced motions preference](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion). +/// +/// ## Demo +/// +/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_prefers_reduced_motion) +/// +/// ## Usage +/// +/// ``` +/// # use leptos::*; +/// # use leptos_use::use_prefers_reduced_motion; +/// # use leptos_use::docs::BooleanDisplay; +/// # +/// # #[component] +/// # fn Demo() -> impl IntoView { +/// let is_reduced_motion_preferred = use_prefers_reduced_motion(); +/// +/// view! { +///
+///

Prefers reduced motions:

+///

+/// Update reduce motion preference +/// +/// documentation. +/// +///

+///
+/// } +/// # } +/// ``` +/// +/// ## Server-Side Rendering +/// +/// On the server this will try to read the +/// [`Sec-CH-Prefers-Reduced-Motion` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-Prefers-Reduced-Motion) +/// to indicate the preference for animations to be displayed with reduced motion. +/// Please have a look at the linked documentation above to see browser support +/// as well as potential serve requirements. +/// +/// > If you're using `axum` you have to enable the `"axum"` feature in your Cargo.toml. +/// > In case it's `actix-web` enable the feature `"actix"`, for `spin` enable `"spin"`. +/// +/// ### Bring your own header +/// +/// In case you're neither using Axum, Actix nor Spin, or the default implementation is not to your +/// liking, you can provide your own way of reading the reduced motion header value using the option +/// [`crate::UsePrefersReducedMotionOptions::ssr_motion_header_getter`]. +/// +/// ## See also +/// +/// * [`fn@crate::use_media_query`] +/// * [`fn@crate::use_preferred_contrast`] +/// * [`fn@crate::use_preferred_dark`] +pub fn use_prefers_reduced_motion() -> Signal { + use_prefers_reduced_motion_with_options(UsePrefersReducedMotionOptions::default()) +} + +/// Version of [`fn@crate::use_prefers_reduced_motion`] that takes a `UsePrefersReducedMotionOptions`. See [`fn@crate::use_prefers_reduced_motion`] for how to use. +pub fn use_prefers_reduced_motion_with_options( + options: UsePrefersReducedMotionOptions, +) -> Signal { + #[cfg(not(feature = "ssr"))] + { + let _ = options; + crate::use_media_query("(prefers-reduced-motion: reduce)") + } + #[cfg(feature = "ssr")] + { + Signal::derive(move || (options.ssr_motion_header_getter)() == Some("reduce".to_string())) + } +} + +/// Options for [`fn@crate::use_prefers_reduced_motion_with_options`]. +#[derive(DefaultBuilder)] +pub struct UsePrefersReducedMotionOptions { + /// Getter function to return the string value of the + /// [`Sec-CH-Prefers-Reduced-Motion`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-Prefers-Reduced-Motion) + /// header. + /// When you use one of the features `"axum"`, `"actix"` or `"spin"` there's a valid default + /// implementation provided. + #[allow(dead_code)] + pub(crate) ssr_motion_header_getter: Rc Option>, +} + +impl Default for UsePrefersReducedMotionOptions { + fn default() -> Self { + Self { + ssr_motion_header_getter: Rc::new(move || { + get_header!( + HeaderName::from_static("sec-ch-prefers-reduced-motion"), + use_locale, + ssr_motion_header_getter + ) + }), + } + } +} diff --git a/src/use_resize_observer.rs b/src/use_resize_observer.rs index 4bdb478..6682580 100644 --- a/src/use_resize_observer.rs +++ b/src/use_resize_observer.rs @@ -172,8 +172,8 @@ pub struct UseResizeObserverOptions { impl From for web_sys::ResizeObserverOptions { fn from(val: UseResizeObserverOptions) -> Self { - let mut options = web_sys::ResizeObserverOptions::new(); - options.box_( + let options = web_sys::ResizeObserverOptions::new(); + options.set_box( val.box_ .unwrap_or(web_sys::ResizeObserverBoxOptions::ContentBox), ); diff --git a/src/use_scroll.rs b/src/use_scroll.rs index a1f28c6..dc09bdd 100644 --- a/src/use_scroll.rs +++ b/src/use_scroll.rs @@ -233,14 +233,14 @@ where if let Some(element) = element { let element = element.into(); - let mut scroll_options = web_sys::ScrollToOptions::new(); - scroll_options.behavior(behavior.get_untracked().into()); + let scroll_options = web_sys::ScrollToOptions::new(); + scroll_options.set_behavior(behavior.get_untracked().into()); if let Some(x) = x { - scroll_options.left(x); + scroll_options.set_left(x); } if let Some(y) = y { - scroll_options.top(y); + scroll_options.set_top(y); } element.scroll_to_with_scroll_to_options(&scroll_options); diff --git a/src/use_timeout_fn.rs b/src/use_timeout_fn.rs index 558f93b..ca548d8 100644 --- a/src/use_timeout_fn.rs +++ b/src/use_timeout_fn.rs @@ -1,6 +1,5 @@ use leptos::leptos_dom::helpers::TimeoutHandle; use leptos::prelude::diagnostics::SpecialNonReactiveZone; -use leptos::prelude::wrappers::read::Signal; use leptos::prelude::*; use std::marker::PhantomData; use std::sync::{Arc, Mutex}; @@ -32,6 +31,11 @@ use std::time::Duration; /// # view! { } /// # } /// ``` +/// +/// ## Server-Side Rendering +/// +/// On the server the callback will never be run. The returned functions are all no-ops and +/// `is_pending` will always be `false`. pub fn use_timeout_fn( callback: CbFn, delay: D, @@ -45,59 +49,82 @@ where let (is_pending, set_pending) = signal(false); - let timer = Arc::new(Mutex::new(None::)); + let start; + let stop; - let clear = { - let timer = Arc::clone(&timer); + #[cfg(not(feature = "ssr"))] + { + use leptos::leptos_dom::helpers::TimeoutHandle; + use std::cell::Cell; + use std::rc::Rc; + use std::time::Duration; - move || { - let timer = timer.lock().unwrap(); - if let Some(timer) = *timer { - timer.clear(); + let delay = delay.into(); + + let timer = Arc::new(Mutex::new(None::)); + + let clear = { + let timer = Arc::clone(&timer); + + move || { + let timer = timer.lock().unwrap(); + if let Some(timer) = *timer { + timer.clear(); + } } - } - }; + }; - let stop = { - let clear = clear.clone(); + stop = { + let clear = clear.clone(); - move || { - set_pending.set(false); - clear(); - } - }; + move || { + set_pending.set(false); + clear(); + } + }; - let start = { - let timer = Arc::clone(&timer); - let callback = callback.clone(); + start = { + let timer = Arc::clone(&timer); + let callback = callback.clone(); - move |arg: Arg| { - set_pending.set(true); + move |arg: Arg| { + set_pending.set(true); - let handle = set_timeout_with_handle( - { - let timer = Arc::clone(&timer); - let callback = callback.clone(); + let handle = set_timeout_with_handle( + { + let timer = Arc::clone(&timer); + let callback = callback.clone(); - move || { - set_pending.set(false); - *timer.lock().unwrap() = None; + move || { + set_pending.set(false); + *timer.lock().unwrap() = None; - #[cfg(debug_assertions)] - let _z = SpecialNonReactiveZone::enter(); + #[cfg(debug_assertions)] + let _z = SpecialNonReactiveZone::enter(); - callback(arg); - } - }, - Duration::from_millis(delay.get_untracked() as u64), - ) - .ok(); + callback(arg); + } + }, + Duration::from_millis(delay.get_untracked() as u64), + ) + .ok(); - *timer.lock().unwrap() = handle; - } - }; + *timer.lock().unwrap() = handle; + } + }; - on_cleanup(clear); + on_cleanup(clear); + } + + #[cfg(feature = "ssr")] + { + let _ = set_pending; + let _ = callback; + let _ = delay; + + start = move |_: Arg| (); + stop = move || (); + } UseTimeoutFnReturn { is_pending: is_pending.into(), diff --git a/src/use_user_media.rs b/src/use_user_media.rs index 64d2513..d6a4705 100644 --- a/src/use_user_media.rs +++ b/src/use_user_media.rs @@ -136,12 +136,12 @@ async fn create_media(video: bool, audio: bool) -> Result for web_sys::NotificationDirection { /// See [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/notification) for more info. /// /// The following implementations are missing: -/// - `renotify` -/// - `vibrate` -/// - `silent` -/// - `image` +/// - `vibrate` #[derive(DefaultBuilder, Clone)] #[cfg_attr(feature = "ssr", allow(dead_code))] pub struct UseWebNotificationOptions { @@ -253,14 +251,25 @@ pub struct UseWebNotificationOptions { #[builder(into)] icon: Option, + /// The URL of the image to be displayed as part of the notification as specified + /// in the constructor's options parameter. + #[builder(into)] + image: Option, + /// A boolean value indicating that a notification should remain active until the /// user clicks or dismisses it, rather than closing automatically. require_interaction: bool, - // /// A boolean value specifying whether the user should be notified after a new notification replaces an old one. - // /// The default is `false`, which means they won't be notified. If `true`, then `tag` also must be set. - // #[builder(into)] - // renotify: bool, + /// A boolean value specifying whether the user should be notified after a new notification replaces an old one. + /// The default is `false`, which means they won't be notified. If `true`, then `tag` also must be set. + #[builder(into)] + renotify: bool, + + /// A boolean value specifying whether the notification should be silent, regardless of the device settings. + /// The default is `false`, which means the notification is not silent. If `true`, then the notification will be silent. + #[builder(into)] + silent: Option, + /// Called when the user clicks on displayed `Notification`. on_click: Rc, @@ -284,8 +293,10 @@ impl Default for UseWebNotificationOptions { language: None, tag: None, icon: None, + image: None, require_interaction: false, - // renotify: false, + renotify: false, + silent: None, on_click: Rc::new(|_| {}), on_close: Rc::new(|_| {}), on_error: Rc::new(|_| {}), @@ -296,27 +307,31 @@ impl Default for UseWebNotificationOptions { impl From<&UseWebNotificationOptions> for web_sys::NotificationOptions { fn from(options: &UseWebNotificationOptions) -> Self { - let mut web_sys_options = Self::new(); + let web_sys_options = Self::new(); - web_sys_options - .dir(options.direction.into()) - .require_interaction(options.require_interaction); - // .renotify(options.renotify); + web_sys_options.set_dir(options.direction.into()); + web_sys_options.set_require_interaction(options.require_interaction); + web_sys_options.set_renotify(options.renotify); + web_sys_options.set_silent(options.silent); if let Some(body) = &options.body { - web_sys_options.body(body); + web_sys_options.set_body(body); } if let Some(icon) = &options.icon { - web_sys_options.icon(icon); + web_sys_options.set_icon(icon); + } + + if let Some(image) = &options.image { + web_sys_options.set_image(image); } if let Some(language) = &options.language { - web_sys_options.lang(language); + web_sys_options.set_lang(language); } if let Some(tag) = &options.tag { - web_sys_options.tag(tag); + web_sys_options.set_tag(tag); } web_sys_options @@ -328,9 +343,7 @@ impl From<&UseWebNotificationOptions> for web_sys::NotificationOptions { /// See [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/notification) for more info. /// /// The following implementations are missing: -/// - `vibrate` -/// - `silent` -/// - `image` +/// - `vibrate` #[derive(DefaultBuilder, Default)] #[cfg_attr(feature = "ssr", allow(dead_code))] pub struct ShowOptions { @@ -365,46 +378,65 @@ pub struct ShowOptions { #[builder(into)] icon: Option, + /// The URL of the image to be displayed as part of the notification as specified + /// in the constructor's options parameter. + #[builder(into)] + image: Option, + /// A boolean value indicating that a notification should remain active until the /// user clicks or dismisses it, rather than closing automatically. #[builder(into)] require_interaction: Option, - // /// A boolean value specifying whether the user should be notified after a new notification replaces an old one. - // /// The default is `false`, which means they won't be notified. If `true`, then `tag` also must be set. - // #[builder(into)] - // renotify: Option, + + /// A boolean value specifying whether the user should be notified after a new notification replaces an old one. + /// The default is `false`, which means they won't be notified. If `true`, then `tag` also must be set. + #[builder(into)] + renotify: Option, + + /// A boolean value specifying whether the notification should be silent, regardless of the device settings. + /// The default is `false`, which means the notification is not silent. If `true`, then the notification will be silent. + #[builder(into)] + silent: Option, } #[cfg(not(feature = "ssr"))] impl ShowOptions { fn override_notification_options(&self, options: &mut web_sys::NotificationOptions) { if let Some(direction) = self.direction { - options.dir(direction.into()); + options.set_dir(direction.into()); } if let Some(require_interaction) = self.require_interaction { - options.require_interaction(require_interaction); + options.set_require_interaction(require_interaction); } if let Some(body) = &self.body { - options.body(body); + options.set_body(body); } if let Some(icon) = &self.icon { - options.icon(icon); + options.set_icon(icon); + } + + if let Some(image) = &self.image { + options.set_image(image); } if let Some(language) = &self.language { - options.lang(language); + options.set_lang(language); } if let Some(tag) = &self.tag { - options.tag(tag); + options.set_tag(tag); } - // if let Some(renotify) = &self.renotify { - // options.renotify(renotify); - // } + if let Some(renotify) = self.renotify { + options.set_renotify(renotify); + } + + if let Some(silent) = self.silent { + options.set_silent(Some(silent)); + } } } @@ -437,7 +469,7 @@ impl From for NotificationPermission { web_sys::NotificationPermission::Default => Self::Default, web_sys::NotificationPermission::Granted => Self::Granted, web_sys::NotificationPermission::Denied => Self::Denied, - web_sys::NotificationPermission::__Nonexhaustive => Self::Default, + _ => Self::Default, } } } diff --git a/src/use_websocket.rs b/src/use_websocket.rs index e658a81..74f28fd 100644 --- a/src/use_websocket.rs +++ b/src/use_websocket.rs @@ -6,7 +6,7 @@ use std::sync::{atomic::AtomicBool, Arc}; use std::time::Duration; use thiserror::Error; -use crate::core::ConnectionReadyState; +use crate::{core::ConnectionReadyState, ReconnectLimit}; use codee::{ CodecError, Decoder, Encoder, HybridCoderError, HybridDecoder, HybridEncoder, IsBinary, }; @@ -609,26 +609,6 @@ where send, } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum ReconnectLimit { - Infinite, - Limited(u64), -} - -impl Default for ReconnectLimit { - fn default() -> Self { - ReconnectLimit::Limited(3) - } -} - -impl ReconnectLimit { - pub fn is_exceeded_by(self, times: u64) -> bool { - match self { - ReconnectLimit::Infinite => false, - ReconnectLimit::Limited(limit) => times >= limit, - } - } -} type ArcFnBytes = Arc; diff --git a/src/utils/header.rs b/src/utils/header.rs new file mode 100644 index 0000000..0e9d6cd --- /dev/null +++ b/src/utils/header.rs @@ -0,0 +1,76 @@ +#[cfg(feature = "actix")] +use http0_2::HeaderName; +#[cfg(any(feature = "axum", feature = "spin"))] +use http1::HeaderName; +use leptos::*; + +/// Get the value of the header with the given name. +/// +/// This function is only meant to be used on the server. +/// So it is only defined when the feature `"ssr"` is enabled together with one of the +/// features `"axum"`, `"actix"` or `"spin"`. +/// +/// ## Example +/// +/// ```ignore +/// # use leptos_use::utils::header; +/// # +/// let content_len = header(http::header::CONTENT_LENGTH); +/// ``` +pub fn header(name: N) -> Option +where + N: Into, +{ + let name = name.into(); + + #[cfg(all(feature = "actix", feature = "axum"))] + compile_error!("You can only enable one of features \"actix\" and \"axum\" at the same time"); + + #[cfg(all(feature = "actix", feature = "spin"))] + compile_error!("You can only enable one of features \"actix\" and \"spin\" at the same time"); + + #[cfg(all(feature = "axum", feature = "spin"))] + compile_error!("You can only enable one of features \"axum\" and \"spin\" at the same time"); + + #[cfg(feature = "actix")] + type HeaderValue = http0_2::HeaderValue; + #[cfg(feature = "axum")] + type HeaderValue = http1::HeaderValue; + + #[cfg(any(feature = "axum", feature = "actix", feature = "spin"))] + let headers; + #[cfg(feature = "actix")] + { + headers = use_context::().map(|req| req.headers().clone()); + } + #[cfg(feature = "axum")] + { + headers = use_context::().map(|parts| parts.headers); + } + #[cfg(feature = "spin")] + { + headers = use_context::().map(|parts| parts.headers().clone()); + } + + #[cfg(any(feature = "axum", feature = "actix"))] + { + headers.map(|headers| { + headers + .get(name) + .cloned() + .unwrap_or_else(|| HeaderValue::from_static("")) + .to_str() + .unwrap_or_default() + .to_owned() + }) + } + #[cfg(feature = "spin")] + { + headers.and_then(|headers| { + headers + .iter() + .find(|(key, _)| **key == name) + .and_then(|(_, value)| String::from_utf8(value.to_vec()).ok()) + }) + } +} diff --git a/src/utils/header_macro.rs b/src/utils/header_macro.rs new file mode 100644 index 0000000..2664f78 --- /dev/null +++ b/src/utils/header_macro.rs @@ -0,0 +1,43 @@ +#![allow(unused_macros, unused_imports)] + +macro_rules! get_header { + ( + $header_name:expr, + $function_name:ident, + $option_name:ident + $(,)? + ) => { + if cfg!(feature = "ssr") { + #[cfg(all( + not(feature = "axum"), + not(feature = "actix"), + not(feature = "spin") + ))] + { + leptos::logging::warn!( + "If you're using `{}` with SSR but without any of the features `axum`, `actix` or `spin` enabled, you have to provide the option `{}`", + stringify!($function_name), + stringify!($option_name) + ); + return None; + } + + #[cfg(feature = "actix")] + #[allow(unused_imports)] + use http0_2::{HeaderName, header::*}; + #[cfg(any(feature = "axum", feature = "spin"))] + #[allow(unused_imports)] + use http1::{HeaderName, header::*}; + + #[cfg(any(feature = "axum", feature = "actix", feature = "spin"))] + { + let header_name = $header_name; + crate::utils::header(header_name) + } + } else { + None + } + }; +} + +pub(crate) use get_header; diff --git a/src/utils/js_value_from_to_string.rs b/src/utils/js_value_from_to_string.rs index f371c96..4149c4c 100644 --- a/src/utils/js_value_from_to_string.rs +++ b/src/utils/js_value_from_to_string.rs @@ -1,3 +1,5 @@ +#![allow(unused_macros, unused_imports)] + macro_rules! js_value_from_to_string { ($name:ident) => { impl From<$name> for JsValue { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 906f4e1..76b179a 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,4 +1,11 @@ mod filters; +#[cfg(all( + feature = "ssr", + any(feature = "axum", feature = "actix", feature = "spin") +))] +mod header; +mod header_macro; +#[cfg(feature = "is")] mod is; mod js; mod js_value_from_to_string; @@ -7,7 +14,17 @@ mod signal_filtered; mod use_derive_signal; pub use filters::*; +#[cfg(all( + feature = "ssr", + any(feature = "axum", feature = "actix", feature = "spin") +))] +pub use header::*; +#[allow(unused_imports)] +pub(crate) use header_macro::*; +#[cfg(feature = "is")] pub use is::*; +#[allow(unused_imports)] pub(crate) use js_value_from_to_string::*; pub use pausable::*; +#[allow(unused_imports)] pub(crate) use signal_filtered::*; diff --git a/src/utils/signal_filtered.rs b/src/utils/signal_filtered.rs index ad9ebe7..52c1144 100644 --- a/src/utils/signal_filtered.rs +++ b/src/utils/signal_filtered.rs @@ -1,3 +1,5 @@ +#![allow(unused_macros, unused_imports)] + macro_rules! signal_filtered { ( $(#[$outer:meta])* diff --git a/src/watch_debounced.rs b/src/watch_debounced.rs index 61491cd..3a9b1e8 100644 --- a/src/watch_debounced.rs +++ b/src/watch_debounced.rs @@ -1,4 +1,4 @@ -use crate::{watch_with_options, DebounceOptions, WatchOptions}; +use crate::{watch_with_options, utils::DebounceOptions, WatchOptions}; use default_struct_builder::DefaultBuilder; use leptos::prelude::*; diff --git a/src/watch_throttled.rs b/src/watch_throttled.rs index fc71f46..d4e8bfe 100644 --- a/src/watch_throttled.rs +++ b/src/watch_throttled.rs @@ -1,4 +1,4 @@ -use crate::{watch_with_options, ThrottleOptions, WatchOptions}; +use crate::{watch_with_options, utils::ThrottleOptions, WatchOptions}; use default_struct_builder::DefaultBuilder; /// A throttled version of `leptos::watch`. diff --git a/template/src/{{ module }}/{{ function_name }}.ffizer.hbs.rs b/template/src/{{ module }}/{{ function_name }}.ffizer.hbs.rs index d3ed23c..1248179 100644 --- a/template/src/{{ module }}/{{ function_name }}.ffizer.hbs.rs +++ b/template/src/{{ module }}/{{ function_name }}.ffizer.hbs.rs @@ -22,19 +22,23 @@ use leptos::prelude::*; /// # /// # view! { } /// # } -/// ```{{#if feature}} +/// ``` +/// +/// ## Server-Side Rendering +/// {{#if feature}} // #[doc(cfg(feature = "{{feature}}"))]{{/if}} + pub fn {{ function_name }}() -> {{ to_pascal_case function_name }}Return { {{ function_name }}_with_options({{ to_pascal_case function_name }}Options::default()) } -/// Version of [`{{ function_name }}`] that takes a `{{ to_pascal_case function_name }}Options`. See [`{{ function_name }}`] for how to use.{{#if feature}} +/// Version of [`fn@crate::{{ function_name }}`] that takes a `{{ to_pascal_case function_name }}Options`. See [`fn@crate::{{ function_name }}`] for how to use.{{#if feature}} // #[doc(cfg(feature = "{{feature}}"))]{{/if}} pub fn {{ function_name }}_with_options(options: {{ to_pascal_case function_name }}Options) -> {{ to_pascal_case function_name }}Return { {{ to_pascal_case function_name }}Return {} } -/// Options for [`{{ function_name }}_with_options`].{{#if feature}} +/// Options for [`fn@crate::{{ function_name }}_with_options`].{{#if feature}} // #[doc(cfg(feature = "{{feature}}"))]{{/if}} #[derive(DefaultBuilder)] pub struct {{ to_pascal_case function_name }}Options {} @@ -45,6 +49,6 @@ impl Default for {{ to_pascal_case function_name }}Options { } } -/// Return type of [`{{ function_name }}`].{{#if feature}} +/// Return type of [`fn@crate::{{ function_name }}`].{{#if feature}} // #[doc(cfg(feature = "{{feature}}"))]{{/if}} pub struct {{ to_pascal_case function_name }}Return {} \ No newline at end of file