diff --git a/.idea/leptos-use.iml b/.idea/leptos-use.iml
index 24a8946..8904b5c 100644
--- a/.idea/leptos-use.iml
+++ b/.idea/leptos-use.iml
@@ -43,6 +43,8 @@
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e6a98bf..e2feaa7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,13 @@
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_websocket` (thanks @sectore)
+- `use_intl_number_format`
+
## [0.4.1] - 2023-07-07
### New Functions π
diff --git a/Cargo.toml b/Cargo.toml
index f6db118..aaa5bcd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,7 +16,7 @@ homepage = "https://leptos-use.rs"
leptos = "0.4"
wasm-bindgen = "0.2"
js-sys = "0.3"
-default-struct-builder = "0.4"
+default-struct-builder = { path = "../default-struct-builder" }
num = { version = "0.4", optional = true }
serde = { version = "1", optional = true }
serde_json = { version = "1", optional = true }
diff --git a/README.md b/README.md
index 43d2f36..b1fd6b9 100644
--- a/README.md
+++ b/README.md
@@ -89,4 +89,4 @@ To scaffold a new function quickly you can run `template/createfn.sh`. It requir
| Crate version | Compatible Leptos version |
|---------------|---------------------------|
| <= 0.3 | 0.3 |
-| \>= 0.4 | 0.4 |
+| 0.4 | 0.4 |
diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md
index 8e38c3d..b24df4c 100644
--- a/docs/book/src/SUMMARY.md
+++ b/docs/book/src/SUMMARY.md
@@ -65,6 +65,10 @@
- [use_supported](utilities/use_supported.md)
- [use_throttle_fn](utilities/use_throttle_fn.md)
+# Intl
+
+- [use_intl_number_format](intl/use_intl_number_format.md)
+
# @Math
- [use_abs](math/use_abs.md)
diff --git a/docs/book/src/functions.md b/docs/book/src/functions.md
index 4e484ee..7d0f314 100644
--- a/docs/book/src/functions.md
+++ b/docs/book/src/functions.md
@@ -6,6 +6,8 @@
+
+
diff --git a/docs/book/src/intl/use_intl_number_format.md b/docs/book/src/intl/use_intl_number_format.md
new file mode 100644
index 0000000..3a87258
--- /dev/null
+++ b/docs/book/src/intl/use_intl_number_format.md
@@ -0,0 +1,3 @@
+# use_intl_number_format
+
+
diff --git a/examples/Cargo.toml b/examples/Cargo.toml
index 3374a1a..293966b 100644
--- a/examples/Cargo.toml
+++ b/examples/Cargo.toml
@@ -16,6 +16,7 @@ members = [
"use_element_size",
"use_element_visibility",
"use_event_listener",
+ "use_intl_number_format",
"use_favicon",
"use_floor",
"use_intersection_observer",
diff --git a/examples/use_intl_number_format/Cargo.toml b/examples/use_intl_number_format/Cargo.toml
new file mode 100644
index 0000000..007983d
--- /dev/null
+++ b/examples/use_intl_number_format/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "use_intl_number_format"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+leptos = { version = "0.4", features = ["nightly", "csr"] }
+console_error_panic_hook = "0.1"
+console_log = "1"
+log = "0.4"
+leptos-use = { path = "../..", features = ["docs"] }
+web-sys = "0.3"
+
+[dev-dependencies]
+wasm-bindgen = "0.2"
+wasm-bindgen-test = "0.3.0"
diff --git a/examples/use_intl_number_format/README.md b/examples/use_intl_number_format/README.md
new file mode 100644
index 0000000..de302ef
--- /dev/null
+++ b/examples/use_intl_number_format/README.md
@@ -0,0 +1,23 @@
+A simple example for `use_intl_number_format`.
+
+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_intl_number_format/Trunk.toml b/examples/use_intl_number_format/Trunk.toml
new file mode 100644
index 0000000..3e4be08
--- /dev/null
+++ b/examples/use_intl_number_format/Trunk.toml
@@ -0,0 +1,2 @@
+[build]
+public_url = "/demo/"
\ No newline at end of file
diff --git a/examples/use_intl_number_format/index.html b/examples/use_intl_number_format/index.html
new file mode 100644
index 0000000..ae249a6
--- /dev/null
+++ b/examples/use_intl_number_format/index.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/examples/use_intl_number_format/input.css b/examples/use_intl_number_format/input.css
new file mode 100644
index 0000000..bd6213e
--- /dev/null
+++ b/examples/use_intl_number_format/input.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
\ No newline at end of file
diff --git a/examples/use_intl_number_format/rust-toolchain.toml b/examples/use_intl_number_format/rust-toolchain.toml
new file mode 100644
index 0000000..271800c
--- /dev/null
+++ b/examples/use_intl_number_format/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "nightly"
\ No newline at end of file
diff --git a/examples/use_intl_number_format/src/main.rs b/examples/use_intl_number_format/src/main.rs
new file mode 100644
index 0000000..2e97d7c
--- /dev/null
+++ b/examples/use_intl_number_format/src/main.rs
@@ -0,0 +1,56 @@
+use leptos::*;
+use leptos_use::docs::demo_or_body;
+use leptos_use::{use_intl_number_format, NumberStyle, UseIntlNumberFormatOptions};
+
+#[component]
+fn Demo(cx: Scope) -> impl IntoView {
+ let (number, set_number) = create_signal(cx, 123456.78);
+
+ let de_nf = use_intl_number_format(
+ UseIntlNumberFormatOptions::default()
+ .locale("de-DE")
+ .style(NumberStyle::Currency)
+ .currency("EUR"),
+ );
+ let de_num = de_nf.format::(cx, number);
+
+ let ja_nf = use_intl_number_format(
+ UseIntlNumberFormatOptions::default()
+ .locale("ja-JP")
+ .style(NumberStyle::Currency)
+ .currency("JPY"),
+ );
+ let ja_num = ja_nf.format::(cx, number);
+
+ let in_nf = use_intl_number_format(
+ UseIntlNumberFormatOptions::default()
+ .locale("en-IN")
+ .maximum_significant_digits(3),
+ );
+ let in_num = in_nf.format::(cx, number);
+
+ view! { cx,
+
+ "Number: " { number }
+ "German currency (EUR): " { de_num }
+ "Japanese currency (JPY): " { ja_num }
+ "Indian 3 max significant digits: " { in_num }
+ }
+}
+
+fn main() {
+ _ = console_log::init_with_level(log::Level::Debug);
+ console_error_panic_hook::set_once();
+
+ mount_to(demo_or_body(), |cx| {
+ view! { cx, }
+ })
+}
diff --git a/examples/use_intl_number_format/style/output.css b/examples/use_intl_number_format/style/output.css
new file mode 100644
index 0000000..ab5191f
--- /dev/null
+++ b/examples/use_intl_number_format/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_intl_number_format/tailwind.config.js b/examples/use_intl_number_format/tailwind.config.js
new file mode 100644
index 0000000..bc09f5e
--- /dev/null
+++ b/examples/use_intl_number_format/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/src/lib.rs b/src/lib.rs
index 23e7d23..1ef038c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -37,6 +37,7 @@ mod use_favicon;
mod use_intersection_observer;
mod use_interval;
mod use_interval_fn;
+mod use_intl_number_format;
mod use_media_query;
mod use_mouse;
mod use_mutation_observer;
@@ -68,6 +69,7 @@ pub use use_favicon::*;
pub use use_intersection_observer::*;
pub use use_interval::*;
pub use use_interval_fn::*;
+pub use use_intl_number_format::*;
pub use use_media_query::*;
pub use use_mouse::*;
pub use use_mutation_observer::*;
diff --git a/src/use_intl_number_format.rs b/src/use_intl_number_format.rs
new file mode 100644
index 0000000..73f4b01
--- /dev/null
+++ b/src/use_intl_number_format.rs
@@ -0,0 +1,922 @@
+use crate::utils::js_value_from_to_string;
+use default_struct_builder::DefaultBuilder;
+use js_sys::Reflect;
+use leptos::*;
+use std::fmt::Display;
+use wasm_bindgen::{JsCast, JsValue};
+
+/// Reactive [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat).
+///
+/// ## Demo
+///
+/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_intl_number_format)
+///
+/// ## Usage
+///
+/// In basic use without specifying a locale, a formatted string in the default locale and with default options is returned.
+///
+/// ```
+/// # use leptos::*;
+/// # use leptos_use::{use_intl_number_format, UseIntlNumberFormatOptions};
+/// #
+/// # #[component]
+/// # fn Demo(cx: Scope) -> impl IntoView {
+/// let (number, set_number) = create_signal(cx, 3500);
+///
+/// let number_format = use_intl_number_format(UseIntlNumberFormatOptions::default());
+///
+/// let formatted = number_format.format::(cx, number); // "3,500" if in US English locale
+/// #
+/// # view! { cx, }
+/// # }
+/// ```
+///
+/// ### Using locales
+///
+/// This example shows some of the variations in localized number formats. In order to get the format
+/// of the language used in the user interface of your application, make sure to specify that language
+/// (and possibly some fallback languages) using the `locales` argument:
+///
+/// ```
+/// # use leptos::*;
+/// # use leptos_use::{use_intl_number_format, UseIntlNumberFormatOptions};
+/// #
+/// # #[component]
+/// # fn Demo(cx: Scope) -> impl IntoView {
+/// let number = 123456.789_f32;
+///
+/// // German uses comma as decimal separator and period for thousands
+/// let number_format = use_intl_number_format(
+/// UseIntlNumberFormatOptions::default().locale("de-DE"),
+/// );
+/// let formatted = number_format.format(cx, number); // 123.456,789
+///
+/// // Arabic in most Arabic speaking countries uses real Arabic digits
+/// let number_format = use_intl_number_format(
+/// UseIntlNumberFormatOptions::default().locale("ar-EG"),
+/// );
+/// let formatted = number_format.format(cx, number); // Ω‘Ω’Ω£Ω€Ω₯Ω¦Ω«Ω§Ω¨Ω©
+///
+/// // India uses thousands/lakh/crore separators
+/// let number_format = use_intl_number_format(
+/// UseIntlNumberFormatOptions::default().locale("en-IN"),
+/// );
+/// let formatted = number_format.format(cx, number); // 1,23,456.789
+///
+/// // the nu extension key requests a numbering system, e.g. Chinese decimal
+/// let number_format = use_intl_number_format(
+/// UseIntlNumberFormatOptions::default().locale("zh-Hans-CN-u-nu-hanidec"),
+/// );
+/// let formatted = number_format.format(cx, number); // δΈδΊδΈ,εδΊε
.δΈε
«δΉ
+///
+/// // when requesting a language that may not be supported, such as
+/// // Balinese, include a fallback language, in this case Indonesian
+/// let number_format = use_intl_number_format(
+/// UseIntlNumberFormatOptions::default().locales(vec!["ban".to_string(), "id".to_string()]),
+/// );
+/// let formatted = number_format.format(cx, number); // 123.456,789
+///
+/// #
+/// # view! { cx, }
+/// # }
+/// ```
+///
+/// ### Using options
+///
+/// The results can be customized in multiple ways.
+///
+/// ```
+/// # use leptos::*;
+/// # use leptos_use::{NumberStyle, UnitDisplay, use_intl_number_format, UseIntlNumberFormatOptions};
+/// #
+/// # #[component]
+/// # fn Demo(cx: Scope) -> impl IntoView {
+/// let number = 123456.789_f64;
+///
+/// // request a currency format
+/// let number_format = use_intl_number_format(
+/// UseIntlNumberFormatOptions::default()
+/// .locale("de-DE")
+/// .style(NumberStyle::Currency)
+/// .currency("EUR"),
+/// );
+/// let formatted = number_format.format(cx, number); // 123.456,79 β¬
+///
+/// // the Japanese yen doesn't use a minor unit
+/// let number_format = use_intl_number_format(
+/// UseIntlNumberFormatOptions::default()
+/// .locale("ja-JP")
+/// .style(NumberStyle::Currency)
+/// .currency("JPY"),
+/// );
+/// let formatted = number_format.format(cx, number); // οΏ₯123,457
+///
+/// // limit to three significant digits
+/// let number_format = use_intl_number_format(
+/// UseIntlNumberFormatOptions::default()
+/// .locale("en-IN")
+/// .maximum_significant_digits(3),
+/// );
+/// let formatted = number_format.format(cx, number); // 1,23,000
+///
+/// // Formatting with units
+/// let number_format = use_intl_number_format(
+/// UseIntlNumberFormatOptions::default()
+/// .locale("pt-PT")
+/// .style(NumberStyle::Unit)
+/// .unit("kilometer-per-hour"),
+/// );
+/// let formatted = number_format.format(cx, 50); // 50 km/h
+///
+/// let number_format = use_intl_number_format(
+/// UseIntlNumberFormatOptions::default()
+/// .locale("en-GB")
+/// .style(NumberStyle::Unit)
+/// .unit("liter")
+/// .unit_display(UnitDisplay::Long),
+/// );
+/// let formatted = number_format.format(cx, 16); // 16 litres
+/// #
+/// # view! { cx, }
+/// # }
+/// ```
+///
+/// For an exhaustive list of options see [`UseIntlNumberFormatOptions`](https://docs.rs/leptos_use/latest/leptos_use/struct.UseIntlNumberFormatOptions.html).
+///
+/// ### Formatting ranges
+///
+/// Apart from the `format` method, the `format_range` method can be used to format a range of numbers.
+/// Please see [`UseIntlNumberFormatReturn::format_range`](https://docs.rs/leptos_use/latest/leptos_use/struct.UseIntlNumberFormatReturn.html#method.format_range)
+/// for details.
+pub fn use_intl_number_format(options: UseIntlNumberFormatOptions) -> UseIntlNumberFormatReturn {
+ let number_format = js_sys::Intl::NumberFormat::new(
+ &js_sys::Array::from_iter(options.locales.iter().map(JsValue::from)),
+ &js_sys::Object::from(options),
+ );
+
+ UseIntlNumberFormatReturn {
+ js_intl_number_format: number_format,
+ }
+}
+
+#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum CompactDisplay {
+ #[default]
+ Short,
+ Long,
+}
+
+impl Display for CompactDisplay {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Short => write!(f, "short"),
+ Self::Long => write!(f, "long"),
+ }
+ }
+}
+
+js_value_from_to_string!(CompactDisplay);
+
+/// How to display the currency in currency formatting.
+#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum CurrencyDisplay {
+ /// use a localized currency symbol such as β¬.
+ #[default]
+ Symbol,
+ /// use a narrow format symbol ("$100" rather than "US$100").
+ NarrowSymbol,
+ /// use the ISO currency code.
+ Code,
+ /// use a localized currency name such as "dollar".
+ Name,
+}
+
+impl Display for CurrencyDisplay {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Symbol => write!(f, "symbol"),
+ Self::NarrowSymbol => write!(f, "narrowSymbol"),
+ Self::Code => write!(f, "code"),
+ Self::Name => write!(f, "name"),
+ }
+ }
+}
+
+js_value_from_to_string!(CurrencyDisplay);
+
+#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum CurrencySign {
+ #[default]
+ Standard,
+ Accounting,
+}
+
+impl Display for CurrencySign {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Standard => write!(f, "standard"),
+ Self::Accounting => write!(f, "accounting"),
+ }
+ }
+}
+
+js_value_from_to_string!(CurrencySign);
+
+#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum LocaleMatcher {
+ #[default]
+ BestFit,
+ Lookup,
+}
+
+impl Display for LocaleMatcher {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::BestFit => write!(f, "best fit"),
+ Self::Lookup => write!(f, "lookup"),
+ }
+ }
+}
+
+js_value_from_to_string!(LocaleMatcher);
+
+/// The formatting that should be displayed for the number.
+#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum Notation {
+ /// plain number formatting.
+ #[default]
+ Standard,
+ /// order-of-magnitude for formatted number.
+ Scientific,
+ /// exponent of ten when divisible by three.
+ Engineering,
+ /// string representing exponent
+ Compact,
+}
+
+impl Display for Notation {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Standard => write!(f, "standard"),
+ Self::Scientific => write!(f, "scientific"),
+ Self::Engineering => write!(f, "engineering"),
+ Self::Compact => write!(f, "compact"),
+ }
+ }
+}
+
+js_value_from_to_string!(Notation);
+
+/// When to display the sign for the number.
+#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum SignDisplay {
+ /// sign display for negative numbers only, including negative zero.
+ #[default]
+ Auto,
+ /// always display the sign.
+ Always,
+ /// sign display for positive and negative numbers, but not zero.
+ ExceptZero,
+ /// sign display for negative numbers only, excluding negative zero. Experimental.
+ Negative,
+ /// never display sign.
+ Never,
+}
+
+impl Display for SignDisplay {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Auto => write!(f, "auto"),
+ Self::Always => write!(f, "always"),
+ Self::ExceptZero => write!(f, "exceptZero"),
+ Self::Negative => write!(f, "negative"),
+ Self::Never => write!(f, "never"),
+ }
+ }
+}
+
+js_value_from_to_string!(SignDisplay);
+
+#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum NumberStyle {
+ #[default]
+ Decimal,
+ Currency,
+ Percent,
+ Unit,
+}
+
+impl Display for NumberStyle {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Decimal => write!(f, "decimal"),
+ Self::Currency => write!(f, "currency"),
+ Self::Percent => write!(f, "percent"),
+ Self::Unit => write!(f, "unit"),
+ }
+ }
+}
+
+js_value_from_to_string!(NumberStyle);
+
+#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum UnitDisplay {
+ /// e.g., `16 litres`
+ Long,
+ /// e.g., `16 l`
+ #[default]
+ Short,
+ /// e.g., `16l`
+ Narrow,
+}
+
+impl Display for UnitDisplay {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Long => write!(f, "long"),
+ Self::Short => write!(f, "short"),
+ Self::Narrow => write!(f, "narrow"),
+ }
+ }
+}
+
+js_value_from_to_string!(UnitDisplay);
+
+#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum NumberGrouping {
+ /// display grouping separators even if the locale prefers otherwise.
+ Always,
+ /// display grouping separators based on the locale preference, which may also be dependent on the currency.
+ #[default]
+ Auto,
+ /// do not display grouping separators.
+ None,
+ /// display grouping separators when there are at least 2 digits in a group.
+ Min2,
+}
+
+impl Display for NumberGrouping {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Always => write!(f, "always"),
+ Self::Auto => write!(f, "auto"),
+ Self::None => write!(f, "none"),
+ Self::Min2 => write!(f, "min2"),
+ }
+ }
+}
+
+impl From for JsValue {
+ fn from(value: NumberGrouping) -> Self {
+ match value {
+ NumberGrouping::None => JsValue::from(false),
+ _ => JsValue::from(&value.to_string()),
+ }
+ }
+}
+
+#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum RoundingMode {
+ /// round toward +β. Positive values round up. Negative values round "more positive".
+ Ceil,
+ /// round toward -β. Positive values round down. Negative values round "more negative".
+ Floor,
+ /// round away from 0. The _magnitude_ of the value is always increased by rounding. Positive values round up. Negative values round "more negative".
+ Expand,
+ /// round toward 0. This _magnitude_ of the value is always reduced by rounding. Positive values round down. Negative values round "less negative".
+ Trunc,
+ /// ties toward +β. Values above the half-increment round like `Ceil` (towards +β), and below like `Floor` (towards -β). On the half-increment, values round like `Ceil`.
+ HalfCeil,
+ /// ties toward -β. Values above the half-increment round like `Ceil` (towards +β), and below like `Floor` (towards -β). On the half-increment, values round like `Floor`.
+ HalfFloor,
+ /// ties away from 0. Values above the half-increment round like `Expand` (away from zero), and below like `Trunc` (towards 0). On the half-increment, values round like `Expand`.
+ #[default]
+ HalfExpand,
+ /// ties toward 0. Values above the half-increment round like `Expand` (away from zero), and below like `Trunc` (towards 0). On the half-increment, values round like `Trunc`.
+ HalfTrunc,
+ /// ties towards the nearest even integer. Values above the half-increment round like `Expand` (away from zero), and below like `Trunc` (towards 0). On the half-increment values round towards the nearest even digit.
+ HalfEven,
+}
+
+impl Display for RoundingMode {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Ceil => write!(f, "ceil"),
+ Self::Floor => write!(f, "floor"),
+ Self::Expand => write!(f, "expand"),
+ Self::Trunc => write!(f, "trunc"),
+ Self::HalfCeil => write!(f, "halfCeil"),
+ Self::HalfFloor => write!(f, "halfFloor"),
+ Self::HalfExpand => write!(f, "halfExpand"),
+ Self::HalfTrunc => write!(f, "halfTrunc"),
+ Self::HalfEven => write!(f, "halfEven"),
+ }
+ }
+}
+
+js_value_from_to_string!(RoundingMode);
+
+#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum RoundingPriority {
+ /// the result from the significant digits property is used.
+ #[default]
+ Auto,
+ /// the result from the property that results in more precision is used.
+ MorePrecision,
+ /// the result from the property that results in less precision is used.
+ LessPrecision,
+}
+
+impl Display for RoundingPriority {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Auto => write!(f, "auto"),
+ Self::MorePrecision => write!(f, "morePrecision"),
+ Self::LessPrecision => write!(f, "lessPrecision"),
+ }
+ }
+}
+
+js_value_from_to_string!(RoundingPriority);
+
+#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum TrailingZeroDisplay {
+ /// keep trailing zeros according to `minimum_fraction_digits` and `minimum_significant_digits`.
+ #[default]
+ Auto,
+ /// remove the fraction digits _if_ they are all zero. This is the same as `Auto` if any of the fraction digits is non-zero.
+ StripIfInteger,
+}
+
+impl Display for TrailingZeroDisplay {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Auto => write!(f, "auto"),
+ Self::StripIfInteger => write!(f, "stripIfInteger"),
+ }
+ }
+}
+
+js_value_from_to_string!(TrailingZeroDisplay);
+
+/// Options for [`use_intl_number_format`].
+#[derive(DefaultBuilder)]
+pub struct UseIntlNumberFormatOptions {
+ /// A vec of strings, each with a BCP 47 language tag. Please refer to the
+ /// [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#parameters)
+ /// for more info.
+ locales: Vec,
+
+ /// Only used when [`UseIntlNumberFormatOptions::notation`] is `Compact`. Takes either `Short` (default) or `Long`.
+ compact_display: CompactDisplay,
+
+ /// The currency to use in currency formatting.
+ /// Possible values are the ISO 4217 currency codes, such as "USD" for the US dollar, "EUR" for the euro,
+ /// or "CNY" for the Chinese RMB β see the [Current currency & funds code list](https://www.six-group.com/en/products-services/financial-information/data-standards.html#scrollTo=currency-codes.
+ /// There is no default value; if the style is `Currency`, the currency property must be provided.
+ #[builder(into)]
+ currency: Option,
+
+ /// How to display the currency in currency formatting. The default is `Symbol`.
+ ///
+ /// - `Symbol`: use a localized currency symbol such as β¬.
+ /// - `NarrowSymbol`: use a narrow format symbol ("$100" rather than "US$100").
+ /// - `Code`: use the ISO currency code.
+ /// - `Name`: use a localized currency name such as `"dollar"`.
+ currency_display: CurrencyDisplay,
+
+ /// In many locales, accounting format means to wrap the number with parentheses instead of appending a minus sign.
+ /// You can enable this formatting by setting this option to `Accounting`. The default value is `Standard`.
+ currency_sign: CurrencySign,
+
+ /// The locale matching algorithm to use. Possible values are `Lookup` and `BestFit`; the default is `"BestFit"`.
+ /// For information about this option, see the [Intl page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_identification_and_negotiation).
+ locale_matcher: LocaleMatcher,
+
+ /// The formatting that should be displayed for the number. The default is `"standard"`.
+ ///
+ /// - `Standard`: plain number formatting.
+ /// - `Scientific`: order-of-magnitude for formatted number.
+ /// - `Engineering`: exponent of ten when divisible by three.
+ /// - `Compact`: string representing exponent; See [`UseIntlNumberFormatOptions::compact_display`].
+ notation: Notation,
+
+ /// Numbering System. Possible values include: `"arab"`, `"arabext"`, `"bali"`, `"beng"`, `"deva"`, `"fullwide"`, `"gujr"`, `"guru"`, `"hanidec"`, `"khmr"`, `"knda"`, `"laoo"`, `"latn"`, `"limb"`, `"mlym"`, `"mong"`, `"mymr"`, `"orya"`, `"tamldec"`, `"telu"`, `"thai"`, `"tibt"`.
+ #[builder(into)]
+ numbering_system: Option,
+
+ /// When to display the sign for the number. The default is `Auto`.
+ ///
+ /// - `Auto`: sign display for negative numbers only, including negative zero.
+ /// - `Always`: always display sign.
+ /// - `ExceptZero`: sign display for positive and negative numbers, but not zero.
+ /// - `Negative`: sign display for negative numbers only, excluding negative zero. Experimental
+ /// - `Never`: never display sign.
+ sign_display: SignDisplay,
+
+ /// The formatting style to use. The default is `Decimal`.
+ ///
+ /// - `Decimal` for plain number formatting.
+ /// - `Currency` for currency formatting.
+ /// - `Percent` for percent formatting.
+ /// - `Unit` for unit formatting.
+ style: NumberStyle,
+
+ /// The unit to use in `unit` formatting, Possible values are core unit identifiers,
+ /// defined in [UTS #35, Part 2, Section 6](https://unicode.org/reports/tr35/tr35-general.html#Unit_Elements).
+ /// A [subset](https://tc39.es/ecma402/#table-sanctioned-single-unit-identifiers) of units
+ /// from the [full list](https://github.com/unicode-org/cldr/blob/main/common/validity/unit.xml)
+ /// was selected for use in ECMAScript.
+ /// Pairs of simple units can be concatenated with "-per-" to make a compound unit.
+ /// There is no default value; if the `style` is `Unit`, the `unit` property must be provided.
+ #[builder(into)]
+ unit: Option,
+
+ /// The unit formatting style to use in `unit` formatting. The default is `Short`.
+ ///
+ /// - `Long` (e.g., `16 litres`).
+ /// - `Short` (e.g., `16 l`).
+ /// - `Narrow` (e.g., `16l`).
+ unit_display: UnitDisplay,
+
+ /// Experimental.
+ /// Whether to use grouping separators, such as thousands separators or thousand/lakh/crore separators.
+ /// The default is `Auto`.
+ ///
+ /// - `Always`: display grouping separators even if the locale prefers otherwise.
+ /// - `Auto`: display grouping separators based on the locale preference, which may also be dependent on the currency.
+ /// - `None`: do not display grouping separators.
+ /// - `Min2`: display grouping separators when there are at least 2 digits in a group.
+ use_grouping: NumberGrouping,
+
+ /// Experimental.
+ /// Options for rounding modes. The default is `HalfExpand`.
+ ///
+ /// - `Ceil`: round toward +β. Positive values round up. Negative values round "more positive".
+ /// - `Floor` round toward -β. Positive values round down. Negative values round "more negative".
+ /// - `Expand`: round away from 0. The _magnitude_ of the value is always increased by rounding. Positive values round up. Negative values round "more negative".
+ /// - `Trunc`: round toward 0. This _magnitude_ of the value is always reduced by rounding. Positive values round down. Negative values round "less negative".
+ /// - `HalfCeil`: ties toward +β. Values above the half-increment round like `Ceil` (towards +β), and below like `Floor` (towards -β). On the half-increment, values round like `Ceil`.
+ /// - `HalfFloor`: ties toward -β. Values above the half-increment round like `Ceil` (towards +β), and below like `Floor` (towards -β). On the half-increment, values round like `Floor`.
+ /// - `HalfExpand`: ties away from 0. Values above the half-increment round like `Expand` (away from zero), and below like `Trunc` (towards 0). On the half-increment, values round like `Expand`.
+ /// - `HalfTrunc`: ties toward 0. Values above the half-increment round like `Expand` (away from zero), and below like `Trunc` (towards 0). On the half-increment, values round like `Trunc`.
+ /// - `HalfEven`: ties towards the nearest even integer. Values above the half-increment round like `Expand` (away from zero), and below like `Trunc` (towards 0). On the half-increment values round towards the nearest even digit.
+ ///
+ /// These options reflect the [ICU user guide](https://unicode-org.github.io/icu/userguide/format_parse/numbers/rounding-modes.html), where `Expand` and `Trunc` map to ICU "UP" and "DOWN", respectively. The [rounding modes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#rounding_modes) example demonstrates how each mode works.
+ rounding_mode: RoundingMode,
+
+ /// Experimental.
+ /// Specify how rounding conflicts will be resolved if both "FractionDigits" ([`UseIntlNumberFormatOptions::minimum_fraction_digits`]/[`UseIntlNumberFormatOptions::maximum_fraction_digits`]) and "SignificantDigits" ([`UseIntlNumberFormatOptions::minimum_significant_digits`]/[`UseIntlNumberFormatOptions::maximum_significant_digits`]) are specified:
+ ///
+ /// - `Auto`: the result from the significant digits property is used (default).
+ /// - `MorePrecision`: the result from the property that results in more precision is used.
+ /// - `LessPrecision`: the result from the property that results in less precision is used.
+ ///
+ /// Note that for values other than `Auto` the result with more precision is calculated from the [`UseIntlNumberFormatOptions::maximum_significant_digits`] and [`UseIntlNumberFormatOptions::maximum_fraction_digits`] (minimum fractional and significant digit settings are ignored).
+ rounding_priority: RoundingPriority,
+
+ /// Experimental.
+ /// Specifies the rounding-increment precision. Must be one of the following integers:
+ /// `1` (default), `2`, `5`, `10`, `20`, `25`, `50`, `100`, `200`, `250`, `500`, `1000`, `2000`, `2500`, `5000`.
+ ///
+ /// This option controls the rounding increment to be used when formatting numbers:
+ ///
+ /// - It indicates the increment at which rounding should take place relative to the calculated rounding magnitude.
+ /// - It cannot be mixed with significant-digits rounding or any setting of `rounding_priority` other than `Auto`.
+ ///
+ /// For example, if `maximum_fraction_digits` is 2 and `rounding_increment` is 5, then the number is rounded to the nearest 0.05 ("nickel rounding").
+ ///
+ /// ```
+ /// # use leptos::*;
+ /// # use leptos_use::{use_intl_number_format, UseIntlNumberFormatOptions, NumberStyle};
+ /// #
+ /// # #[component]
+ /// # fn Demo(cx: Scope) -> impl IntoView {
+ /// let nf = use_intl_number_format(
+ /// UseIntlNumberFormatOptions::default()
+ /// .style(NumberStyle::Currency)
+ /// .currency("USD")
+ /// .maximum_fraction_digits(2)
+ /// .rounding_increment(5),
+ /// );
+ ///
+ /// let formatted = nf.format(cx, 11.29); // "$11.30"
+ /// let formatted = nf.format(cx, 11.25); // "$11.25"
+ /// let formatted = nf.format(cx, 11.22); // "$11.20"
+ /// #
+ /// # view! { cx, }
+ /// # }
+ /// ```
+ ///
+ /// If you set `minimum_fraction_digits` and `maximum_fraction_digits`, they must set them to the same value; otherwise a `RangeError` is thrown.
+ rounding_increment: u16,
+
+ /// Experimental.
+ /// A string expressing the strategy for displaying trailing zeros on whole numbers. The default is `"auto"`.
+ ///
+ /// - `Auto`: keep trailing zeros according to `minimum_fraction_digits` and `minimum_significant_digits`.
+ /// - `StripIfInteger`: remove the fraction digits _if_ they are all zero. This is the same as `Auto` if any of the fraction digits is non-zero.
+ trailing_zero_display: TrailingZeroDisplay,
+
+ /// These properties fall into two groups: `minimum_integer_digits`, `minimum_fraction_digits`,
+ /// and `maximum_fraction_digits` in one group, `minimum_significant_digits` and `maximum_significant_digits`
+ /// in the other. If properties from both groups are specified, conflicts in the resulting
+ /// display format are resolved based on the value of the [`UseIntlNumberFormatOptions::rounding_priority`] property.
+ ///
+ /// The minimum number of integer digits to use. A value with a smaller number of integer digits
+ /// than this number will be left-padded with zeros (to the specified length) when formatted.
+ /// Possible values are from 1 to 21; the default is 1.
+ minimum_integer_digits: u8,
+
+ /// The minimum number of fraction digits to use. Possible values are from 0 to 20;
+ /// the default for plain number and percent formatting is 0;
+ /// the default for currency formatting is the number of minor unit digits provided by the
+ /// [ISO 4217 currency code list](https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/lists/list-one.xml)
+ /// (2 if the list doesn't provide that information).
+ #[builder(into)]
+ minimum_fraction_digits: Option,
+
+ /// The maximum number of fraction digits to use. Possible values are from 0 to 20;
+ /// the default for plain number formatting is the larger of `minimum_fraction_digits` and 3;
+ /// the default for currency formatting is the larger of `minimum_fraction_digits` and
+ /// the number of minor unit digits provided by the
+ /// [ISO 4217 currency code list](https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/lists/list-one.xml)
+ /// (2 if the list doesn't provide that information); the default for percent formatting is
+ /// `minimum_fraction_digits`.
+ #[builder(into)]
+ maximum_fraction_digits: Option,
+
+ /// The minimum number of significant digits to use. Possible values are from 1 to 21; the default is 1.
+ minimum_significant_digits: u8,
+
+ /// The maximum number of significant digits to use. Possible values are from 1 to 21; the default is 21.
+ maximum_significant_digits: u8,
+}
+
+impl UseIntlNumberFormatOptions {
+ /// A string with a BCP 47 language tag. Please refer to the
+ /// [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#parameters)
+ /// for more info.
+ pub fn locale(self, locale: &str) -> Self {
+ Self {
+ locales: vec![locale.to_string()],
+ ..self
+ }
+ }
+}
+
+impl Default for UseIntlNumberFormatOptions {
+ fn default() -> Self {
+ Self {
+ locales: Default::default(),
+ compact_display: Default::default(),
+ currency: None,
+ currency_display: Default::default(),
+ currency_sign: Default::default(),
+ locale_matcher: Default::default(),
+ notation: Default::default(),
+ numbering_system: None,
+ sign_display: Default::default(),
+ style: Default::default(),
+ unit: None,
+ unit_display: Default::default(),
+ use_grouping: Default::default(),
+ rounding_mode: Default::default(),
+ rounding_priority: Default::default(),
+ rounding_increment: 1,
+ trailing_zero_display: Default::default(),
+ minimum_integer_digits: 1,
+ minimum_fraction_digits: None,
+ maximum_fraction_digits: None,
+ minimum_significant_digits: 1,
+ maximum_significant_digits: 21,
+ }
+ }
+}
+
+impl From for js_sys::Object {
+ fn from(options: UseIntlNumberFormatOptions) -> Self {
+ let obj = Self::new();
+
+ let _ = Reflect::set(
+ &obj,
+ &"compactDisplay".into(),
+ &options.compact_display.into(),
+ );
+
+ if let Some(currency) = options.currency {
+ let _ = Reflect::set(&obj, &"currency".into(), ¤cy.into());
+ }
+
+ let _ = Reflect::set(
+ &obj,
+ &"currencyDisplay".into(),
+ &options.currency_display.into(),
+ );
+
+ let _ = Reflect::set(&obj, &"currencySign".into(), &options.currency_sign.into());
+ let _ = Reflect::set(
+ &obj,
+ &"localeMatcher".into(),
+ &options.locale_matcher.into(),
+ );
+ let _ = Reflect::set(&obj, &"notation".into(), &options.notation.into());
+
+ if let Some(numbering_system) = options.numbering_system {
+ let _ = Reflect::set(&obj, &"numberingSystem".into(), &numbering_system.into());
+ }
+
+ let _ = Reflect::set(&obj, &"signDisplay".into(), &options.sign_display.into());
+ let _ = Reflect::set(&obj, &"style".into(), &options.style.into());
+
+ if let Some(unit) = options.unit {
+ let _ = Reflect::set(&obj, &"unit".into(), &unit.into());
+ }
+
+ let _ = Reflect::set(&obj, &"unitDisplay".into(), &options.unit_display.into());
+ let _ = Reflect::set(&obj, &"useGrouping".into(), &options.use_grouping.into());
+
+ let _ = Reflect::set(&obj, &"roundingMode".into(), &options.rounding_mode.into());
+ let _ = Reflect::set(
+ &obj,
+ &"roundingPriority".into(),
+ &options.rounding_priority.into(),
+ );
+ let _ = Reflect::set(
+ &obj,
+ &"roundingIncrement".into(),
+ &options.rounding_increment.into(),
+ );
+ let _ = Reflect::set(
+ &obj,
+ &"trailingZeroDisplay".into(),
+ &options.trailing_zero_display.into(),
+ );
+
+ let _ = Reflect::set(
+ &obj,
+ &"minimumIntegerDigits".into(),
+ &options.minimum_integer_digits.into(),
+ );
+ if let Some(minimum_fraction_digits) = options.minimum_fraction_digits {
+ let _ = Reflect::set(
+ &obj,
+ &"minimumFractionDigits".into(),
+ &minimum_fraction_digits.into(),
+ );
+ }
+ if let Some(maximum_fraction_digits) = options.maximum_fraction_digits {
+ let _ = Reflect::set(
+ &obj,
+ &"maximumFractionDigits".into(),
+ &maximum_fraction_digits.into(),
+ );
+ }
+
+ let _ = Reflect::set(
+ &obj,
+ &"minimumSignificantDigits".into(),
+ &options.minimum_significant_digits.into(),
+ );
+ let _ = Reflect::set(
+ &obj,
+ &"maximumSignificantDigits".into(),
+ &options.maximum_significant_digits.into(),
+ );
+
+ obj
+ }
+}
+
+/// Return type of [`use_intl_number_format`].
+pub struct UseIntlNumberFormatReturn {
+ /// The instance of [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat).
+ pub js_intl_number_format: js_sys::Intl::NumberFormat,
+}
+
+impl UseIntlNumberFormatReturn {
+ /// Formats a number according to the [locale and formatting options](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#parameters) of this `Intl.NumberFormat` object.
+ /// See [`use_intl_number_format`] for more information.
+ pub fn format(&self, cx: Scope, number: impl Into>) -> Signal
+ where
+ N: Clone + 'static,
+ js_sys::Number: From,
+ {
+ let number = number.into();
+ let number_format = self.js_intl_number_format.clone();
+
+ Signal::derive(cx, move || {
+ if let Ok(result) = number_format
+ .format()
+ .call1(&number_format, &js_sys::Number::from(number.get()).into())
+ {
+ result.as_string().unwrap_or_default()
+ } else {
+ "".to_string()
+ }
+ })
+ }
+
+ /// Formats a range of numbers according to the locale and formatting options of this `Intl.NumberFormat` object.
+ ///
+ /// ```
+ /// # use leptos::*;
+ /// # use leptos_use::{NumberStyle, use_intl_number_format, UseIntlNumberFormatOptions};
+ /// #
+ /// # #[component]
+ /// # fn Demo(cx: Scope) -> impl IntoView {
+ /// let nf = use_intl_number_format(
+ /// UseIntlNumberFormatOptions::default()
+ /// .locale("en-US")
+ /// .style(NumberStyle::Currency)
+ /// .currency("USD")
+ /// .maximum_fraction_digits(0),
+ /// );
+ ///
+ /// let formatted = nf.format_range(cx, 3, 5); // "$3 β $5"
+ ///
+ /// // Note: the "approximately equals" symbol is added if
+ /// // startRange and endRange round to the same values.
+ /// let formatted = nf.format_range(cx, 2.9, 3.1); // "~$3"
+ /// #
+ /// # view! { cx, }
+ /// # }
+ /// ```
+ ///
+ /// ```
+ /// # use leptos::*;
+ /// # use leptos_use::{NumberStyle, use_intl_number_format, UseIntlNumberFormatOptions};
+ /// #
+ /// # #[component]
+ /// # fn Demo(cx: Scope) -> impl IntoView {
+ /// let nf = use_intl_number_format(
+ /// UseIntlNumberFormatOptions::default()
+ /// .locale("es-ES")
+ /// .style(NumberStyle::Currency)
+ /// .currency("EUR")
+ /// .maximum_fraction_digits(0),
+ /// );
+ ///
+ /// let formatted = nf.format_range(cx, 3, 5); // "3-5 β¬"
+ /// let formatted = nf.format_range(cx, 2.9, 3.1); // "~3 β¬"
+ /// #
+ /// # view! { cx, }
+ /// # }
+ /// ```
+ pub fn format_range(
+ &self,
+ cx: Scope,
+ start: impl Into>,
+ end: impl Into>,
+ ) -> Signal
+ where
+ NStart: Clone + 'static,
+ NEnd: Clone + 'static,
+ js_sys::Number: From,
+ js_sys::Number: From,
+ {
+ let start = start.into();
+ let end = end.into();
+ let number_format = self.js_intl_number_format.clone();
+
+ Signal::derive(cx, move || {
+ if let Ok(function) = Reflect::get(&number_format, &"formatRange".into()) {
+ let function = function.unchecked_into::();
+
+ if let Ok(result) = function.call2(
+ &number_format,
+ &js_sys::Number::from(start.get()).into(),
+ &js_sys::Number::from(end.get()).into(),
+ ) {
+ return result.as_string().unwrap_or_default();
+ }
+ }
+
+ "".to_string()
+ })
+ }
+
+ // TODO : Allows locale-aware formatting of strings produced by this `Intl.NumberFormat` object.
+ // pub fn format_to_parts(
+ // &self,
+ // cx: Scope,
+ // number: impl Into>,
+ // ) -> Signal>
+ // where
+ // N: Clone + 'static,
+ // f64: From,
+ // {
+ // let number = number.into();
+ // let number_format = self.js_intl_number_format.clone();
+ //
+ // Signal::derive(cx, move || {
+ // let array = number_format.format_to_parts(number.get().into());
+ //
+ // array
+ // .to_vec()
+ // .iter()
+ // .map(|p| p.as_string().unwrap_or_default())
+ // .collect()
+ // })
+ // }
+}
diff --git a/src/utils/js_value_from_to_string.rs b/src/utils/js_value_from_to_string.rs
new file mode 100644
index 0000000..f371c96
--- /dev/null
+++ b/src/utils/js_value_from_to_string.rs
@@ -0,0 +1,11 @@
+macro_rules! js_value_from_to_string {
+ ($name:ident) => {
+ impl From<$name> for JsValue {
+ fn from(value: $name) -> Self {
+ JsValue::from(&value.to_string())
+ }
+ }
+ };
+}
+
+pub(crate) use js_value_from_to_string;
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 9e8b20f..d985115 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -1,9 +1,11 @@
mod clonable_fn;
mod filters;
mod is;
+mod js_value_from_to_string;
mod pausable;
pub use clonable_fn::*;
pub use filters::*;
pub use is::*;
+pub(crate) use js_value_from_to_string::*;
pub use pausable::*;
diff --git a/template/modify_files.py b/template/modify_files.py
index 6685abc..ab43f15 100644
--- a/template/modify_files.py
+++ b/template/modify_files.py
@@ -13,6 +13,7 @@ def main():
args = parser.parse_args()
+ modify_changelog(args)
modify_librs(args)
modify_modrs(args)
modify_summarymd(args)
@@ -106,5 +107,36 @@ def modify_librs(args):
f.write(lib_source)
+def modify_changelog(args):
+ with open("CHANGELOG.md", "r") as f:
+ changelog_source = f.readlines()
+
+ unreleased_heading_exists = False
+ for line in changelog_source:
+ if line.startswith("## [Unreleased"):
+ unreleased_heading_exists = True
+ break
+
+ if not unreleased_heading_exists:
+ changelog_source.insert(5, "## [Unreleased] - \n")
+ changelog_source.insert(6, "\n")
+
+ new_function_heading_exists = False
+ for line in changelog_source:
+ if line.startswith("### New Functions"):
+ new_function_heading_exists = True
+ break
+
+ if not new_function_heading_exists:
+ changelog_source.insert(7, "### New Functions π\n")
+ changelog_source.insert(8, "\n")
+ changelog_source.insert(9, "\n")
+
+ changelog_source.insert(9, f"- `{args.function_name}`\n")
+
+ with open("CHANGELOG.md", "w") as f:
+ f.write("".join(changelog_source))
+
+
if __name__ == '__main__':
main()
diff --git a/template/src/{{ module }}/{{ function_name }}.ffizer.hbs.rs b/template/src/{{ module }}/{{ function_name }}.ffizer.hbs.rs
index ef41a02..5119af5 100644
--- a/template/src/{{ module }}/{{ function_name }}.ffizer.hbs.rs
+++ b/template/src/{{ module }}/{{ function_name }}.ffizer.hbs.rs
@@ -11,7 +11,7 @@ use leptos::*;
///
/// ```
/// # use leptos::*;
-/// use leptos_use{{#if module}}::{{ module }}{{/if}}::{{ function_name }};
+/// # use leptos_use{{#if module}}::{{ module }}{{/if}}::{{ function_name }};
/// #
/// # #[component]
/// # fn Demo(cx: Scope) -> impl IntoView {