#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
use crate::utils::js_value_from_to_string;
use crate::{js, sendwrap_fn};
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::prelude::*;
use leptos::reactive::wrappers::read::Signal;
use std::fmt::Display;
use wasm_bindgen::{JsCast, JsValue};
pub fn use_intl_number_format(options: UseIntlNumberFormatOptions) -> UseIntlNumberFormatReturn {
cfg_if! { if #[cfg(feature = "ssr")] {
UseIntlNumberFormatReturn
} else {
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);
#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum CurrencyDisplay {
#[default]
Symbol,
NarrowSymbol,
Code,
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);
#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Notation {
#[default]
Standard,
Scientific,
Engineering,
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);
#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum SignDisplay {
#[default]
Auto,
Always,
ExceptZero,
Negative,
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 {
Long,
#[default]
Short,
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 {
Always,
#[default]
Auto,
None,
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<NumberGrouping> 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 {
Ceil,
Floor,
Expand,
Trunc,
HalfCeil,
HalfFloor,
#[default]
HalfExpand,
HalfTrunc,
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 {
#[default]
Auto,
MorePrecision,
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 {
#[default]
Auto,
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);
#[derive(DefaultBuilder)]
pub struct UseIntlNumberFormatOptions {
locales: Vec<String>,
compact_display: CompactDisplay,
#[builder(into)]
currency: Option<String>,
currency_display: CurrencyDisplay,
currency_sign: CurrencySign,
locale_matcher: LocaleMatcher,
notation: Notation,
#[builder(into)]
numbering_system: Option<String>,
sign_display: SignDisplay,
style: NumberStyle,
#[builder(into)]
unit: Option<String>,
unit_display: UnitDisplay,
use_grouping: NumberGrouping,
rounding_mode: RoundingMode,
rounding_priority: RoundingPriority,
rounding_increment: u16,
trailing_zero_display: TrailingZeroDisplay,
minimum_integer_digits: u8,
#[builder(into)]
minimum_fraction_digits: Option<u8>,
#[builder(into)]
maximum_fraction_digits: Option<u8>,
#[builder(into)]
minimum_significant_digits: Option<u8>,
#[builder(into)]
maximum_significant_digits: Option<u8>,
}
impl UseIntlNumberFormatOptions {
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: None,
maximum_significant_digits: None,
}
}
}
impl From<UseIntlNumberFormatOptions> for js_sys::Object {
fn from(options: UseIntlNumberFormatOptions) -> Self {
let obj = Self::new();
js!(obj["compactDisplay"] = options.compact_display);
if let Some(currency) = options.currency {
js!(obj["currency"] = currency);
}
js!(obj["currencyDisplay"] = options.currency_display);
js!(obj["currencySign"] = options.currency_sign);
js!(obj["localeMatcher"] = options.locale_matcher);
js!(obj["notation"] = options.notation);
if let Some(numbering_system) = options.numbering_system {
js!(obj["numberingSystem"] = numbering_system);
}
js!(obj["signDisplay"] = options.sign_display);
js!(obj["style"] = options.style);
if let Some(unit) = options.unit {
js!(obj["unit"] = unit);
}
js!(obj["unitDisplay"] = options.unit_display);
js!(obj["useGrouping"] = options.use_grouping);
js!(obj["roundingMode"] = options.rounding_mode);
js!(obj["roundingPriority"] = options.rounding_priority);
js!(obj["roundingIncrement"] = options.rounding_increment);
js!(obj["trailingZeroDisplay"] = options.trailing_zero_display);
js!(obj["minimumIntegerDigits"] = options.minimum_integer_digits);
if let Some(minimum_fraction_digits) = options.minimum_fraction_digits {
js!(obj["minimumFractionDigits"] = minimum_fraction_digits);
}
if let Some(maximum_fraction_digits) = options.maximum_fraction_digits {
js!(obj["maximumFractionDigits"] = maximum_fraction_digits);
}
if let Some(minimum_significant_digits) = options.minimum_significant_digits {
js!(obj["minimumSignificantDigits"] = minimum_significant_digits);
}
if let Some(maximum_significant_digits) = options.maximum_significant_digits {
js!(obj["maximumSignificantDigits"] = maximum_significant_digits);
}
obj
}
}
cfg_if! { if #[cfg(feature = "ssr")] {
pub struct UseIntlNumberFormatReturn;
} else {
pub struct UseIntlNumberFormatReturn {
pub js_intl_number_format: js_sys::Intl::NumberFormat,
}
}}
impl UseIntlNumberFormatReturn {
pub fn format<N>(&self, number: impl Into<Signal<N>>) -> Signal<String>
where
N: Clone + Display + Send + Sync + 'static,
js_sys::Number: From<N>,
{
let number = number.into();
cfg_if! { if #[cfg(feature = "ssr")] {
Signal::derive(move || {
format!("{}", number.get())
})
} else {
let number_format = self.js_intl_number_format.clone();
Signal::derive(sendwrap_fn!(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()
}
}))
}}
}
pub fn format_range<NStart, NEnd>(
&self,
start: impl Into<Signal<NStart>>,
end: impl Into<Signal<NEnd>>,
) -> Signal<String, LocalStorage>
where
NStart: Clone + Display + Send + Sync + 'static,
NEnd: Clone + Display + Send + Sync + 'static,
js_sys::Number: From<NStart>,
js_sys::Number: From<NEnd>,
{
let start = start.into();
let end = end.into();
cfg_if! { if #[cfg(feature = "ssr")] {
Signal::derive_local(move || {
format!("{} - {}", start.get(), end.get())
})
} else {
let number_format = self.js_intl_number_format.clone();
Signal::derive_local(move || {
if let Ok(function) = js!(number_format["formatRange"]) {
let function = function.unchecked_into::<js_sys::Function>();
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()
})
}}
}
}