@innertia-solutions/ui 0.1.5 → 0.1.6

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.
@@ -1,198 +1,38 @@
1
1
  <script setup>
2
2
  // Props del componente
3
3
  const props = defineProps({
4
- // Endpoint para la carga (SSR)
5
- endpoint: {
6
- type: String,
7
- required: true,
8
- },
9
-
10
- listFormat: {
11
- type: Boolean,
12
- required: false,
13
- default: true,
14
- },
15
-
16
- perPage: {
17
- type: Number,
18
- required: false,
19
- default: 15,
20
- },
21
-
22
- initialOptions: {
23
- type: Array,
24
- required: false,
25
- default: () => [],
26
- },
27
-
28
- // v-model binding
29
- modelValue: {
30
- type: [String, Number, Array, Object],
31
- required: false,
32
- default: null,
33
- },
34
-
35
- // Basic configuration
36
- placeholder: {
37
- type: String,
38
- required: false,
39
- default: "Seleccionar opción...",
40
- },
41
-
42
- // Size configuration
43
- size: {
44
- type: String,
45
- required: false,
46
- default: "sm",
47
- validator: (value) => ["xs", "sm", "md", "lg"].includes(value),
48
- },
49
-
50
- // Style configuration
51
- class: {
52
- type: String,
53
- required: false,
54
- default: "",
55
- },
56
-
57
- severity: {
58
- type: String,
59
- required: false,
60
- default: "primary",
61
- validator: (value) =>
62
- ["primary", "secondary", "success", "danger", "warning", "info"].includes(
63
- value
64
- ),
65
- },
66
-
67
- // Functional properties
68
- multiple: {
69
- type: Boolean,
70
- required: false,
71
- default: false,
72
- },
73
-
74
- searchable: {
75
- type: Boolean,
76
- required: false,
77
- default: false,
78
- },
79
-
80
- loading: {
81
- type: Boolean,
82
- required: false,
83
- default: false,
84
- },
85
-
86
- disabled: {
87
- type: Boolean,
88
- required: false,
89
- default: false,
90
- },
91
-
92
- clearable: {
93
- type: Boolean,
94
- required: false,
95
- default: false,
96
- },
97
-
98
- // Search configuration
99
- searchPlaceholder: {
100
- type: String,
101
- required: false,
102
- default: "Buscar...",
103
- },
104
-
105
- minSearchLength: {
106
- type: Number,
107
- required: false,
108
- default: 0,
109
- },
110
-
111
- searchLimit: {
112
- type: Number,
113
- required: false,
114
- default: 0,
115
- },
116
-
117
- // Multiple selection configuration
118
- tagsMode: {
119
- type: Boolean,
120
- required: false,
121
- default: false,
122
- },
123
-
124
- showCounter: {
125
- type: Boolean,
126
- required: false,
127
- default: false,
128
- },
129
-
130
- maxSelection: {
131
- type: Number,
132
- required: false,
133
- default: 0,
134
- },
135
-
136
- // Form integration
137
- name: {
138
- type: String,
139
- required: false,
140
- default: "",
141
- },
142
-
143
- // Event control
144
- emitFocusEvents: {
145
- type: Boolean,
146
- required: false,
147
- default: false,
148
- },
149
-
150
- // Validation
151
- errorMessage: {
152
- type: String,
153
- required: false,
154
- default: "",
155
- },
156
-
157
- successMessage: {
158
- type: String,
159
- required: false,
160
- default: "",
161
- },
162
-
163
- // Advanced options
164
- allowEmpty: {
165
- type: Boolean,
166
- required: false,
167
- default: true,
168
- },
169
-
170
- closeOnSelect: {
171
- type: Boolean,
172
- required: false,
173
- default: true,
174
- },
175
-
176
- // Campo personalizable para mostrar como texto/label
177
- labelKey: {
178
- type: String,
179
- required: false,
180
- default: "label",
181
- },
182
-
183
- // Campo personalizable para el valor real
184
- valueKey: {
185
- type: String,
186
- required: false,
187
- default: "id",
188
- },
189
-
190
- // Campo personalizable para descripción adicional
191
- descriptionKey: {
192
- type: String,
193
- required: false,
194
- default: "description",
195
- },
4
+ // Endpoint para la carga desde API
5
+ endpoint: { type: String, required: true },
6
+ listFormat: { type: Boolean, default: true },
7
+ perPage: { type: Number, default: 15 },
8
+ initialOptions: { type: Array, default: () => [] },
9
+
10
+ // v-model
11
+ modelValue: { type: [String, Number, Array, Object], default: null },
12
+
13
+ // Form wrapper
14
+ label: { type: String, default: "" },
15
+ placeholder: { type: String, default: "Seleccionar..." },
16
+ hint: { type: String, default: "" },
17
+ error: { type: String, default: "" },
18
+
19
+ // Functional
20
+ multiple: { type: Boolean, default: false },
21
+ searchable: { type: Boolean, default: true },
22
+ disabled: { type: Boolean, default: false },
23
+ clearable: { type: Boolean, default: false },
24
+ searchPlaceholder: { type: String, default: "Buscar..." },
25
+ minSearchLength: { type: Number, default: 0 },
26
+ tagsMode: { type: Boolean, default: false },
27
+ showCounter: { type: Boolean, default: false },
28
+ maxSelection: { type: Number, default: 0 },
29
+ allowEmpty: { type: Boolean, default: true },
30
+ closeOnSelect: { type: Boolean, default: true },
31
+
32
+ // Claves de campo
33
+ labelKey: { type: String, default: "name" },
34
+ valueKey: { type: String, default: "id" },
35
+ descriptionKey: { type: String, default: "description" },
196
36
  });
