Merge branch 'main' into mouse-coord-type

This commit is contained in:
Marc-Stefan Cassola 2024-08-13 13:50:16 +01:00 committed by GitHub
commit 2a1f532b0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 111 additions and 100 deletions

View file

@ -3,11 +3,15 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased] -
### Removed 🗑 ### 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)
- Removed `UseMouseEventExtractorDefault`
## [0.11.4] - 2024-08-12 ## [0.11.4] - 2024-08-12

View file

@ -1,6 +1,6 @@
[package] [package]
name = "leptos-use" name = "leptos-use"
version = "0.11.4" version = "0.11.5"
edition = "2021" edition = "2021"
authors = ["Marc-Stefan Cassola"] authors = ["Marc-Stefan Cassola"]
categories = ["gui", "web-programming"] categories = ["gui", "web-programming"]
@ -33,11 +33,12 @@ leptos-spin = { version = "0.1", optional = true }
num = { version = "0.4", optional = true } num = { version = "0.4", optional = true }
paste = "1" paste = "1"
thiserror = "1" thiserror = "1"
wasm-bindgen = "0.2.92" unic-langid = "0.9"
wasm-bindgen = ">=0.2.93"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
[dependencies.web-sys] [dependencies.web-sys]
version = "0.3" version = ">=0.3.70"
features = [ features = [
"AddEventListenerOptions", "AddEventListenerOptions",
"BinaryType", "BinaryType",
@ -129,11 +130,12 @@ features = [
] ]
[dev-dependencies] [dev-dependencies]
codee = { version = "0.1", features = ["json_serde", "msgpack_serde", "base64", "prost"] }
getrandom = { version = "0.2", features = ["js"] } getrandom = { version = "0.2", features = ["js"] }
leptos_meta = "0.6" leptos_meta = "0.6"
rand = "0.8" rand = "0.8"
codee = { version = "0.1", features = ["json_serde", "msgpack_serde", "base64", "prost"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
unic-langid = { version = "0.9", features = ["macros"] }
[features] [features]
actix = ["dep:actix-web", "dep:leptos_actix", "dep:http0_2"] actix = ["dep:actix-web", "dep:leptos_actix", "dep:http0_2"]

View file

@ -1,2 +0,0 @@
[build]
rustflags = ["--cfg=web_sys_unstable_apis"]

View file

@ -9,6 +9,7 @@ console_error_panic_hook = "0.1"
console_log = "1" console_log = "1"
log = "0.4" log = "0.4"
leptos-use = { path = "../..", features = ["docs"] } leptos-use = { path = "../..", features = ["docs"] }
unic-langid = { version = "0.9", features = ["macros"] }
web-sys = "0.3" web-sys = "0.3"
[dev-dependencies] [dev-dependencies]

View file

@ -1,13 +1,14 @@
use leptos::*; use leptos::*;
use leptos_use::docs::demo_or_body; use leptos_use::docs::demo_or_body;
use leptos_use::use_locale; use leptos_use::use_locale;
use unic_langid::langid_slice;
#[component] #[component]
fn Demo() -> impl IntoView { fn Demo() -> impl IntoView {
let locale = use_locale(["en", "de", "fr"]); let locale = use_locale(langid_slice!["en", "de", "fr"]);
view! { view! {
<p>Locale: <code class="font-bold">{locale}</code></p> <p>Locale: <code class="font-bold">{move || locale.get().to_string()}</code></p>
} }
} }

View file

@ -15,11 +15,6 @@ pub mod utils;
// #[cfg(web_sys_unstable_apis)] // #[cfg(web_sys_unstable_apis)]
// pub use use_webtransport::*; // pub use use_webtransport::*;
#[cfg(web_sys_unstable_apis)]
mod use_clipboard;
#[cfg(web_sys_unstable_apis)]
pub use use_clipboard::*;
mod is_err; mod is_err;
mod is_none; mod is_none;
mod is_ok; mod is_ok;
@ -31,6 +26,7 @@ mod sync_signal;
mod use_active_element; mod use_active_element;
mod use_breakpoints; mod use_breakpoints;
mod use_broadcast_channel; mod use_broadcast_channel;
mod use_clipboard;
mod use_color_mode; mod use_color_mode;
mod use_cookie; mod use_cookie;
mod use_css_var; mod use_css_var;
@ -99,6 +95,7 @@ pub use sync_signal::*;
pub use use_active_element::*; pub use use_active_element::*;
pub use use_breakpoints::*; pub use use_breakpoints::*;
pub use use_broadcast_channel::*; pub use use_broadcast_channel::*;
pub use use_clipboard::*;
pub use use_color_mode::*; pub use use_color_mode::*;
pub use use_cookie::*; pub use use_cookie::*;
pub use use_css_var::*; pub use use_css_var::*;

View file

@ -215,8 +215,8 @@ where
queue_microtask(move || { queue_microtask(move || {
// TODO : better to use a BroadcastChannel (use_broadcast_channel)? // TODO : better to use a BroadcastChannel (use_broadcast_channel)?
// Note: we cannot construct a full StorageEvent so we _must_ rely on a custom event // Note: we cannot construct a full StorageEvent so we _must_ rely on a custom event
let mut custom = web_sys::CustomEventInit::new(); let custom = web_sys::CustomEventInit::new();
custom.detail(&JsValue::from_str(&key)); custom.set_detail(&JsValue::from_str(&key));
let result = window() let result = window()
.dispatch_event( .dispatch_event(
&web_sys::CustomEvent::new_with_event_init_dict( &web_sys::CustomEvent::new_with_event_init_dict(

View file

@ -10,9 +10,6 @@ use leptos::*;
/// [Permissions API](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API). /// [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. /// 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 /// ## Demo
/// ///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_clipboard) /// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_clipboard)
@ -79,10 +76,9 @@ pub fn use_clipboard_with_options(
let update_text = move |_| { let update_text = move |_| {
if is_supported.get() { if is_supported.get() {
spawn_local(async move { spawn_local(async move {
if let Some(clipboard) = window().navigator().clipboard() { let clipboard = window().navigator().clipboard();
if let Ok(text) = js_fut!(clipboard.read_text()).await { if let Ok(text) = js_fut!(clipboard.read_text()).await {
set_text.set(text.as_string()); set_text.set(text.as_string());
}
} }
}) })
} }
@ -102,12 +98,11 @@ pub fn use_clipboard_with_options(
let value = value.to_owned(); let value = value.to_owned();
spawn_local(async move { spawn_local(async move {
if let Some(clipboard) = window().navigator().clipboard() { let clipboard = window().navigator().clipboard();
if js_fut!(clipboard.write_text(&value)).await.is_ok() { if js_fut!(clipboard.write_text(&value)).await.is_ok() {
set_text.set(Some(value)); set_text.set(Some(value));
set_copied.set(true); set_copied.set(true);
start(()); start(());
}
} }
}); });
} }

View file

@ -131,9 +131,9 @@ async fn create_media(audio: bool) -> Result<web_sys::MediaStream, JsValue> {
.ok_or_else(|| JsValue::from_str("Failed to access window.navigator")) .ok_or_else(|| JsValue::from_str("Failed to access window.navigator"))
.and_then(|n| n.media_devices())?; .and_then(|n| n.media_devices())?;
let mut constraints = web_sys::DisplayMediaStreamConstraints::new(); let constraints = web_sys::DisplayMediaStreamConstraints::new();
if audio { if audio {
constraints.audio(&JsValue::from(true)); constraints.set_audio(&JsValue::from(true));
} }
let promise = media.get_display_media_with_constraints(&constraints)?; let promise = media.get_display_media_with_constraints(&constraints)?;

View file

@ -235,11 +235,11 @@ impl UseEventListenerOptions {
passive, passive,
} = self; } = self;
let mut options = web_sys::AddEventListenerOptions::new(); let options = web_sys::AddEventListenerOptions::new();
options.capture(*capture); options.set_capture(*capture);
options.once(*once); options.set_once(*once);
if let Some(passive) = passive { if let Some(passive) = passive {
options.passive(*passive); options.set_passive(*passive);
} }
options options

View file

@ -183,8 +183,8 @@ where
return; return;
} }
let mut event_src_opts = web_sys::EventSourceInit::new(); let event_src_opts = web_sys::EventSourceInit::new();
event_src_opts.with_credentials(with_credentials); event_src_opts.set_with_credentials(with_credentials);
let es = web_sys::EventSource::new_with_event_source_init_dict(&url, &event_src_opts) let es = web_sys::EventSource::new_with_event_source_init_dict(&url, &event_src_opts)
.unwrap_throw(); .unwrap_throw();

View file

@ -186,10 +186,10 @@ impl UseGeolocationOptions {
.. ..
} = self; } = self;
let mut options = web_sys::PositionOptions::new(); let options = web_sys::PositionOptions::new();
options.enable_high_accuracy(*enable_high_accuracy); options.set_enable_high_accuracy(*enable_high_accuracy);
options.maximum_age(*maximum_age); options.set_maximum_age(*maximum_age);
options.timeout(*timeout); options.set_timeout(*timeout);
options options
} }

View file

@ -154,8 +154,9 @@ where
return; return;
} }
let mut options = web_sys::IntersectionObserverInit::new(); let options = web_sys::IntersectionObserverInit::new();
options.root_margin(&root_margin).threshold( options.set_root_margin(&root_margin);
options.set_threshold(
&thresholds &thresholds
.iter() .iter()
.copied() .copied()
@ -165,7 +166,7 @@ where
if let Some(Some(root)) = root { if let Some(Some(root)) = root {
let root: web_sys::Element = root.clone().into(); 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( let obs = web_sys::IntersectionObserver::new_with_options(

View file

@ -1,14 +1,16 @@
use crate::{use_locales_with_options, UseLocalesOptions}; use crate::{use_locales_with_options, UseLocalesOptions};
use leptos::*; use leptos::*;
use unic_langid::LanguageIdentifier;
/// Reactive locale matching. /// Reactive locale matching.
/// ///
/// Returns the first matching locale given by [`fn@crate::use_locales`] that is also found in /// 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 /// the `supported` list. In case there is no match, then the first locale in `supported` will be
/// returned. If `supported` is empty, the empty string is returned. /// returned.
/// ///
/// Matching is done by checking if an accepted locale from `use_locales` starts with a supported /// > If `supported` is empty, this function will panic!
/// locale. If a match is found the locale from the `supported` list is returned. ///
/// Matching is done by using the [`fn@unic_langid::LanguageIdentifier::matches`] method.
/// ///
/// ## Demo /// ## Demo
/// ///
@ -19,10 +21,11 @@ use leptos::*;
/// ``` /// ```
/// # use leptos::*; /// # use leptos::*;
/// # use leptos_use::use_locale; /// # use leptos_use::use_locale;
/// use unic_langid::langid_slice;
/// # /// #
/// # #[component] /// # #[component]
/// # fn Demo() -> impl IntoView { /// # fn Demo() -> impl IntoView {
/// let locale = use_locale(["en", "de", "fr"]); /// let locale = use_locale(langid_slice!["en", "de", "fr"]);
/// # /// #
/// # view! { } /// # view! { }
/// # } /// # }
@ -31,45 +34,55 @@ use leptos::*;
/// ## Server-Side Rendering /// ## Server-Side Rendering
/// ///
/// See [`fn@crate::use_locales`] /// See [`fn@crate::use_locales`]
pub fn use_locale<S>(supported: S) -> Signal<String> pub fn use_locale<S>(supported: S) -> Signal<LanguageIdentifier>
where where
S: IntoIterator, S: IntoIterator,
S::Item: Into<String> + Clone + 'static, S::Item: AsRef<LanguageIdentifier>,
{ {
use_locale_with_options(supported, UseLocaleOptions::default()) 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. /// 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<S>(supported: S, options: UseLocaleOptions) -> Signal<String> pub fn use_locale_with_options<S>(
supported: S,
options: UseLocaleOptions,
) -> Signal<LanguageIdentifier>
where where
S: IntoIterator, S: IntoIterator,
S::Item: Into<String> + Clone + 'static, S::Item: AsRef<LanguageIdentifier>,
{ {
let locales = use_locales_with_options(options); let client_locales = use_locales_with_options(options);
let supported = supported.into_iter().collect::<Vec<_>>(); let supported = supported
.into_iter()
.map(|l| l.as_ref().clone())
.collect::<Vec<_>>();
const EMPTY_ERR_MSG: &'static str = "Empty supported list. You have to provide at least one locale in the `supported` parameter";
assert!(supported.len() > 0, "{}", EMPTY_ERR_MSG);
Signal::derive(move || { Signal::derive(move || {
let supported = supported.clone(); let supported = supported.clone();
locales.with(|locales| { client_locales.with(|clienht_locales| {
let mut first_supported = None; let mut first_supported = None;
for s in supported { for s in supported {
let s = s.into();
if first_supported.is_none() { if first_supported.is_none() {
first_supported = Some(s.clone()); first_supported = Some(s.clone());
} }
for locale in locales { for client_locale in clienht_locales {
if locale.starts_with(&s) { 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; return s;
} }
} }
} }
first_supported.unwrap_or_else(|| "".to_string()) unreachable!("{}", EMPTY_ERR_MSG);
}) })
}) })
} }

View file

@ -214,20 +214,20 @@ impl From<UseMutationObserverOptions> for web_sys::MutationObserverInit {
character_data_old_value, character_data_old_value,
} = val; } = val;
let mut init = Self::new(); let init = Self::new();
init.subtree(subtree) init.set_subtree(subtree);
.child_list(child_list) init.set_child_list(child_list);
.attributes(attributes) init.set_attributes(attributes);
.attribute_old_value(attribute_old_value) init.set_attribute_old_value(attribute_old_value);
.character_data_old_value(character_data_old_value); init.set_character_data_old_value(character_data_old_value);
if let Some(attribute_filter) = attribute_filter { if let Some(attribute_filter) = attribute_filter {
let array = js_sys::Array::from_iter(attribute_filter.into_iter().map(JsValue::from)); 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 { if let Some(character_data) = character_data {
init.character_data(character_data); init.set_character_data(character_data);
} }
init init

View file

@ -171,8 +171,8 @@ pub struct UseResizeObserverOptions {
impl From<UseResizeObserverOptions> for web_sys::ResizeObserverOptions { impl From<UseResizeObserverOptions> for web_sys::ResizeObserverOptions {
fn from(val: UseResizeObserverOptions) -> Self { fn from(val: UseResizeObserverOptions) -> Self {
let mut options = web_sys::ResizeObserverOptions::new(); let options = web_sys::ResizeObserverOptions::new();
options.box_( options.set_box(
val.box_ val.box_
.unwrap_or(web_sys::ResizeObserverBoxOptions::ContentBox), .unwrap_or(web_sys::ResizeObserverBoxOptions::ContentBox),
); );

View file

@ -231,14 +231,14 @@ where
if let Some(element) = element { if let Some(element) = element {
let element = element.into(); let element = element.into();
let mut scroll_options = web_sys::ScrollToOptions::new(); let scroll_options = web_sys::ScrollToOptions::new();
scroll_options.behavior(behavior.get_untracked().into()); scroll_options.set_behavior(behavior.get_untracked().into());
if let Some(x) = x { if let Some(x) = x {
scroll_options.left(x); scroll_options.set_left(x);
} }
if let Some(y) = y { if let Some(y) = y {
scroll_options.top(y); scroll_options.set_top(y);
} }
element.scroll_to_with_scroll_to_options(&scroll_options); element.scroll_to_with_scroll_to_options(&scroll_options);

View file

@ -136,12 +136,12 @@ async fn create_media(video: bool, audio: bool) -> Result<web_sys::MediaStream,
.ok_or_else(|| JsValue::from_str("Failed to access window.navigator")) .ok_or_else(|| JsValue::from_str("Failed to access window.navigator"))
.and_then(|n| n.media_devices())?; .and_then(|n| n.media_devices())?;
let mut constraints = web_sys::MediaStreamConstraints::new(); let constraints = web_sys::MediaStreamConstraints::new();
if video { if video {
constraints.video(&JsValue::from(true)); constraints.set_video(&JsValue::from(true));
} }
if audio { if audio {
constraints.audio(&JsValue::from(true)); constraints.set_audio(&JsValue::from(true));
} }
let promise = media.get_user_media_with_constraints(&constraints)?; let promise = media.get_user_media_with_constraints(&constraints)?;

View file

@ -318,32 +318,31 @@ impl Default for UseWebNotificationOptions {
impl From<&UseWebNotificationOptions> for web_sys::NotificationOptions { impl From<&UseWebNotificationOptions> for web_sys::NotificationOptions {
fn from(options: &UseWebNotificationOptions) -> Self { fn from(options: &UseWebNotificationOptions) -> Self {
let mut web_sys_options = Self::new(); let web_sys_options = Self::new();
web_sys_options web_sys_options.set_dir(options.direction.into());
.dir(options.direction.into()) web_sys_options.set_require_interaction(options.require_interaction);
.require_interaction(options.require_interaction) web_sys_options.set_renotify(options.renotify);
.renotify(options.renotify) web_sys_options.set_silent(options.silent);
.silent(options.silent);
if let Some(body) = &options.body { if let Some(body) = &options.body {
web_sys_options.body(body); web_sys_options.set_body(body);
} }
if let Some(icon) = &options.icon { if let Some(icon) = &options.icon {
web_sys_options.icon(icon); web_sys_options.set_icon(icon);
} }
if let Some(image) = &options.image { if let Some(image) = &options.image {
web_sys_options.image(image); web_sys_options.set_image(image);
} }
if let Some(language) = &options.language { if let Some(language) = &options.language {
web_sys_options.lang(language); web_sys_options.set_lang(language);
} }
if let Some(tag) = &options.tag { if let Some(tag) = &options.tag {
web_sys_options.tag(tag); web_sys_options.set_tag(tag);
} }
web_sys_options web_sys_options
@ -415,39 +414,39 @@ pub struct ShowOptions {
impl ShowOptions { impl ShowOptions {
fn override_notification_options(&self, options: &mut web_sys::NotificationOptions) { fn override_notification_options(&self, options: &mut web_sys::NotificationOptions) {
if let Some(direction) = self.direction { if let Some(direction) = self.direction {
options.dir(direction.into()); options.set_dir(direction.into());
} }
if let Some(require_interaction) = self.require_interaction { 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 { if let Some(body) = &self.body {
options.body(body); options.set_body(body);
} }
if let Some(icon) = &self.icon { if let Some(icon) = &self.icon {
options.icon(icon); options.set_icon(icon);
} }
if let Some(image) = &self.image { if let Some(image) = &self.image {
options.image(image); options.set_image(image);
} }
if let Some(language) = &self.language { if let Some(language) = &self.language {
options.lang(language); options.set_lang(language);
} }
if let Some(tag) = &self.tag { if let Some(tag) = &self.tag {
options.tag(tag); options.set_tag(tag);
} }
if let Some(renotify) = self.renotify { if let Some(renotify) = self.renotify {
options.renotify(renotify); options.set_renotify(renotify);
} }
if let Some(silent) = self.silent { if let Some(silent) = self.silent {
options.silent(Some(silent)); options.set_silent(Some(silent));
} }
} }
} }
@ -481,7 +480,7 @@ impl From<web_sys::NotificationPermission> for NotificationPermission {
web_sys::NotificationPermission::Default => Self::Default, web_sys::NotificationPermission::Default => Self::Default,
web_sys::NotificationPermission::Granted => Self::Granted, web_sys::NotificationPermission::Granted => Self::Granted,
web_sys::NotificationPermission::Denied => Self::Denied, web_sys::NotificationPermission::Denied => Self::Denied,
web_sys::NotificationPermission::__Nonexhaustive => Self::Default, _ => Self::Default,
} }
} }
} }