From 45c09b4431e87112057707dd6589b80dc9f9296c Mon Sep 17 00:00:00 2001 From: Maccesch Date: Fri, 14 Jul 2023 05:48:37 +0100 Subject: [PATCH] added use_intl_number_format --- .idea/leptos-use.iml | 2 + CHANGELOG.md | 7 + Cargo.toml | 2 +- README.md | 2 +- docs/book/src/SUMMARY.md | 4 + docs/book/src/functions.md | 2 + docs/book/src/intl/use_intl_number_format.md | 3 + examples/Cargo.toml | 1 + examples/use_intl_number_format/Cargo.toml | 16 + examples/use_intl_number_format/README.md | 23 + examples/use_intl_number_format/Trunk.toml | 2 + examples/use_intl_number_format/index.html | 7 + examples/use_intl_number_format/input.css | 3 + .../rust-toolchain.toml | 2 + examples/use_intl_number_format/src/main.rs | 56 ++ .../use_intl_number_format/style/output.css | 289 ++++++ .../use_intl_number_format/tailwind.config.js | 15 + src/lib.rs | 2 + src/use_intl_number_format.rs | 922 ++++++++++++++++++ src/utils/js_value_from_to_string.rs | 11 + src/utils/mod.rs | 2 + template/modify_files.py | 32 + .../{{ function_name }}.ffizer.hbs.rs | 2 +- 23 files changed, 1404 insertions(+), 3 deletions(-) create mode 100644 docs/book/src/intl/use_intl_number_format.md create mode 100644 examples/use_intl_number_format/Cargo.toml create mode 100644 examples/use_intl_number_format/README.md create mode 100644 examples/use_intl_number_format/Trunk.toml create mode 100644 examples/use_intl_number_format/index.html create mode 100644 examples/use_intl_number_format/input.css create mode 100644 examples/use_intl_number_format/rust-toolchain.toml create mode 100644 examples/use_intl_number_format/src/main.rs create mode 100644 examples/use_intl_number_format/style/output.css create mode 100644 examples/use_intl_number_format/tailwind.config.js create mode 100644 src/use_intl_number_format.rs create mode 100644 src/utils/js_value_from_to_string.rs 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 {