@merkaly/nuxt 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,3 +21,67 @@ A Nuxt module that provides Auth0 authentication integration and API utilities f
21
21
  ## Quick Setup
22
22
 
23
23
  Install the module to your Nuxt application:
24
+
25
+ ## Money formatting
26
+
27
+ Money in Merkaly is stored as **integer cents**. `MKFormatMoney`, `MKInputMoney`,
28
+ `formatMoney()` and `useMoneyFormat()` divide by 100 and format with
29
+ `Intl.NumberFormat(locale, { style: 'currency', currency })`, so `24969` with
30
+ `BRL` + `pt-BR` renders `R$ 249,69`.
31
+
32
+ Currency and locale are resolved in this order:
33
+
34
+ 1. Component props: `<MKFormatMoney :value="24969" currency="BRL" locale="pt-BR" />`
35
+ 2. App-level default set at runtime with `setMoneyDefaults()` (stored with `useState`)
36
+ 3. Module option `merkaly.money` (runtime config)
37
+ 4. Fallback `USD` / `en-US`
38
+
39
+ ### Module option
40
+
41
+ ```ts
42
+ export default defineNuxtConfig({
43
+ merkaly: {
44
+ money: {
45
+ currency: 'BRL', // ISO 4217
46
+ locale: 'pt-BR', // BCP 47
47
+ },
48
+ },
49
+ });
50
+ ```
51
+
52
+ It lives in `runtimeConfig.public.merkaly.money`, so it can also be set with
53
+ `NUXT_PUBLIC_MERKALY_MONEY_CURRENCY` / `NUXT_PUBLIC_MERKALY_MONEY_LOCALE`.
54
+
55
+ ### Runtime default (per organization)
56
+
57
+ ```ts
58
+ const { setMoneyDefaults } = useMoneyFormat();
59
+
60
+ // after loading the organization
61
+ setMoneyDefaults({
62
+ currency: organization.localization.currency, // e.g. 'BRL'
63
+ locale: organization.localization.language, // e.g. 'pt' or 'pt-BR'
64
+ });
65
+ ```
66
+
67
+ `useMoneyFormat()` returns:
68
+
69
+ | Key | Description |
70
+ | --- | --- |
71
+ | `currency`, `locale` | Resolved defaults (computed) |
72
+ | `setMoneyDefaults({ currency?, locale? })` | Sets the app-level default (merged) |
73
+ | `resetMoneyDefaults()` | Clears the app-level default |
74
+ | `format(cents, options?)` | Formatted string using the resolved defaults |
75
+ | `formatParts(cents, options?)` | `{ prefix, amount, digits, suffix }` parts |
76
+
77
+ `setMoneyDefaults(defaults)` is also auto-imported as a standalone shortcut.
78
+
79
+ ### Components
80
+
81
+ - `MKFormatMoney`: props `value` (cents), `currency`, `locale`, `base` (default `100`),
82
+ `mode`, `hideDecimal`, `tag`. `currency` also accepts a plain symbol (`$`) for
83
+ backward compatibility: the number keeps the resolved currency layout and the
84
+ symbol replaces the currency sign.
85
+ - `MKInputMoney`: `v-model` in cents; props `currency`, `locale`. `prefix`, `decimal`,
86
+ `thousands` and `placeholder` default to the resolved currency/locale and can still
87
+ be passed explicitly.
package/dist/module.d.mts CHANGED
@@ -36,6 +36,18 @@ interface MerkalyModuleOptions {
36
36
  */
37
37
  strategy?: Strategies;
38
38
  };
