@innertia-solutions/innertia-nuxt 0.1.42 → 0.1.43

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.
@@ -0,0 +1,99 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, watch, nextTick } from 'vue'
3
+
4
+ /*
5
+ * CurrencyInput — input de moneda con formato SOLO visual.
6
+ * El `v-model` es SIEMPRE un número puro (o null); lo que se muestra es el valor
7
+ * agrupado con separador de miles y prefijo (ej. "$2.234.234"). Pensado para CLP
8
+ * (entero, sin decimales); `locale`/`prefix` lo hacen reusable.
9
+ */
10
+ const props = withDefaults(defineProps<{
11
+ label?: string
12
+ placeholder?: string
13
+ disabled?: boolean
14
+ error?: string | null
15
+ hint?: string
16
+ size?: 'sm' | 'md'
17
+ prefix?: string
18
+ locale?: string
19
+ min?: number | null
20
+ }>(), {
21
+ prefix: '$',
22
+ locale: 'es-CL',
23
+ min: null,
24
+ })
25
+
26
+ const model = defineModel<number | null>({ default: null })
27
+
28
+ const inputEl = ref<HTMLInputElement | null>(null)
29
+ const focused = ref(false)
30
+
31
+ const nf = computed(() => new Intl.NumberFormat(props.locale, {
32
+ minimumFractionDigits: 0,
33
+ maximumFractionDigits: 0,
34
+ }))
35
+
36
+ function format(n: number | null | undefined): string {
37
+ if (n === null || n === undefined || Number.isNaN(n)) return ''
38
+ return props.prefix + nf.value.format(n)
39
+ }
40
+
41
+ function parse(str: string): number | null {
42
+ const digits = (str || '').replace(/\D/g, '')
43
+ if (!digits) return null
44
+ return Number(digits)
45
+ }
46
+
47
+ const display = ref(format(model.value))
48
+
49
+ // Mantener el display en sync cuando el valor cambia desde fuera (no mientras escribe).
50
+ watch(model, (v) => {
51
+ if (!focused.value) display.value = format(v)
52
+ })
53
+
54
+ function onInput(e: Event) {
55
+ const el = e.target as HTMLInputElement
56
+ let n = parse(el.value)
57
+ if (n !== null && props.min !== null && n < props.min) n = props.min
58
+ model.value = n
59
+ display.value = format(n)
60
+ // Caret al final tras reformatear (evita saltos raros al agrupar).
61
+ nextTick(() => {
62
+ const len = inputEl.value?.value.length ?? 0
63
+ inputEl.value?.setSelectionRange(len, len)
64
+ })
65
+ }
66
+
67
+ function onBlur() {
68
+ focused.value = false
69
+ display.value = format(model.value)
70
+ }
71
+
72
+ const baseClasses = computed(() =>
73
+ `innertia-field${props.size === 'sm' ? ' innertia-field-sm' : ''}`,
74
+ )
75
+ </script>
76
+
77
+ <template>
78
+ <div class="w-full">
79
+ <label v-if="label" class="block text-sm font-medium text-foreground mb-1.5">
80
+ {{ label }}
81
+ </label>
82
+
83
+ <input
84
+ ref="inputEl"
85
+ type="text"
86
+ inputmode="numeric"
87
+ :value="display"
88
+ :placeholder="placeholder"
89
+ :disabled="disabled"
90
+ :class="[baseClasses, error ? '!border-red-400 dark:!border-red-500' : '']"
91
+ @input="onInput"
92
+ @focus="focused = true"
93
+ @blur="onBlur"
94
+ />
95
+
96
+ <p v-if="error" class="text-xs text-red-500 dark:text-red-400 mt-1">{{ error }}</p>
97
+ <p v-else-if="hint" class="text-xs text-muted-foreground mt-1">{{ hint }}</p>
98
+ </div>
99
+ </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@innertia-solutions/innertia-nuxt",
3
- "version": "0.1.42",
3
+ "version": "0.1.43",
4
4
  "description": "Innertia Solutions — Nuxt layer unificada: core, auth, multitenancy, theme y app contexts",
5
5
  "keywords": [
6
6
  "nuxt",