197
37
 
198
38
  // Emits
@@ -490,13 +330,8 @@ const sizeClasses = computed(() => {
490
330
 
491
331
  // Computed classes for validation states
492
332
  const validationClasses = computed(() => {
493
- if (props.errorMessage) {
494
- return "border-red-500";
495
- }
496
- if (props.successMessage) {
497
- return "border-emerald-500";
498
- }
499
- return "border-gray-200 dark:border-slate-700";
333
+ if (props.error) return "border-red-400 dark:border-red-500";
334
+ return "border-slate-200 dark:border-slate-700";
500
335
  });
501
336
 
502
337
  // Combined select classes
@@ -662,6 +497,12 @@ onMounted(() => {
662
497
  </script>
663
498
 
664
499
  <template>
500
+ <div class="space-y-1.5">
501
+ <!-- Label -->
502
+ <label v-if="label" class="block text-sm font-medium text-slate-700 dark:text-slate-300">
503
+ {{ label }}
504
+ </label>
505
+
665
506
  <div class="relative" ref="selectRef">
666
507
  <!-- Hidden input for form integration -->
667
508
  <input v-if="name" type="hidden" :name="name" :value="multiple
@@ -876,11 +717,10 @@ onMounted(() => {
876
717
  </Transition>
877
718
 
878
719
  <!-- Validation messages -->
879
- <div v-if="errorMessage" class="mt-1 text-sm text-red-600 dark:text-red-400">
880
- {{ errorMessage }}
881
- </div>
882
- <div v-if="successMessage" class="mt-1 text-sm text-green-600 dark:text-green-400">
883
- {{ successMessage }}
884
- </div>
720
+ </div>
721
+
722
+ <!-- Error / Hint -->
723
+ <p v-if="error" class="text-xs text-red-500 dark:text-red-400">{{ error }}</p>
724
+ <p v-else-if="hint" class="text-xs text-slate-400 dark:text-slate-500">{{ hint }}</p>
885
725
  </div>
886
726
  </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@innertia-solutions/ui",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Innertia Solutions — Nuxt UI layer: components and composables",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -22,7 +22,8 @@
22
22
  },
23
23
  "peerDependencies": {
24
24
  "nuxt": ">=3.0.0",
25
- "vue": ">=3.0.0"
25
+ "vue": ">=3.0.0",
26
+ "preline": ">=2.0.0"
26
27
  },
27
28
  "devDependencies": {
28
29
  "nuxt": "^3.16.0",
@@ -0,0 +1,77 @@
1
+ declare global {
2
+ interface Window {
3
+ HSStaticMethods?: { autoInit?: () => void }
4
+ HSSelect?: new (el: HTMLElement) => { destroy?: () => void }
5
+ HSThemeAppearance?: { init?: () => void }
6
+ }
7
+ }
8
+
9
+ export default defineNuxtPlugin(async () => {
10
+ if (!process.client) return
11
+
12
+ try {
13
+ await import('preline')
14
+
15
+ const initPreline = () => {
16
+ try { window.HSStaticMethods?.autoInit?.() } catch (_) {}
17
+ try { window.HSThemeAppearance?.init?.() } catch (_) {}
18
+ }
19
+
20
+ const performMultipleInits = () => {
21
+ initPreline()
22
+ setTimeout(initPreline, 50)
23
+ setTimeout(initPreline, 200)
24
+ setTimeout(initPreline, 500)
25
+ }
26
+
27
+ // Inicialización inicial
28
+ if (document.readyState === 'loading') {
29
+ document.addEventListener('DOMContentLoaded', performMultipleInits)
30
+ } else {
31
+ nextTick(performMultipleInits)
32
+ }
33
+
34
+ const nuxtApp = useNuxtApp()
35
+
36
+ // Re-inicializar en cada cambio de página
37
+ nuxtApp.hooks.hook('page:finish', () => {
38
+ requestAnimationFrame(performMultipleInits)
39
+ })
40
+
41
+ nuxtApp.hooks.hookOnce('app:mounted', () => {
42
+ performMultipleInits()
43
+ })
44
+
45
+ // MutationObserver: reinicializar cuando aparezcan elementos Preline en el DOM
46
+ const observer = new MutationObserver((mutations) => {
47
+ const hasPreline = mutations.some(({ addedNodes }) =>
48
+ Array.from(addedNodes).some((node) => {
49
+ if (node.nodeType !== Node.ELEMENT_NODE) return false
50
+ const el = node as Element
51
+ return (
52
+ el.querySelector?.('[data-hs-overlay],[data-hs-dropdown],[data-hs-select]') ||
53
+ el.hasAttribute?.('data-hs-overlay') ||
54
+ el.hasAttribute?.('data-hs-dropdown') ||
55
+ el.hasAttribute?.('data-hs-select') ||
56
+ (typeof el.className === 'string' && el.className.includes('hs-'))
57
+ )
58
+ })
59
+ )
60
+ if (hasPreline) {
61
+ setTimeout(initPreline, 10)
62
+ setTimeout(initPreline, 100)
63
+ }
64
+ })
65
+
66
+ if (document.readyState === 'loading') {
67
+ document.addEventListener('DOMContentLoaded', () =>
68
+ observer.observe(document.body, { childList: true, subtree: true })
69
+ )
70
+ } else {
71
+ observer.observe(document.body, { childList: true, subtree: true })
72
+ }
73
+
74
+ } catch (e) {
75
+ console.warn('[innertia-ui] Error al cargar Preline:', e)
76
+ }
77
+ })