39
+ /**
40
+ * Default currency/locale for `MKFormatMoney`, `MKInputMoney` and
41
+ * `useMoneyFormat()`. Apps can override it at runtime with
42
+ * `setMoneyDefaults()` (e.g. after loading the organization) and per
43
+ * component with the `currency` / `locale` props.
44
+ */
45
+ money?: {
46
+ /** ISO 4217 code, e.g. `BRL`. Defaults to `USD`. */
47
+ currency?: string;
48
+ /** BCP 47 locale, e.g. `pt-BR`. Defaults to `en-US`. */
49
+ locale?: string;
50
+ };
39
51
  plausible?: {
40
52
  domain: string;
41
53
  localhost: string;
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.14.0"
6
6
  },
7
- "version": "0.8.1",
7
+ "version": "0.9.1",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -30,6 +30,10 @@ const defaultOptions = {
30
30
  locales: [],
31
31
  strategy: "no_prefix"
32
32
  },
33
+ money: {
34
+ currency: "USD",
35
+ locale: "en-US"
36
+ },
33
37
  plausible: {
34
38
  domain: "",
35
39
  localhost: ""
@@ -133,6 +137,7 @@ function configureRuntimeConfig(nuxt, options) {
133
137
  api: options.api,
134
138
  auth0: options.auth0,
135
139
  i18n: options.i18n,
140
+ money: options.money,
136
141
  plausible: options.plausible,
137
142
  sentry: {
138
143
  dsn: options.sentry.dsn,
@@ -4,17 +4,19 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
4
4
  type: NumberConstructor;
5
5
  default: number;
6
6
  };
7
+ /** ISO 4217 code (`BRL`) or, for backward compatibility, a symbol (`$`). Falls back to the app default. */
7
8
  currency: {
8
9
  type: StringConstructor;
9
- default: () => string;
10
+ default: undefined;
10
11
  };
11
12
  hideDecimal: {
12
13
  type: BooleanConstructor;
13
14
  default: () => boolean;
14
15
  };
16
+ /** BCP 47 locale (`pt-BR`). Falls back to the app default. */
15
17
  locale: {
16
18
  type: StringConstructor;
17
- default: () => string;
19
+ default: undefined;
18
20
  };
19
21
  mode: {
20
22
  type: PropType<Intl.NumberFormatOptionsStyle>;
@@ -33,17 +35,19 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
33
35
  type: NumberConstructor;
34
36
  default: number;
35
37
  };
38
+ /** ISO 4217 code (`BRL`) or, for backward compatibility, a symbol (`$`). Falls back to the app default. */
36
39
  currency: {
37
40
  type: StringConstructor;
38
- default: () => string;
41
+ default: undefined;
39
42
  };
40
43
  hideDecimal: {
41
44
  type: BooleanConstructor;
42
45
  default: () => boolean;
43
46
  };
47
+ /** BCP 47 locale (`pt-BR`). Falls back to the app default. */
44
48
  locale: {
45
49
  type: StringConstructor;
46
- default: () => string;
50
+ default: undefined;
47
51
  };
48
52
  mode: {
49
53
  type: PropType<Intl.NumberFormatOptionsStyle>;
@@ -61,10 +65,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
61
65
  mode: keyof Intl.NumberFormatOptionsStyleRegistry;
62
66
  value: number;
63
67
  currency: string;
68
+ locale: string;
64
69
  base: number;
65
70
  tag: string;
66
71
  hideDecimal: boolean;
67
- locale: string;
68
72
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
69
73
  declare const _default: typeof __VLS_export;
70
74
  export default _default;
@@ -1,33 +1,32 @@
1
1
  <script setup>
2
2
  import { computed } from "vue";
3
- import { formatMoney } from "../../utils/formatMoney";
3
+ import { useMoneyFormat } from "../../composables/useMoneyFormat";
4
4
  const props = defineProps({
5
5
  base: { type: Number, default: 100 },
6
- currency: { type: String, default: () => "USD" },
6
+ /** ISO 4217 code (`BRL`) or, for backward compatibility, a symbol (`$`). Falls back to the app default. */
7
+ currency: { type: String, default: void 0 },
7
8
  hideDecimal: { type: Boolean, default: () => false },
8
- locale: { type: String, default: () => "en-US" },
9
+ /** BCP 47 locale (`pt-BR`). Falls back to the app default. */
10
+ locale: { type: String, default: void 0 },
9
11
  mode: { type: String, default: "currency" },
10
12
  tag: { type: String, default: "span" },
11
13
  value: { type: Number, default: 0 }
12
14
  });
13
- const money = computed(() => formatMoney(props.value, {
15
+ const { formatParts } = useMoneyFormat();
16
+ const price = computed(() => formatParts(props.value, {
14
17
  base: props.base,
15
18
  currency: props.currency,
16
19
  locale: props.locale,
17
20
  mode: props.mode
18
21
  }));
19
- const price = computed(() => ({
20
- currency: money.value[0],
21
- amount: money.value.slice(1, -3),
22
- digits: money.value.slice(-3)
23
- }));
24
22
  </script>
25
23
 
26
24
  <template>
27
- <component :is="tag" class="format-money">
28
- <span class="currency" v-text="price.currency" />
25
+ <component :is="props.tag" class="format-money">
26
+ <span v-if="price.prefix" class="currency" v-text="price.prefix" />
29
27
  <span class="value" v-text="price.amount" />
30
- <sup v-if="!props.hideDecimal" class="digits" v-text="price.digits" />
28
+ <sup v-if="!props.hideDecimal && price.digits" class="digits" v-text="price.digits" />
29
+ <span v-if="price.suffix" class="currency" v-text="price.suffix" />
31
30
  </component>
32
31
  </template>
33
32
 
@@ -4,17 +4,19 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
4
4
  type: NumberConstructor;
5
5
  default: number;
6
6
  };
7
+ /** ISO 4217 code (`BRL`) or, for backward compatibility, a symbol (`$`). Falls back to the app default. */
7
8
  currency: {
8
9
  type: StringConstructor;
9
- default: () => string;
10
+ default: undefined;
10
11
  };
11
12
  hideDecimal: {
12
13
  type: BooleanConstructor;
13
14
  default: () => boolean;
14
15
  };
16
+ /** BCP 47 locale (`pt-BR`). Falls back to the app default. */
15
17
  locale: {
16
18
  type: StringConstructor;
17
- default: () => string;
19
+ default: undefined;
18
20
  };
19
21
  mode: {
20
22
  type: PropType<Intl.NumberFormatOptionsStyle>;
@@ -33,17 +35,19 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
33
35
  type: NumberConstructor;
34
36
  default: number;
35
37
  };
38
+ /** ISO 4217 code (`BRL`) or, for backward compatibility, a symbol (`$`). Falls back to the app default. */
36
39
  currency: {
37
40
  type: StringConstructor;
38
- default: () => string;
41
+ default: undefined;
39
42
  };
40
43
  hideDecimal: {
41
44
  type: BooleanConstructor;
42
45
  default: () => boolean;
43
46
  };
47
+ /** BCP 47 locale (`pt-BR`). Falls back to the app default. */
44
48
  locale: {
45
49
  type: StringConstructor;
46
- default: () => string;
50
+ default: undefined;
47
51
  };
48
52
  mode: {
49
53
  type: PropType<Intl.NumberFormatOptionsStyle>;
@@ -61,10 +65,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
61
65
  mode: keyof Intl.NumberFormatOptionsStyleRegistry;
62
66
  value: number;
63
67
  currency: string;
68
+ locale: string;
64
69
  base: number;
65
70
  tag: string;
66
71
  hideDecimal: boolean;
67
- locale: string;
68
72
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
69
73
  declare const _default: typeof __VLS_export;
70
74
  export default _default;
@@ -1,8 +1,18 @@
1
1
  import type { Numberish } from 'bootstrap-vue-next/dist/src/types/CommonTypes';
2
2
  declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
3
+ /** ISO 4217 code (`BRL`). Falls back to the app default. */
4
+ currency: {
5
+ type: StringConstructor;
6
+ default: undefined;
7
+ };
3
8
  decimal: {
4
9
  type: StringConstructor;
5
- default: () => string;
10
+ default: undefined;
11
+ };
12
+ /** BCP 47 locale (`pt-BR`). Falls back to the app default. */
13
+ locale: {
14
+ type: StringConstructor;
15
+ default: undefined;
6
16
  };
7
17
  max: {
8
18
  type: NumberConstructor;
@@ -15,7 +25,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
15
25
  };
16
26
  placeholder: {
17
27
  type: StringConstructor;
18
- default: () => string;
28
+ default: undefined;
19
29
  };
20
30
  precision: {
21
31
  type: NumberConstructor;
@@ -23,7 +33,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
23
33
  };
24
34
  prefix: {
25
35
  type: StringConstructor;
26
- default: () => string;
36
+ default: undefined;
27
37
  };
28
38
  suffix: {
29
39
  type: StringConstructor;
@@ -31,15 +41,25 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
31
41
  };
32
42
  thousands: {
33
43
  type: StringConstructor;
34
- default: () => string;
44
+ default: undefined;
35
45
  };
36
46
  modelValue: Numberish;
37
47
  }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
38
48
  "update:modelValue": (value: Numberish) => any;
39
49
  }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
50
+ /** ISO 4217 code (`BRL`). Falls back to the app default. */
51
+ currency: {
52
+ type: StringConstructor;
53
+ default: undefined;
54
+ };
40
55
  decimal: {
41
56
  type: StringConstructor;
42
- default: () => string;
57
+ default: undefined;
58
+ };
59
+ /** BCP 47 locale (`pt-BR`). Falls back to the app default. */
60
+ locale: {
61
+ type: StringConstructor;
62
+ default: undefined;
43
63
  };
44
64
  max: {
45
65
  type: NumberConstructor;
@@ -52,7 +72,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
52
72
  };
53
73
  placeholder: {
54
74
  type: StringConstructor;
55
- default: () => string;
75
+ default: undefined;
56
76
  };
57
77
  precision: {
58
78
  type: NumberConstructor;
@@ -60,7 +80,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
60
80
  };
61
81
  prefix: {
62
82
  type: StringConstructor;
63
- default: () => string;
83
+ default: undefined;
64
84
  };
65
85
  suffix: {
66
86
  type: StringConstructor;
@@ -68,15 +88,17 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
68
88
  };
69
89
  thousands: {
70
90
  type: StringConstructor;
71
- default: () => string;
91
+ default: undefined;
72
92
  };
73
93
  modelValue: Numberish;
74
94
  }>> & Readonly<{
75
95
  "onUpdate:modelValue"?: ((value: Numberish) => any) | undefined;
76
96
  }>, {
97
+ decimal: string;
98
+ currency: string;
99
+ locale: string;
77
100
  min: number;
78
101
  max: number;
79
- decimal: string;
80
102
  placeholder: string;
81
103
  precision: number;
82
104
  prefix: string;
@@ -1,6 +1,8 @@
1
1
  <script setup>
2
2
  import { computed } from "vue";
3
3
  import { Money3Component } from "v-money3";
4
+ import { useMoneyFormat } from "../../composables/useMoneyFormat";
5
+ import { isCurrencyCode } from "../../utils/formatMoney";
4
6
  const money = defineModel({ type: null, ...{
5
7
  default: () => 0,
6
8
  get: (v) => v / 100,
@@ -8,21 +10,43 @@ const money = defineModel({ type: null, ...{
8
10
  type: [String, Number]
9
11
  } });
10
12
  const props = defineProps({
11
- decimal: { type: String, default: () => "." },
13
+ /** ISO 4217 code (`BRL`). Falls back to the app default. */
14
+ currency: { type: String, default: void 0 },
15
+ decimal: { type: String, default: void 0 },
16
+ /** BCP 47 locale (`pt-BR`). Falls back to the app default. */
17
+ locale: { type: String, default: void 0 },
12
18
  max: { type: Number, required: false, default: void 0 },
13
19
  min: { type: Number, default: () => 0 },
14
- placeholder: { type: String, default: () => "$0.00" },
20
+ placeholder: { type: String, default: void 0 },
15
21
  precision: { type: Number, default: () => 2 },
16
- prefix: { type: String, default: () => "$" },
22
+ prefix: { type: String, default: void 0 },
17
23
  suffix: { type: String, default: () => " " },
18
- thousands: { type: String, default: () => "," }
24
+ thousands: { type: String, default: void 0 }
25
+ });
26
+ const { resolve, formatParts } = useMoneyFormat();
27
+ const separators = computed(() => {
28
+ const options = resolve({ currency: props.currency, locale: props.locale });
29
+ const code = isCurrencyCode(options.currency) ? options.currency.toUpperCase() : options.symbolCurrency;
30
+ const parts = new Intl.NumberFormat(options.locale, { style: "currency", currency: code }).formatToParts(123456789e-2);
31
+ return {
32
+ decimal: parts.find((part) => part.type === "decimal")?.value ?? ".",
33
+ symbol: isCurrencyCode(options.currency) ? parts.find((part) => part.type === "currency")?.value ?? options.currency : options.currency,
34
+ thousands: parts.find((part) => part.type === "group")?.value ?? ","
35
+ };
36
+ });
37
+ const placeholder = computed(() => {
38
+ if (props.placeholder !== void 0) {
39
+ return props.placeholder;
40
+ }
41
+ const parts = formatParts(0, { currency: props.currency, locale: props.locale });
42
+ return parts.prefix + parts.amount + parts.digits + parts.suffix;
19
43
  });
20
44
  const config = computed(() => ({
21
- decimal: props.decimal,
45
+ decimal: props.decimal ?? separators.value.decimal,
22
46
  precision: props.precision,
23
- prefix: props.prefix.concat(" "),
47
+ prefix: (props.prefix ?? separators.value.symbol).concat(" "),
24
48
  suffix: props.suffix,
25
- thousands: props.thousands
49
+ thousands: props.thousands ?? separators.value.thousands
26
50
  }));
27
51
  </script>
28
52
 
@@ -31,7 +55,7 @@ const config = computed(() => ({
31
55
  v-model.number="money"
32
56
  :max="props.max"
33
57
  :min="props.min"
34
- :placeholder="props.placeholder"
58
+ :placeholder="placeholder"
35
59
  class="form-control"
36
60
  v-bind="config"
37
61
  />
@@ -1,8 +1,18 @@
1
1
  import type { Numberish } from 'bootstrap-vue-next/dist/src/types/CommonTypes';
2
2
  declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
3
+ /** ISO 4217 code (`BRL`). Falls back to the app default. */
4
+ currency: {
5
+ type: StringConstructor;
6
+ default: undefined;
7
+ };
3
8
  decimal: {
4
9
  type: StringConstructor;
5
- default: () => string;
10
+ default: undefined;
11
+ };
12
+ /** BCP 47 locale (`pt-BR`). Falls back to the app default. */
13
+ locale: {
14
+ type: StringConstructor;
15
+ default: undefined;
6
16
  };
7
17
  max: {
8
18
  type: NumberConstructor;
@@ -15,7 +25,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
15
25
  };
16
26
  placeholder: {
17
27
  type: StringConstructor;
18
- default: () => string;
28
+ default: undefined;
19
29
  };
20
30
  precision: {
21
31
  type: NumberConstructor;
@@ -23,7 +33,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
23
33
  };
24
34
  prefix: {
25
35
  type: StringConstructor;
26
- default: () => string;
36
+ default: undefined;
27
37
  };
28
38
  suffix: {
29
39
  type: StringConstructor;
@@ -31,15 +41,25 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
31
41
  };
32
42
  thousands: {
33
43
  type: StringConstructor;
34
- default: () => string;
44
+ default: undefined;
35
45
  };
36
46
  modelValue: Numberish;
37
47
  }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
38
48
  "update:modelValue": (value: Numberish) => any;
39
49
  }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
50
+ /** ISO 4217 code (`BRL`). Falls back to the app default. */
51
+ currency: {
52
+ type: StringConstructor;
53
+ default: undefined;
54
+ };
40
55
  decimal: {
41
56
  type: StringConstructor;
42
- default: () => string;
57
+ default: undefined;
58
+ };
59
+ /** BCP 47 locale (`pt-BR`). Falls back to the app default. */
60
+ locale: {
61
+ type: StringConstructor;
62
+ default: undefined;
43
63
  };
44
64
  max: {
45
65
  type: NumberConstructor;
@@ -52,7 +72,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
52
72
  };
53
73
  placeholder: {
54
74
  type: StringConstructor;
55
- default: () => string;
75
+ default: undefined;
56
76
  };
57
77
  precision: {
58
78
  type: NumberConstructor;
@@ -60,7 +80,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
60
80
  };
61
81
  prefix: {
62
82
  type: StringConstructor;
63
- default: () => string;
83
+ default: undefined;
64
84
  };
65
85
  suffix: {
66
86
  type: StringConstructor;
@@ -68,15 +88,17 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
68
88
  };
69
89
  thousands: {
70
90
  type: StringConstructor;
71
- default: () => string;
91
+ default: undefined;
72
92
  };
73
93
  modelValue: Numberish;
74
94
  }>> & Readonly<{
75
95
  "onUpdate:modelValue"?: ((value: Numberish) => any) | undefined;
76
96
  }>, {
97
+ decimal: string;
98
+ currency: string;
99
+ locale: string;
77
100
  min: number;
78
101
  max: number;
79
- decimal: string;
80
102
  placeholder: string;
81
103
  precision: number;
82
104
  prefix: string;
@@ -0,0 +1,24 @@
1
+ import { type FormatMoneyOptions } from '../utils/formatMoney.js';
2
+ export interface MoneyDefaults {
3
+ /** ISO 4217 code, e.g. `BRL`. */
4
+ currency?: string;
5
+ /** BCP 47 locale, e.g. `pt-BR` (a bare language such as `pt` also works). */
6
+ locale?: string;
7
+ }
8
+ /**
9
+ * App-level money defaults used by `MKFormatMoney` / `MKInputMoney`.
10
+ *
11
+ * Resolution order: component props > `setMoneyDefaults()` (runtime, per app) >
12
+ * `merkaly.money` module option (runtime config) > `USD` / `en-US`.
13
+ */
14
+ export declare const useMoneyFormat: () => {
15
+ currency: import("vue").ComputedRef<string>;
16
+ format: (value: number, options?: FormatMoneyOptions) => string;
17
+ formatParts: (value: number, options?: FormatMoneyOptions) => import("#imports").FormatMoneyParts;
18
+ locale: import("vue").ComputedRef<string>;
19
+ resetMoneyDefaults: () => void;
20
+ resolve: (options?: FormatMoneyOptions) => FormatMoneyOptions;
21
+ setMoneyDefaults: (defaults: MoneyDefaults) => void;
22
+ };
23
+ /** Shortcut for `useMoneyFormat().setMoneyDefaults(defaults)`. */
24
+ export declare const setMoneyDefaults: (defaults: MoneyDefaults) => void;
@@ -0,0 +1,37 @@
1
+ import { computed } from "vue";
2
+ import { useRuntimeConfig, useState } from "#imports";
3
+ import {
4
+ DEFAULT_MONEY_CURRENCY,
5
+ DEFAULT_MONEY_LOCALE,
6
+ formatMoney,
7
+ formatMoneyParts
8
+ } from "../utils/formatMoney.js";
9
+ const STATE_KEY = "merkaly:money";
10
+ export const useMoneyFormat = () => {
11
+ const state = useState(STATE_KEY, () => ({}));
12
+ const config = useRuntimeConfig().public.merkaly;
13
+ const currency = computed(() => state.value.currency || config?.money?.currency || DEFAULT_MONEY_CURRENCY);
14
+ const locale = computed(() => state.value.locale || config?.money?.locale || DEFAULT_MONEY_LOCALE);
15
+ const setMoneyDefaults2 = (defaults) => {
16
+ state.value = { ...state.value, ...defaults };
17
+ };
18
+ const resetMoneyDefaults = () => {
19
+ state.value = {};
20
+ };
21
+ const resolve = (options = {}) => ({
22
+ ...options,
23
+ currency: options.currency || currency.value,
24
+ locale: options.locale || locale.value,
25
+ symbolCurrency: options.symbolCurrency || currency.value
26
+ });
27
+ return {
28
+ currency,
29
+ format: (value, options) => formatMoney(value, resolve(options)),
30
+ formatParts: (value, options) => formatMoneyParts(value, resolve(options)),
31
+ locale,
32
+ resetMoneyDefaults,
33
+ resolve,
34
+ setMoneyDefaults: setMoneyDefaults2
35
+ };
36
+ };
37
+ export const setMoneyDefaults = (defaults) => useMoneyFormat().setMoneyDefaults(defaults);
@@ -1,19 +1,49 @@
1
+ export declare const DEFAULT_MONEY_CURRENCY = "USD";
2
+ export declare const DEFAULT_MONEY_LOCALE = "en-US";
1
3
  export interface FormatMoneyOptions {
2
4
  base?: number;
5
+ /**
6
+ * ISO 4217 code (`BRL`, `USD`, ...). For backward compatibility a plain symbol
7
+ * (`$`, `R$`) is also accepted: the number is formatted with `symbolCurrency`
8
+ * (or USD) and the symbol replaces the currency sign.
9
+ */
3
10
  currency?: string;
4
11
  locale?: string;
5
12
  mode?: Intl.NumberFormatOptionsStyle;
6
13
  notation?: Intl.NumberFormatOptions['notation'];
7
14
  maximumFractionDigits?: number;
15
+ /** Currency used for the number layout when `currency` is a symbol. */
16
+ symbolCurrency?: string;
8
17
  }
18
+ export interface FormatMoneyParts {
19
+ /** Everything before the integer part (sign, currency symbol, spacing). */
20
+ prefix: string;
21
+ /** Integer part with grouping separators. */
22
+ amount: string;
23
+ /** Decimal separator plus fraction digits (empty when there are none). */
24
+ digits: string;
25
+ /** Everything after the fraction (suffix currency symbols, compact units). */
26
+ suffix: string;
27
+ }
28
+ export declare function isCurrencyCode(value?: string): value is string;
29
+ /**
30
+ * Splits a monetary value held as integer cents into display parts using
31
+ * `Intl.NumberFormat#formatToParts`, so multi-character symbols (`R$`) and
32
+ * locale separators (`249,69`) are preserved.
33
+ */
34
+ export declare function formatMoneyParts(value: number, options?: FormatMoneyOptions): FormatMoneyParts;
9
35
  /**
10
36
  * Formats a monetary value held as an **integer in the minor unit (cents)**.
11
37
  *
12
38
  * Across Merkaly money is stored and transported as integer cents ($1.00 -> 100),
13
39
  * so this helper divides by `base` (default 100) before formatting. Pass cents in,
14
- * get a currency string out: `formatMoney(100)` -> `"$1.00"`.
40
+ * get a currency string out: `formatMoney(100)` -> `"$1.00"`,
41
+ * `formatMoney(24969, { currency: 'BRL', locale: 'pt-BR' })` -> `"R$ 249,69"`.
42
+ *
43
+ * This helper is pure (no app defaults). Inside components prefer
44
+ * `useMoneyFormat().format()`, which applies the app-level currency/locale.
15
45
  *
16
46
  * For non-currency numbers (counts, units, percentages) use `formatNumber` /
17
- * `MKFormatNumber` instead — those do NOT divide and would show the raw cents.
47
+ * `MKFormatNumber` instead, those do NOT divide and would show the raw cents.
18
48
  */
19
49
  export declare function formatMoney(value: number, options?: FormatMoneyOptions): string;
@@ -1,8 +1,45 @@
1
- export function formatMoney(value, options = {}) {
2
- return (value / (options.base ?? 100)).toLocaleString(options.locale ?? "en-US", {
3
- style: options.mode ?? "currency",
4
- currency: options.currency ?? "USD",
1
+ export const DEFAULT_MONEY_CURRENCY = "USD";
2
+ export const DEFAULT_MONEY_LOCALE = "en-US";
3
+ const CURRENCY_CODE = /^[A-Z]{3}$/i;
4
+ export function isCurrencyCode(value) {
5
+ return Boolean(value && CURRENCY_CODE.test(value));
6
+ }
7
+ function toFormatter(options) {
8
+ const symbol = isCurrencyCode(options.currency) ? void 0 : options.currency;
9
+ const currency = symbol ? isCurrencyCode(options.symbolCurrency) ? options.symbolCurrency : DEFAULT_MONEY_CURRENCY : options.currency ?? DEFAULT_MONEY_CURRENCY;
10
+ const formatter = new Intl.NumberFormat(options.locale || DEFAULT_MONEY_LOCALE, {
11
+ currency: currency.toUpperCase(),
12
+ maximumFractionDigits: options.maximumFractionDigits,
5
13
  notation: options.notation,
6
- maximumFractionDigits: options.maximumFractionDigits
14
+ style: options.mode ?? "currency"
15
+ });
16
+ return { formatter, symbol };
17
+ }
18
+ export function formatMoneyParts(value, options = {}) {
19
+ const { formatter, symbol } = toFormatter(options);
20
+ const parts = formatter.formatToParts(value / (options.base ?? 100)).map((part) => symbol && part.type === "currency" ? { ...part, value: symbol } : part);
21
+ const result = { prefix: "", amount: "", digits: "", suffix: "" };
22
+ const firstNumeric = parts.findIndex((part) => part.type === "integer");
23
+ let lastNumeric = -1;
24
+ parts.forEach((part, index) => {
25
+ if (["integer", "group", "decimal", "fraction"].includes(part.type)) {
26
+ lastNumeric = index;
27
+ }
7
28
  });
29
+ parts.forEach((part, index) => {
30
+ if (firstNumeric === -1 || index < firstNumeric) {
31
+ result.prefix += part.value;
32
+ } else if (index > lastNumeric) {
33
+ result.suffix += part.value;
34
+ } else if (part.type === "decimal" || part.type === "fraction") {
35
+ result.digits += part.value;
36
+ } else {
37
+ result.amount += part.value;
38
+ }
39
+ });
40
+ return result;
41
+ }
42
+ export function formatMoney(value, options = {}) {
43
+ const parts = formatMoneyParts(value, options);
44
+ return parts.prefix + parts.amount + parts.digits + parts.suffix;
8
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merkaly/nuxt",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/merkaly-io/nuxt.git"
@@ -30,7 +30,7 @@
30
30
  "dependencies": {
31
31
  "@auth0/auth0-spa-js": "^2.29.0",
32
32
  "@bootstrap-vue-next/nuxt": "^1.0.0",
33
- "@nuxt/devtools": "^3.4.0",
33
+ "@nuxt/devtools": "^4.0.0",
34
34
  "@nuxt/eslint": "1.17.0",
35
35
  "@nuxt/eslint-config": "^1.17.0",
36
36
  "@nuxt/fonts": "1.2.0",