@verdnatura/vn-front-lib 0.0.2

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/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Copyright (C) 2026 - Verdnatura Levante S.L.
2
+
3
+ This package is free software; you can redistribute it and/or modify
4
+ it under the terms of the GNU General Public License as published by
5
+ the Free Software Foundation, either version 3 of the License, or
6
+ (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful,
9
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ GNU General Public License for more details.
12
+
13
+ You should have received a copy of the GNU General Public License
14
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ On Debian systems, the complete text of the GNU General Public
17
+ License can be found in "/usr/share/common-licenses/GPL-3".
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # vn-front-lib
2
+
3
+ A shared library of Vue 3 components and composables used across the
4
+ **salix-front** and **hedera-web** projects. It is not a standalone app —
5
+ it's built in library mode (Vite `build.lib`) and consumed as a dependency.
6
+
7
+ ## What's inside
8
+
9
+ - `lib/components/` — Vue components (prefixed with `Vn`, e.g. `VnInput`).
10
+ - `lib/composables/` — reusable composables (e.g. `useRequired`, `useValidator`).
11
+ - `lib/stores/` — Pinia stores (e.g. `useValidationsStore`).
12
+ - `lib/config.js` — internal library config, filled via `setConfig()` from the
13
+ `options` passed to `app.use(VnFrontLib, options)` (`axios` is required).
14
+ - `lib/i18n/locale/` — translation files (`en.yml`, `es.yml`).
15
+
16
+ `pinia`, `vue-i18n`, `axios`, `validator` and `quasar` are expected to be
17
+ provided by the consuming project (declared as `peerDependencies`).
18
+
19
+ ## Recommended VS Code extension
20
+
21
+ This project uses [oxlint](https://oxc.rs/docs/guide/usage/linter.html) and
22
+ [oxfmt](https://oxc.rs/docs/guide/usage/formatter.html) for linting and
23
+ formatting. It's advisable to install the
24
+ [Oxc VS Code extension](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode),
25
+ which powers format-on-save and inline lint diagnostics based on the
26
+ `.oxlintrc.json` and `.oxfmtrc.json` config files in this repo.
27
+
28
+ ## Scripts
29
+
30
+ ```bash
31
+ pnpm install
32
+ pnpm run dev # Vite dev server
33
+ pnpm run build # build the library (output to dist/)
34
+ pnpm run lint # oxlint
35
+ pnpm run lint:fix # oxlint --fix
36
+ pnpm run fmt # oxfmt
37
+ pnpm run fmt:check # oxfmt --check
38
+ ```
39
+
40
+ ## Developing locally against another project
41
+
42
+ To test changes locally in another project (e.g. `salix-front` or
43
+ `hedera-web`) without publishing, add this repo as a real pnpm workspace
44
+ member rather than using a plain `link:` dependency, so `vue`/`pinia` get
45
+ deduped into a single instance across both projects:
46
+
47
+ 1. In the consuming project's `pnpm-workspace.yaml`, add this repo as a
48
+ package:
49
+
50
+ ```yaml
51
+ packages:
52
+ - .
53
+ - ../vn-front-lib
54
+ ```
55
+
56
+ 2. In the consuming project's `package.json`, depend on it via the workspace
57
+ protocol, using the real package name:
58
+
59
+ ```json
60
+ "vn-front-lib": "workspace:@verdnatura/vn-front-lib@*",
61
+ ```
62
+
63
+ 3. Enable the `vnSource` resolution condition in the consumer's Vite/Vitest
64
+ config so imports resolve to this repo's `lib/` source instead of the
65
+ `dist/` build:
66
+
67
+ ```js
68
+ resolve: {
69
+ conditions: ['vnSource'],
70
+ },
71
+ ```
72
+
73
+ 4. Reinstall dependencies in the consuming project (`pnpm install`), then run
74
+ `pnpm why vue -r` / `pnpm why pinia -r` to confirm only one version of
75
+ each resolves across the workspace.
@@ -0,0 +1 @@
1
+ const e=require("./useRequired-I81Vonhu.cjs");let t=require("quasar"),n=require("vue"),r=require("vue-i18n");function i(e){let t=e;t.__i18n=t.__i18n||[],t.__i18n.push({locale:``,resource:{en:{inputMin:{t:0,b:{t:2,i:[{t:3,v:`Must be more than `},{t:4,k:`value`}]}},maxLength:{t:0,b:{t:2,i:[{t:3,v:`The value exceeds `},{t:4,k:`value`},{t:3,v:` characters`}]}},inputMax:{t:0,b:{t:2,i:[{t:3,v:`Must be less than `},{t:4,k:`value`}]}}},es:{inputMin:{t:0,b:{t:2,i:[{t:3,v:`Debe ser mayor a `},{t:4,k:`value`}]}},maxLength:{t:0,b:{t:2,i:[{t:3,v:`El valor excede los `},{t:4,k:`value`},{t:3,v:` carácteres`}]}},inputMax:{t:0,b:{t:2,i:[{t:3,v:`Debe ser menor a `},{t:4,k:`value`}]}},"Convert to uppercase":{t:0,b:{t:2,i:[{t:3}],s:`Convertir a mayúsculas`}},Remove:{t:0,b:{t:2,i:[{t:3}],s:`Eliminar`}}}}})}var a={__name:`VnInput`,props:(0,n.mergeModels)({isOutlined:{type:Boolean,default:!1},info:{type:String,default:``},clearable:{type:Boolean,default:!0},emptyToNull:{type:Boolean,default:!0},insertable:{type:Boolean,default:!1},maxlength:{type:Number,default:null},uppercase:{type:Boolean,default:!1}},{modelValue:{default:null},modelModifiers:{}}),emits:(0,n.mergeModels)([`remove`],[`update:modelValue`]),setup(i,{expose:a,emit:o}){let s=(0,n.useAttrs)(),{isRequired:c,requiredFieldRule:l}=e.t(s),{t:u}=(0,r.useI18n)(),d=o,f=i,p=(0,n.useModel)(i,`modelValue`),m=[`number`,`search`,`tel`,`url`,`password`],h=e=>{let t=e;f.emptyToNull&&t===``&&(t=null),p.value=t},g=(0,n.ref)(null),_=(0,n.ref)(!1),v=(0,n.computed)(()=>f.isOutlined?{dense:!0,outlined:!0,rounded:!0}:{}),y=e=>{g.value?.focus(e)},b=e=>{(0,n.nextTick)(()=>{let t=e.target||g.value?.$el?.querySelector(`input`),n=e.target.type||t?.getAttribute(`type`);if(n!==`email`){if(!m.includes(n)){if(t?.setSelectionRange){let e=t.value?.length??0;t.setSelectionRange(e,e)}return}t?.select?.()}})};a({focus:y,vnInputRef:g});let x=(0,n.computed)(()=>[l,...s.rules??[],e=>{let t=f.maxlength;if(t&&+e?.length>t)return u(`maxLength`,{value:t});let{min:n,max:r}=s,i=n===void 0?void 0:Number(n),a=r===void 0?void 0:Number(r);return i!==void 0&&i>=0&&Math.floor(e)<i?u(`inputMin`,{value:i}):a!==void 0&&a>0&&Math.floor(e)>a?u(`inputMax`,{value:a}):!0}]),S=e=>{e.key!==`Backspace`&&f.insertable&&e.key.match(/[0-9]/)&&C(e)},C=e=>{e.preventDefault();let t=e.target,r=t.selectionStart,i=f.maxlength,a=p.value;a||=e.key;let o=e.key;o&&!isNaN(o)&&r<i&&h(a.substring(0,r)+o+a.substring(r+1)),(0,n.nextTick)(()=>{t.setSelectionRange(r+1,r+1)})},w=()=>{p.value=p.value?.toUpperCase()||``};return(0,n.onMounted)(()=>{s.autofocus&&y()}),(e,r)=>{let a=t.QTooltip,o=t.QIcon,l=t.QInput;return(0,n.openBlock)(),(0,n.createElementBlock)(`div`,{onMouseover:r[1]||=e=>_.value=!0,onMouseleave:r[2]||=e=>_.value=!1},[(0,n.createVNode)(l,(0,n.mergeProps)({ref_key:`vnInputRef`,ref:g,"model-value":(0,n.unref)(p),"onUpdate:modelValue":h},{...(0,n.unref)(s),...v.value},{class:{required:(0,n.unref)(c)},onKeydown:S,onFocus:b,clearable:!1,rules:x.value,"lazy-rules":!0,"hide-bottom-space":``,"data-cy":((0,n.unref)(s)[`data-cy`]??(0,n.unref)(s).label)+`_input`}),(0,n.createSlots)({append:(0,n.withCtx)(()=>[(0,n.createVNode)(o,{name:`close`,size:`xs`,class:`cursor-pointer`,style:(0,n.normalizeStyle)({visibility:_.value&&(0,n.unref)(p)&&!(0,n.unref)(s).disabled&&!(0,n.unref)(s).readonly&&f.clearable?`visible`:`hidden`}),onClick:r[0]||=()=>{h(null),g.value?.focus(),d(`remove`)}},{default:(0,n.withCtx)(()=>[(0,n.createVNode)(a,null,{default:(0,n.withCtx)(()=>[(0,n.createTextVNode)((0,n.toDisplayString)((0,n.unref)(u)(`Remove`)),1)]),_:1})]),_:1},8,[`style`]),!(0,n.unref)(s).disabled&&!(0,n.unref)(s).readonly&&f.uppercase?((0,n.openBlock)(),(0,n.createBlock)(o,{key:0,name:`match_case`,size:`xs`,onClick:w,class:`uppercase-icon`},{default:(0,n.withCtx)(()=>[(0,n.createVNode)(a,null,{default:(0,n.withCtx)(()=>[(0,n.createTextVNode)((0,n.toDisplayString)((0,n.unref)(u)(`Convert to uppercase`)),1)]),_:1})]),_:1})):(0,n.createCommentVNode)(``,!0),e.$slots.append&&!(0,n.unref)(s).disabled?(0,n.renderSlot)(e.$slots,`append`,{},void 0,void 0,1):(0,n.createCommentVNode)(``,!0),i.info?((0,n.openBlock)(),(0,n.createBlock)(o,{key:2,name:`info`},{default:(0,n.withCtx)(()=>[(0,n.createVNode)(a,{"max-width":`350px`},{default:(0,n.withCtx)(()=>[(0,n.createTextVNode)((0,n.toDisplayString)(i.info),1)]),_:1})]),_:1})):(0,n.createCommentVNode)(``,!0)]),_:2},[e.$slots.prepend?{name:`prepend`,fn:(0,n.withCtx)(()=>[(0,n.renderSlot)(e.$slots,`prepend`)]),key:`0`}:void 0]),1040,[`model-value`,`class`,`rules`,`data-cy`])],32)}}};typeof i==`function`&&i(a);var o=e.r({VnInput:()=>a});Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return o}});
@@ -0,0 +1,316 @@
1
+ import { t as e } from "./useRequired-BDeZirAF.js";
2
+ import { QIcon as t, QInput as n, QTooltip as r } from "quasar";
3
+ import { computed as i, createBlock as a, createCommentVNode as o, createElementBlock as s, createSlots as c, createTextVNode as l, createVNode as u, mergeModels as d, mergeProps as f, nextTick as p, normalizeStyle as m, onMounted as h, openBlock as g, ref as _, renderSlot as v, toDisplayString as y, unref as b, useAttrs as x, useModel as S, withCtx as C } from "vue";
4
+ import { useI18n as w } from "vue-i18n";
5
+ //#region \0rolldown/runtime.js
6
+ var T = Object.defineProperty, E = (e, t) => {
7
+ let n = {};
8
+ for (var r in e) T(n, r, {
9
+ get: e[r],
10
+ enumerable: !0
11
+ });
12
+ return t || T(n, Symbol.toStringTag, { value: "Module" }), n;
13
+ };
14
+ //#endregion
15
+ //#region lib/components/inputs/VnInput.vue?vue&type=i18n&index=0&lang.yaml
16
+ function D(e) {
17
+ let t = e;
18
+ t.__i18n = t.__i18n || [], t.__i18n.push({
19
+ locale: "",
20
+ resource: {
21
+ en: {
22
+ inputMin: {
23
+ t: 0,
24
+ b: {
25
+ t: 2,
26
+ i: [{
27
+ t: 3,
28
+ v: "Must be more than "
29
+ }, {
30
+ t: 4,
31
+ k: "value"
32
+ }]
33
+ }
34
+ },
35
+ maxLength: {
36
+ t: 0,
37
+ b: {
38
+ t: 2,
39
+ i: [
40
+ {
41
+ t: 3,
42
+ v: "The value exceeds "
43
+ },
44
+ {
45
+ t: 4,
46
+ k: "value"
47
+ },
48
+ {
49
+ t: 3,
50
+ v: " characters"
51
+ }
52
+ ]
53
+ }
54
+ },
55
+ inputMax: {
56
+ t: 0,
57
+ b: {
58
+ t: 2,
59
+ i: [{
60
+ t: 3,
61
+ v: "Must be less than "
62
+ }, {
63
+ t: 4,
64
+ k: "value"
65
+ }]
66
+ }
67
+ }
68
+ },
69
+ es: {
70
+ inputMin: {
71
+ t: 0,
72
+ b: {
73
+ t: 2,
74
+ i: [{
75
+ t: 3,
76
+ v: "Debe ser mayor a "
77
+ }, {
78
+ t: 4,
79
+ k: "value"
80
+ }]
81
+ }
82
+ },
83
+ maxLength: {
84
+ t: 0,
85
+ b: {
86
+ t: 2,
87
+ i: [
88
+ {
89
+ t: 3,
90
+ v: "El valor excede los "
91
+ },
92
+ {
93
+ t: 4,
94
+ k: "value"
95
+ },
96
+ {
97
+ t: 3,
98
+ v: " carácteres"
99
+ }
100
+ ]
101
+ }
102
+ },
103
+ inputMax: {
104
+ t: 0,
105
+ b: {
106
+ t: 2,
107
+ i: [{
108
+ t: 3,
109
+ v: "Debe ser menor a "
110
+ }, {
111
+ t: 4,
112
+ k: "value"
113
+ }]
114
+ }
115
+ },
116
+ "Convert to uppercase": {
117
+ t: 0,
118
+ b: {
119
+ t: 2,
120
+ i: [{ t: 3 }],
121
+ s: "Convertir a mayúsculas"
122
+ }
123
+ },
124
+ Remove: {
125
+ t: 0,
126
+ b: {
127
+ t: 2,
128
+ i: [{ t: 3 }],
129
+ s: "Eliminar"
130
+ }
131
+ }
132
+ }
133
+ }
134
+ });
135
+ }
136
+ //#endregion
137
+ //#region lib/components/inputs/VnInput.vue
138
+ var O = {
139
+ __name: "VnInput",
140
+ props: /*@__PURE__*/ d({
141
+ isOutlined: {
142
+ type: Boolean,
143
+ default: !1
144
+ },
145
+ info: {
146
+ type: String,
147
+ default: ""
148
+ },
149
+ clearable: {
150
+ type: Boolean,
151
+ default: !0
152
+ },
153
+ emptyToNull: {
154
+ type: Boolean,
155
+ default: !0
156
+ },
157
+ insertable: {
158
+ type: Boolean,
159
+ default: !1
160
+ },
161
+ maxlength: {
162
+ type: Number,
163
+ default: null
164
+ },
165
+ uppercase: {
166
+ type: Boolean,
167
+ default: !1
168
+ }
169
+ }, {
170
+ modelValue: { default: null },
171
+ modelModifiers: {}
172
+ }),
173
+ emits: /*@__PURE__*/ d(["remove"], ["update:modelValue"]),
174
+ setup(d, { expose: T, emit: E }) {
175
+ let D = x(), { isRequired: O, requiredFieldRule: k } = e(D), { t: A } = w(), j = E, M = d, N = S(d, "modelValue"), P = [
176
+ "number",
177
+ "search",
178
+ "tel",
179
+ "url",
180
+ "password"
181
+ ], F = (e) => {
182
+ let t = e;
183
+ M.emptyToNull && t === "" && (t = null), N.value = t;
184
+ }, I = _(null), L = _(!1), R = i(() => M.isOutlined ? {
185
+ dense: !0,
186
+ outlined: !0,
187
+ rounded: !0
188
+ } : {}), z = (e) => {
189
+ I.value?.focus(e);
190
+ }, B = (e) => {
191
+ p(() => {
192
+ let t = e.target || I.value?.$el?.querySelector("input"), n = e.target.type || t?.getAttribute("type");
193
+ if (n !== "email") {
194
+ if (!P.includes(n)) {
195
+ if (t?.setSelectionRange) {
196
+ let e = t.value?.length ?? 0;
197
+ t.setSelectionRange(e, e);
198
+ }
199
+ return;
200
+ }
201
+ t?.select?.();
202
+ }
203
+ });
204
+ };
205
+ T({
206
+ focus: z,
207
+ vnInputRef: I
208
+ });
209
+ let V = i(() => [
210
+ k,
211
+ ...D.rules ?? [],
212
+ (e) => {
213
+ let t = M.maxlength;
214
+ if (t && +e?.length > t) return A("maxLength", { value: t });
215
+ let { min: n, max: r } = D, i = n === void 0 ? void 0 : Number(n), a = r === void 0 ? void 0 : Number(r);
216
+ return i !== void 0 && i >= 0 && Math.floor(e) < i ? A("inputMin", { value: i }) : a !== void 0 && a > 0 && Math.floor(e) > a ? A("inputMax", { value: a }) : !0;
217
+ }
218
+ ]), H = (e) => {
219
+ e.key !== "Backspace" && M.insertable && e.key.match(/[0-9]/) && U(e);
220
+ }, U = (e) => {
221
+ e.preventDefault();
222
+ let t = e.target, n = t.selectionStart, r = M.maxlength, i = N.value;
223
+ i ||= e.key;
224
+ let a = e.key;
225
+ a && !isNaN(a) && n < r && F(i.substring(0, n) + a + i.substring(n + 1)), p(() => {
226
+ t.setSelectionRange(n + 1, n + 1);
227
+ });
228
+ }, W = () => {
229
+ N.value = N.value?.toUpperCase() || "";
230
+ };
231
+ return h(() => {
232
+ D.autofocus && z();
233
+ }), (e, i) => {
234
+ let p = r, h = t, _ = n;
235
+ return g(), s("div", {
236
+ onMouseover: i[1] ||= (e) => L.value = !0,
237
+ onMouseleave: i[2] ||= (e) => L.value = !1
238
+ }, [u(_, f({
239
+ ref_key: "vnInputRef",
240
+ ref: I,
241
+ "model-value": b(N),
242
+ "onUpdate:modelValue": F
243
+ }, {
244
+ ...b(D),
245
+ ...R.value
246
+ }, {
247
+ class: { required: b(O) },
248
+ onKeydown: H,
249
+ onFocus: B,
250
+ clearable: !1,
251
+ rules: V.value,
252
+ "lazy-rules": !0,
253
+ "hide-bottom-space": "",
254
+ "data-cy": (b(D)["data-cy"] ?? b(D).label) + "_input"
255
+ }), c({
256
+ append: C(() => [
257
+ u(h, {
258
+ name: "close",
259
+ size: "xs",
260
+ class: "cursor-pointer",
261
+ style: m({ visibility: L.value && b(N) && !b(D).disabled && !b(D).readonly && M.clearable ? "visible" : "hidden" }),
262
+ onClick: i[0] ||= () => {
263
+ F(null), I.value?.focus(), j("remove");
264
+ }
265
+ }, {
266
+ default: C(() => [u(p, null, {
267
+ default: C(() => [l(y(b(A)("Remove")), 1)]),
268
+ _: 1
269
+ })]),
270
+ _: 1
271
+ }, 8, ["style"]),
272
+ !b(D).disabled && !b(D).readonly && M.uppercase ? (g(), a(h, {
273
+ key: 0,
274
+ name: "match_case",
275
+ size: "xs",
276
+ onClick: W,
277
+ class: "uppercase-icon"
278
+ }, {
279
+ default: C(() => [u(p, null, {
280
+ default: C(() => [l(y(b(A)("Convert to uppercase")), 1)]),
281
+ _: 1
282
+ })]),
283
+ _: 1
284
+ })) : o("", !0),
285
+ e.$slots.append && !b(D).disabled ? v(e.$slots, "append", {}, void 0, void 0, 1) : o("", !0),
286
+ d.info ? (g(), a(h, {
287
+ key: 2,
288
+ name: "info"
289
+ }, {
290
+ default: C(() => [u(p, { "max-width": "350px" }, {
291
+ default: C(() => [l(y(d.info), 1)]),
292
+ _: 1
293
+ })]),
294
+ _: 1
295
+ })) : o("", !0)
296
+ ]),
297
+ _: 2
298
+ }, [e.$slots.prepend ? {
299
+ name: "prepend",
300
+ fn: C(() => [v(e.$slots, "prepend")]),
301
+ key: "0"
302
+ } : void 0]), 1040, [
303
+ "model-value",
304
+ "class",
305
+ "rules",
306
+ "data-cy"
307
+ ])], 32);
308
+ };
309
+ }
310
+ };
311
+ typeof D == "function" && D(O);
312
+ //#endregion
313
+ //#region lib/components/index.js
314
+ var k = /* @__PURE__ */ E({ VnInput: () => O });
315
+ //#endregion
316
+ export { O as n, k as t };
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./components-C4qIWgU6.cjs");exports.VnInput=e.n;
@@ -0,0 +1,2 @@
1
+ import { n as e } from "./components-Ct0VDaxz.js";
2
+ export { e as VnInput };
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./useRequired-I81Vonhu.cjs");exports.useRequired=e.t,exports.useValidator=e.n;
@@ -0,0 +1,2 @@
1
+ import { n as e, t } from "./useRequired-BDeZirAF.js";
2
+ export { t as useRequired, e as useValidator };
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./useValidationsStore-CmGCLBNO.cjs");exports.useValidationsStore=e.t;
package/dist/stores.js ADDED
@@ -0,0 +1,2 @@
1
+ import { t as e } from "./useValidationsStore-B5R8zW5O.js";
2
+ export { e as useValidationsStore };
@@ -0,0 +1,58 @@
1
+ import { t as e } from "./useValidationsStore-B5R8zW5O.js";
2
+ import { useI18n as t } from "vue-i18n";
3
+ import n from "validator";
4
+ //#region lib/composables/useValidator.js
5
+ function r() {
6
+ let r = e().validations;
7
+ function i(e) {
8
+ let t = r;
9
+ if (!t || !e) return;
10
+ let n = e.split("."), i = n[0], a = n[1], s = i.charAt(0).toUpperCase() + i.slice(1);
11
+ if (!t[s]) return;
12
+ let c = t[s].validations;
13
+ if (c[a]) return c[a].map((e) => o(e)[e.validation]);
14
+ }
15
+ let { t: a } = t({ useScope: "global" }), o = function(e = {}) {
16
+ return {
17
+ format: (t) => {
18
+ let { allowNull: n, with: r, allowBlank: i } = e, o = a(e.message) || e.message;
19
+ if (!i && t === "" || !n && t === null || !new RegExp(r).test(t)) return o;
20
+ },
21
+ presence: (t) => {
22
+ let r = a("globals.valueCantBeEmpty");
23
+ return e.message && (r = a(e.message) || e.message), !n.isEmpty(t ? String(t) : "") || r;
24
+ },
25
+ required: (e, t) => e ? t === 0 || !!t || a("globals.fieldRequired") : null,
26
+ length: (t) => {
27
+ let r = {
28
+ min: e.min || e.is,
29
+ max: e.max || e.is
30
+ };
31
+ t = String(t), t ||= "";
32
+ let i = `Value should have at most ${r.max} characters`;
33
+ return e.is && (i = `Value should be ${e.is} characters long`), e.min && (i = `Value should have at least ${e.min} characters`), e.min && e.max && (i = `Value should have a length between ${e.min} and ${e.max}`), n.isLength(t, r) || i;
34
+ },
35
+ numericality: (t) => e.int ? n.isInt(t) || "Value should be integer" : n.isNumeric(t) || "Value should be a number",
36
+ min: (e, t) => {
37
+ if (t >= 0 && Math.floor(e) < t) return a("inputMin", { value: t });
38
+ },
39
+ custom: (t) => Function(`return (${e.bindedFunction})`)()(t) || "Invalid value"
40
+ };
41
+ };
42
+ return {
43
+ validate: i,
44
+ validations: o,
45
+ models: r
46
+ };
47
+ }
48
+ //#endregion
49
+ //#region lib/composables/useRequired.js
50
+ function i(e) {
51
+ let { validations: t } = r(), n = typeof e.required == "boolean" ? e.required : Object.keys(e).includes("required");
52
+ return {
53
+ isRequired: n,
54
+ requiredFieldRule: (e) => t().required(n, e)
55
+ };
56
+ }
57
+ //#endregion
58
+ export { r as n, i as t };
@@ -0,0 +1 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const l=require("./useValidationsStore-CmGCLBNO.cjs");let u=require("vue-i18n"),d=require("validator");d=c(d,1);function f(){let e=l.t().validations;function t(t){let n=e;if(!n||!t)return;let i=t.split(`.`),a=i[0],o=i[1],s=a.charAt(0).toUpperCase()+a.slice(1);if(!n[s])return;let c=n[s].validations;if(c[o])return c[o].map(e=>r(e)[e.validation])}let{t:n}=(0,u.useI18n)({useScope:`global`}),r=function(e={}){return{format:t=>{let{allowNull:r,with:i,allowBlank:a}=e,o=n(e.message)||e.message;if(!a&&t===``||!r&&t===null||!new RegExp(i).test(t))return o},presence:t=>{let r=n(`globals.valueCantBeEmpty`);return e.message&&(r=n(e.message)||e.message),!d.default.isEmpty(t?String(t):``)||r},required:(e,t)=>e?t===0||!!t||n(`globals.fieldRequired`):null,length:t=>{let n={min:e.min||e.is,max:e.max||e.is};t=String(t),t||=``;let r=`Value should have at most ${n.max} characters`;return e.is&&(r=`Value should be ${e.is} characters long`),e.min&&(r=`Value should have at least ${e.min} characters`),e.min&&e.max&&(r=`Value should have a length between ${e.min} and ${e.max}`),d.default.isLength(t,n)||r},numericality:t=>e.int?d.default.isInt(t)||`Value should be integer`:d.default.isNumeric(t)||`Value should be a number`,min:(e,t)=>{if(t>=0&&Math.floor(e)<t)return n(`inputMin`,{value:t})},custom:t=>Function(`return (${e.bindedFunction})`)()(t)||`Invalid value`}};return{validate:t,validations:r,models:e}}function p(e){let{validations:t}=f(),n=typeof e.required==`boolean`?e.required:Object.keys(e).includes(`required`);return{isRequired:n,requiredFieldRule:e=>t().required(n,e)}}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return p}});
@@ -0,0 +1,19 @@
1
+ import { defineStore as e } from "pinia";
2
+ //#region lib/config.js
3
+ var t = {}, n = ["axios"], r;
4
+ function i(e = {}) {
5
+ let i = n.filter((t) => !e[t]);
6
+ if (i.length) throw Error(`vn-front-lib: missing required configuration: ${i.join(", ")}. Pass app.use(VnFrontLib, { ${i.join(", ")} })`);
7
+ Object.assign(t, e), r = t.axios;
8
+ }
9
+ //#endregion
10
+ //#region lib/stores/useValidationsStore.js
11
+ var a = e("validationsStore", {
12
+ state: () => ({ validations: null }),
13
+ actions: { async fetchModels() {
14
+ let { data: e } = await r.get("Schemas/modelinfo");
15
+ this.validations = e;
16
+ } }
17
+ });
18
+ //#endregion
19
+ export { r as n, i as r, a as t };
@@ -0,0 +1 @@
1
+ let e=require("pinia");var t={},n=[`axios`],r;function i(e={}){let i=n.filter(t=>!e[t]);if(i.length)throw Error(`vn-front-lib: missing required configuration: ${i.join(`, `)}. Pass app.use(VnFrontLib, { ${i.join(`, `)} })`);Object.assign(t,e),r=t.axios}var a=(0,e.defineStore)(`validationsStore`,{state:()=>({validations:null}),actions:{async fetchModels(){let{data:e}=await r.get(`Schemas/modelinfo`);this.validations=e}}});Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return a}});
@@ -0,0 +1 @@
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("./useRequired-I81Vonhu.cjs"),t=require("./useValidationsStore-CmGCLBNO.cjs"),n=require("./components-C4qIWgU6.cjs");require("./composables.cjs");function r(e,r={}){t.r(r);for(let t in n.t)e.component(t,n.t[t])}var i={install:r};exports.VnInput=n.n,Object.defineProperty(exports,"axios",{enumerable:!0,get:function(){return t.n}}),exports.default=i,exports.setConfig=t.r,exports.useRequired=e.t,exports.useValidator=e.n;
@@ -0,0 +1,2 @@
1
+ .uppercase-icon{cursor:pointer;transition:color .3s,transform .2s}.uppercase-icon:hover{color:#ed9937;transform:scale(1.2)}
2
+ /*$vite$:1*/
@@ -0,0 +1,12 @@
1
+ import { n as e, t } from "./components-Ct0VDaxz.js";
2
+ import { n, r } from "./useValidationsStore-B5R8zW5O.js";
3
+ import { n as i, t as a } from "./useRequired-BDeZirAF.js";
4
+ import "./composables.js";
5
+ //#region lib/main.js
6
+ function o(e, n = {}) {
7
+ r(n);
8
+ for (let n in t) e.component(n, t[n]);
9
+ }
10
+ var s = { install: o };
11
+ //#endregion
12
+ export { e as VnInput, n as axios, s as default, r as setConfig, a as useRequired, i as useValidator };
@@ -0,0 +1 @@
1
+ export { default as VnInput } from './inputs/VnInput.vue';
@@ -0,0 +1,243 @@
1
+ <script setup>
2
+ import { computed, ref, useAttrs, nextTick, onMounted } from 'vue';
3
+ import { useI18n } from 'vue-i18n';
4
+ import { useRequired } from 'lib/composables/useRequired';
5
+
6
+ const $attrs = useAttrs();
7
+ const { isRequired, requiredFieldRule } = useRequired($attrs);
8
+ const { t } = useI18n();
9
+ const emit = defineEmits(['remove']);
10
+
11
+ const $props = defineProps({
12
+ isOutlined: { type: Boolean, default: false },
13
+ info: {
14
+ type: String,
15
+ default: '',
16
+ },
17
+ clearable: {
18
+ type: Boolean,
19
+ default: true,
20
+ },
21
+ emptyToNull: {
22
+ type: Boolean,
23
+ default: true,
24
+ },
25
+ insertable: {
26
+ type: Boolean,
27
+ default: false,
28
+ },
29
+ maxlength: {
30
+ type: Number,
31
+ default: null,
32
+ },
33
+ uppercase: {
34
+ type: Boolean,
35
+ default: false,
36
+ },
37
+ });
38
+
39
+ const model = defineModel({ default: null });
40
+ const value = model;
41
+ const selectableTypes = ['number', 'search', 'tel', 'url', 'password'];
42
+
43
+ const update = (val) => {
44
+ let next = val;
45
+ if ($props.emptyToNull && next === '') next = null;
46
+ value.value = next;
47
+ };
48
+
49
+ const vnInputRef = ref(null);
50
+
51
+ const hover = ref(false);
52
+ const styleAttrs = computed(() => {
53
+ return $props.isOutlined
54
+ ? {
55
+ dense: true,
56
+ outlined: true,
57
+ rounded: true,
58
+ }
59
+ : {};
60
+ });
61
+
62
+ const focus = (evt) => {
63
+ vnInputRef.value?.focus(evt);
64
+ };
65
+
66
+ const onFocus = (evt) => {
67
+ nextTick(() => {
68
+ const inputEl = evt.target || vnInputRef.value?.$el?.querySelector('input');
69
+ const realType = evt.target.type || inputEl?.getAttribute('type');
70
+ if (realType === 'email') {
71
+ return;
72
+ }
73
+ if (!selectableTypes.includes(realType)) {
74
+ if (inputEl?.setSelectionRange) {
75
+ const end = inputEl.value?.length ?? 0;
76
+ inputEl.setSelectionRange(end, end);
77
+ }
78
+ return;
79
+ }
80
+ inputEl?.select?.();
81
+ });
82
+ };
83
+
84
+ defineExpose({
85
+ focus,
86
+ vnInputRef,
87
+ });
88
+
89
+ const mixinRules = computed(() => {
90
+ return [
91
+ requiredFieldRule,
92
+ ...($attrs.rules ?? []),
93
+ (val) => {
94
+ const maxlength = $props.maxlength;
95
+ if (maxlength && +val?.length > maxlength)
96
+ return t('maxLength', { value: maxlength });
97
+ const { min, max } = $attrs;
98
+ const minNum = min !== undefined ? Number(min) : undefined;
99
+ const maxNum = max !== undefined ? Number(max) : undefined;
100
+
101
+ if (minNum !== undefined && minNum >= 0 && Math.floor(val) < minNum)
102
+ return t('inputMin', { value: minNum });
103
+
104
+ if (maxNum !== undefined && maxNum > 0 && Math.floor(val) > maxNum)
105
+ return t('inputMax', { value: maxNum });
106
+
107
+ return true;
108
+ },
109
+ ];
110
+ });
111
+
112
+ const handleKeydown = (e) => {
113
+ if (e.key === 'Backspace') return;
114
+
115
+ if ($props.insertable && e.key.match(/[0-9]/)) {
116
+ handleInsertMode(e);
117
+ }
118
+ };
119
+
120
+ const handleInsertMode = (e) => {
121
+ e.preventDefault();
122
+ const input = e.target;
123
+ const cursorPos = input.selectionStart;
124
+ const maxlength = $props.maxlength;
125
+ let currentValue = value.value;
126
+ if (!currentValue) currentValue = e.key;
127
+ const newValue = e.key;
128
+ if (newValue && !isNaN(newValue) && cursorPos < maxlength) {
129
+ update(
130
+ currentValue.substring(0, cursorPos) +
131
+ newValue +
132
+ currentValue.substring(cursorPos + 1),
133
+ );
134
+ }
135
+
136
+ nextTick(() => {
137
+ input.setSelectionRange(cursorPos + 1, cursorPos + 1);
138
+ });
139
+ };
140
+
141
+ const handleUppercase = () => {
142
+ value.value = value.value?.toUpperCase() || '';
143
+ };
144
+
145
+ onMounted(() => {
146
+ if ($attrs.autofocus) focus();
147
+ });
148
+ </script>
149
+
150
+ <template>
151
+ <div @mouseover="hover = true" @mouseleave="hover = false">
152
+ <QInput
153
+ ref="vnInputRef"
154
+ :model-value="value"
155
+ @update:model-value="update"
156
+ v-bind="{ ...$attrs, ...styleAttrs }"
157
+ :class="{ required: isRequired }"
158
+ @keydown="handleKeydown"
159
+ @focus="onFocus"
160
+ :clearable="false"
161
+ :rules="mixinRules"
162
+ :lazy-rules="true"
163
+ hide-bottom-space
164
+ :data-cy="($attrs['data-cy'] ?? $attrs.label) + '_input'"
165
+ >
166
+ <template #prepend v-if="$slots.prepend">
167
+ <slot name="prepend" />
168
+ </template>
169
+ <template #append>
170
+ <QIcon
171
+ name="close"
172
+ size="xs"
173
+ class="cursor-pointer"
174
+ :style="{
175
+ visibility:
176
+ hover &&
177
+ value &&
178
+ !$attrs.disabled &&
179
+ !$attrs.readonly &&
180
+ $props.clearable
181
+ ? 'visible'
182
+ : 'hidden',
183
+ }"
184
+ @click="
185
+ () => {
186
+ update(null);
187
+ vnInputRef?.focus();
188
+ emit('remove');
189
+ }
190
+ "
191
+ >
192
+ <QTooltip>{{ t('Remove') }}</QTooltip>
193
+ </QIcon>
194
+
195
+ <QIcon
196
+ name="match_case"
197
+ size="xs"
198
+ v-if="!$attrs.disabled && !$attrs.readonly && $props.uppercase"
199
+ @click="handleUppercase"
200
+ class="uppercase-icon"
201
+ >
202
+ <QTooltip>
203
+ {{ t('Convert to uppercase') }}
204
+ </QTooltip>
205
+ </QIcon>
206
+
207
+ <slot name="append" v-if="$slots.append && !$attrs.disabled" />
208
+ <QIcon v-if="info" name="info">
209
+ <QTooltip max-width="350px">
210
+ {{ info }}
211
+ </QTooltip>
212
+ </QIcon>
213
+ </template>
214
+ </QInput>
215
+ </div>
216
+ </template>
217
+
218
+ <style>
219
+ .uppercase-icon {
220
+ transition:
221
+ color 0.3s,
222
+ transform 0.2s;
223
+ cursor: pointer;
224
+ }
225
+
226
+ .uppercase-icon:hover {
227
+ color: #ed9937;
228
+ transform: scale(1.2);
229
+ }
230
+ </style>
231
+ <i18n lang="yaml">
232
+ en:
233
+ inputMin: Must be more than {value}
234
+ maxLength: The value exceeds {value} characters
235
+ inputMax: Must be less than {value}
236
+
237
+ es:
238
+ inputMin: Debe ser mayor a {value}
239
+ maxLength: El valor excede los {value} carácteres
240
+ inputMax: Debe ser menor a {value}
241
+ Convert to uppercase: Convertir a mayúsculas
242
+ Remove: Eliminar
243
+ </i18n>
@@ -0,0 +1,2 @@
1
+ export * from './useRequired';
2
+ export * from './useValidator';
@@ -0,0 +1,15 @@
1
+ import { useValidator } from 'lib/composables/useValidator';
2
+
3
+ export function useRequired($attrs) {
4
+ const { validations } = useValidator();
5
+ const isRequired =
6
+ typeof $attrs['required'] === 'boolean'
7
+ ? $attrs['required']
8
+ : Object.keys($attrs).includes('required');
9
+ const requiredFieldRule = (val) => validations().required(isRequired, val);
10
+
11
+ return {
12
+ isRequired,
13
+ requiredFieldRule,
14
+ };
15
+ }
@@ -0,0 +1,94 @@
1
+ import { useI18n } from 'vue-i18n';
2
+ import validator from 'validator';
3
+ import { useValidationsStore } from 'lib/stores/useValidationsStore';
4
+
5
+ export function useValidator() {
6
+ const models = useValidationsStore().validations;
7
+
8
+ function validate(propertyRule) {
9
+ const modelInfo = models;
10
+ if (!modelInfo || !propertyRule) return;
11
+
12
+ const rule = propertyRule.split('.');
13
+ const model = rule[0];
14
+ const property = rule[1];
15
+ const modelName = model.charAt(0).toUpperCase() + model.slice(1);
16
+
17
+ if (!modelInfo[modelName]) return;
18
+
19
+ const modelValidations = modelInfo[modelName].validations;
20
+
21
+ if (!modelValidations[property]) return;
22
+
23
+ const rules = modelValidations[property].map((validation) => {
24
+ return validations(validation)[validation.validation];
25
+ });
26
+
27
+ return rules;
28
+ }
29
+
30
+ const { t } = useI18n({ useScope: 'global' });
31
+ const validations = function (validation = {}) {
32
+ return {
33
+ format: (value) => {
34
+ const { allowNull, with: format, allowBlank } = validation;
35
+ const message = t(validation.message) || validation.message;
36
+ if (!allowBlank && value === '') return message;
37
+ if (!allowNull && value === null) return message;
38
+
39
+ const isValid = new RegExp(format).test(value);
40
+ if (!isValid) return message;
41
+ },
42
+ presence: (value) => {
43
+ let message = t(`globals.valueCantBeEmpty`);
44
+ if (validation.message)
45
+ message = t(validation.message) || validation.message;
46
+
47
+ return !validator.isEmpty(value ? String(value) : '') || message;
48
+ },
49
+ required: (required, value) => {
50
+ return required
51
+ ? value === 0 || !!value || t('globals.fieldRequired')
52
+ : null;
53
+ },
54
+ length: (value) => {
55
+ const options = {
56
+ min: validation.min || validation.is,
57
+ max: validation.max || validation.is,
58
+ };
59
+
60
+ value = String(value);
61
+
62
+ if (!value) value = '';
63
+
64
+ let message = `Value should have at most ${options.max} characters`;
65
+ if (validation.is)
66
+ message = `Value should be ${validation.is} characters long`;
67
+ if (validation.min)
68
+ message = `Value should have at least ${validation.min} characters`;
69
+ if (validation.min && validation.max)
70
+ message = `Value should have a length between ${validation.min} and ${validation.max}`;
71
+
72
+ return validator.isLength(value, options) || message;
73
+ },
74
+ numericality: (value) => {
75
+ if (validation.int)
76
+ return validator.isInt(value) || 'Value should be integer';
77
+ return validator.isNumeric(value) || 'Value should be a number';
78
+ },
79
+ min: (value, min) => {
80
+ if (min >= 0)
81
+ if (Math.floor(value) < min) return t('inputMin', { value: min });
82
+ },
83
+ custom: (value) =>
84
+ new Function(`return (${validation.bindedFunction})`)()(value) ||
85
+ 'Invalid value',
86
+ };
87
+ };
88
+
89
+ return {
90
+ validate,
91
+ validations,
92
+ models,
93
+ };
94
+ }
package/lib/config.js ADDED
@@ -0,0 +1,16 @@
1
+ // Default library configuration, overridden by whatever is passed to app.use(VnFrontLib, options)
2
+ const config = {};
3
+ const requiredOptions = ['axios'];
4
+ export default config;
5
+ export let axios;
6
+
7
+ export function setConfig(options = {}) {
8
+ const missing = requiredOptions.filter((key) => !options[key]);
9
+ if (missing.length) {
10
+ throw new Error(
11
+ `vn-front-lib: missing required configuration: ${missing.join(', ')}. Pass app.use(VnFrontLib, { ${missing.join(', ')} })`,
12
+ );
13
+ }
14
+ Object.assign(config, options);
15
+ axios = config.axios;
16
+ }
@@ -0,0 +1,4 @@
1
+ globals:
2
+ fieldRequired: This field is required
3
+ valueCantBeEmpty: This field cannot be empty
4
+ inputMin: Must be more than {value}
@@ -0,0 +1,4 @@
1
+ globals:
2
+ fieldRequired: Este campo es obligatorio
3
+ valueCantBeEmpty: Este campo no puede estar vacío
4
+ inputMin: Debe ser mayor a {value}
package/lib/main.js ADDED
@@ -0,0 +1,16 @@
1
+ export * from './components';
2
+ export * from './composables';
3
+ export * from './config';
4
+ import * as components from './components';
5
+ import { setConfig } from './config';
6
+
7
+ function install(app, options = {}) {
8
+ setConfig(options);
9
+ for (const name in components) {
10
+ app.component(name, components[name]);
11
+ }
12
+ }
13
+
14
+ export default {
15
+ install,
16
+ };
@@ -0,0 +1 @@
1
+ export * from './useValidationsStore';
@@ -0,0 +1,14 @@
1
+ import { defineStore } from 'pinia';
2
+ import { axios } from 'lib/config';
3
+
4
+ export const useValidationsStore = defineStore('validationsStore', {
5
+ state: () => ({
6
+ validations: null,
7
+ }),
8
+ actions: {
9
+ async fetchModels() {
10
+ const { data } = await axios.get('Schemas/modelinfo');
11
+ this.validations = data;
12
+ },
13
+ },
14
+ });
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@verdnatura/vn-front-lib",
3
+ "version": "0.0.2",
4
+ "files": [
5
+ "dist",
6
+ "lib"
7
+ ],
8
+ "type": "module",
9
+ "main": "./dist/vn-front-lib.cjs",
10
+ "module": "./dist/vn-front-lib.js",
11
+ "exports": {
12
+ ".": {
13
+ "vnSource": "./lib/main.js",
14
+ "import": "./dist/vn-front-lib.js",
15
+ "require": "./dist/vn-front-lib.cjs"
16
+ },
17
+ "./components": {
18
+ "vnSource": "./lib/components/index.js",
19
+ "import": "./dist/components.js",
20
+ "require": "./dist/components.cjs"
21
+ },
22
+ "./composables": {
23
+ "vnSource": "./lib/composables/index.js",
24
+ "import": "./dist/composables.js",
25
+ "require": "./dist/composables.cjs"
26
+ },
27
+ "./stores": {
28
+ "vnSource": "./lib/stores/index.js",
29
+ "import": "./dist/stores.js",
30
+ "require": "./dist/stores.cjs"
31
+ },
32
+ "./style.css": "./dist/vn-front-lib.css"
33
+ },
34
+ "scripts": {
35
+ "dev": "vite",
36
+ "build": "vite build",
37
+ "lint": "oxlint",
38
+ "lint:fix": "oxlint --fix",
39
+ "fmt": "oxfmt",
40
+ "fmt:check": "oxfmt --check"
41
+ },
42
+ "devDependencies": {
43
+ "@intlify/unplugin-vue-i18n": "^11.2.4",
44
+ "@quasar/vite-plugin": "^1.12.0",
45
+ "@vitejs/plugin-vue": "^6.0.8",
46
+ "@vue/test-utils": "^2.4.11",
47
+ "axios": "^1.18.1",
48
+ "jsdom": "^30.0.1",
49
+ "oxfmt": "^0.61.0",
50
+ "oxlint": "^1.76.0",
51
+ "pinia": "^2.3.1",
52
+ "unplugin-vue-components": "^32.1.0",
53
+ "validator": "^13.15.35",
54
+ "vite": "^8.1.1",
55
+ "vitest": "^4.1.10",
56
+ "vue": "^3.5.13",
57
+ "vue-i18n": "^9.14.4",
58
+ "yaml": "^2.9.0"
59
+ },
60
+ "peerDependencies": {
61
+ "axios": ">=1.0.0",
62
+ "pinia": ">=2.0.0",
63
+ "quasar": ">=2.0.0",
64
+ "validator": ">=13.0.0",
65
+ "vue": ">=3.5.0",
66
+ "vue-i18n": ">=9.0.0"
67
+ }
68
+ }