@teamnovu/kit-vue-forms 0.1.6 → 0.1.8

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/dist/index.js CHANGED
@@ -1,61 +1,64 @@
1
1
  var T = Object.defineProperty;
2
- var G = (t, e, r) => e in t ? T(t, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : t[e] = r;
3
- var F = (t, e, r) => G(t, typeof e != "symbol" ? e + "" : e, r);
4
- import { toValue as L, toRaw as Z, computed as c, unref as d, reactive as D, toRefs as N, shallowReactive as q, toRef as _, onScopeDispose as H, ref as W, watch as w, isRef as k, getCurrentScope as Q, onBeforeUnmount as X, defineComponent as j, renderSlot as O, normalizeProps as z, guardReactiveProps as B, resolveComponent as Y, createBlock as $, openBlock as A, withCtx as C, resolveDynamicComponent as x, mergeProps as ee } from "vue";
2
+ var G = (r, e, t) => e in r ? T(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
3
+ var E = (r, e, t) => G(r, typeof e != "symbol" ? e + "" : e, t);
4
+ import { toValue as L, toRaw as Z, computed as c, unref as d, isRef as D, reactive as P, toRefs as W, shallowReactive as q, toRef as S, onScopeDispose as H, ref as k, watch as w, getCurrentScope as Q, onBeforeUnmount as X, defineComponent as $, renderSlot as j, normalizeProps as z, guardReactiveProps as B, resolveComponent as Y, createBlock as A, openBlock as C, withCtx as M, resolveDynamicComponent as x, mergeProps as ee } from "vue";
5
5
  import { cloneDeep as re } from "lodash-es";
6
6
  import "zod";
7
- function g(t) {
8
- const e = L(t), r = Z(e);
9
- return re(r);
7
+ function g(r) {
8
+ const e = L(r), t = Z(e);
9
+ return re(t);
10
10
  }
11
- function K(t) {
12
- return t === "" ? [] : t.split(/\s*\.\s*/).filter(Boolean);
11
+ function K(r) {
12
+ return r === "" ? [] : r.split(/\s*\.\s*/).filter(Boolean);
13
13
  }
14
- function P(t, e) {
14
+ function _(r, e) {
15
15
  return (Array.isArray(e) ? e : K(e)).reduce(
16
16
  (s, o) => s == null ? void 0 : s[o],
17
- t
17
+ r
18
18
  );
19
19
  }
20
- function te(t, e, r) {
21
- const s = Array.isArray(e) ? e : K(e);
22
- if (s.length === 0)
23
- throw new Error("Path cannot be empty");
24
- const o = s.at(-1), n = s.slice(0, -1).reduce(
25
- (p, h) => p[h],
26
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
- t
28
- );
29
- n[o] = r;
20
+ function te(r, e, t) {
21
+ const s = Array.isArray(e) ? e : K(e), o = s.at(-1);
22
+ if (!o && D(r))
23
+ r.value = t;
24
+ else if (D(r)) {
25
+ const n = s.slice(0, -1).reduce(
26
+ (p, h) => p[h],
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ d(r)
29
+ );
30
+ n[o] = t;
31
+ } else
32
+ return;
30
33
  }
31
- const U = (t, e) => c({
34
+ const U = (r, e) => c({
32
35
  get() {
33
- return P(d(t), d(e));
36
+ return _(d(r), d(e));
34
37
  },
35
- set(r) {
36
- te(d(t), d(e), r);
38
+ set(t) {
39
+ te(r, d(e), t);
37
40
  }
38
41
  });
39
- function R(t, e) {
40
- return !t && !e ? "" : !t && e ? e : !e && t ? t : `${t}.${e}`;
42
+ function R(r, e) {
43
+ return !r && !e ? "" : !r && e ? e : !e && r ? r : `${r}.${e}`;
41
44
  }
42
- function se(t, e) {
45
+ function se(r, e) {
43
46
  if (!e)
44
- return t;
45
- const r = `${e}.`, s = Object.fromEntries(
46
- Object.entries(t.propertyErrors).filter(([o]) => o.startsWith(r)).map(
47
- ([o, n]) => [o.slice(r.length), n]
47
+ return r;
48
+ const t = `${e}.`, s = Object.fromEntries(
49
+ Object.entries(r.propertyErrors).filter(([o]) => o.startsWith(t)).map(
50
+ ([o, n]) => [o.slice(t.length), n]
48
51
  )
49
52
  );
50
53
  return {
51
- general: t.general,
54
+ general: r.general,
52
55
  // Keep general errors
53
56
  propertyErrors: s
54
57
  };
55
58
  }
56
59
  class ae {
57
60
  constructor(e) {
58
- F(this, "rc", 1);
61
+ E(this, "rc", 1);
59
62
  this.drop = e;
60
63
  }
61
64
  inc() {
@@ -65,14 +68,14 @@ class ae {
65
68
  this.rc > 0 && (this.rc -= 1, this.rc === 0 && this.drop && this.drop());
66
69
  }
67
70
  }
68
- function oe(t) {
69
- const e = D({
70
- value: t.value,
71
- path: t.path,
72
- initialValue: c(() => Object.freeze(g(t.initialValue))),
73
- errors: t.errors,
71
+ function oe(r) {
72
+ const e = P({
73
+ value: r.value,
74
+ path: r.path,
75
+ initialValue: c(() => Object.freeze(g(r.initialValue))),
76
+ errors: r.errors,
74
77
  touched: !1
75
- }), r = c(() => JSON.stringify(e.value) !== JSON.stringify(e.initialValue)), s = (a) => {
78
+ }), t = c(() => JSON.stringify(e.value) !== JSON.stringify(e.initialValue)), s = (a) => {
76
79
  e.value = a;
77
80
  }, o = () => {
78
81
  e.touched = !0;
@@ -83,14 +86,14 @@ function oe(t) {
83
86
  e.errors = a;
84
87
  }, V = () => {
85
88
  e.errors = [];
86
- }, i = N(e);
89
+ }, i = W(e);
87
90
  return {
88
91
  data: i.value,
89
92
  path: i.path,
90
93
  initialValue: i.initialValue,
91
94
  errors: i.errors,
92
95
  touched: i.touched,
93
- dirty: r,
96
+ dirty: t,
94
97
  setData: s,
95
98
  onBlur: o,
96
99
  onFocus: n,
@@ -99,30 +102,30 @@ function oe(t) {
99
102
  clearErrors: V
100
103
  };
101
104
  }
102
- function ne(t, e) {
103
- const r = /* @__PURE__ */ new Map(), s = q(/* @__PURE__ */ new Map()), o = (a) => {
105
+ function ne(r, e) {
106
+ const t = /* @__PURE__ */ new Map(), s = q(/* @__PURE__ */ new Map()), o = (a) => {
104
107
  const u = d(a.path);
105
108
  s.set(u, a);
106
109
  }, n = (a) => {
107
110
  s.delete(a);
108
111
  }, p = (a) => {
109
112
  var u;
110
- r.has(a) ? (u = r.get(a)) == null || u.inc() : r.set(a, new ae(() => n(a)));
113
+ t.has(a) ? (u = t.get(a)) == null || u.inc() : t.set(a, new ae(() => n(a)));
111
114
  }, h = (a) => {
112
115
  var u;
113
- r.has(a) && ((u = r.get(a)) == null || u.dec());
116
+ t.has(a) && ((u = t.get(a)) == null || u.dec());
114
117
  }, V = (a) => {
115
118
  if (!s.has(a)) {
116
119
  const v = oe({
117
120
  path: a,
118
- value: U(_(t, "data"), a),
119
- initialValue: c(() => P(t.initialData, a)),
121
+ value: U(S(r, "data"), a),
122
+ initialValue: c(() => _(r.initialData, a)),
120
123
  errors: c({
121
124
  get() {
122
125
  return e.errors.value.propertyErrors[a] || [];
123
126
  },
124
- set(E) {
125
- e.errors.value.propertyErrors[a] = E;
127
+ set(F) {
128
+ e.errors.value.propertyErrors[a] = F;
126
129
  }
127
130
  })
128
131
  });
@@ -141,62 +144,62 @@ function ne(t, e) {
141
144
  defineField: i
142
145
  };
143
146
  }
144
- function ie(t) {
145
- const e = c(() => t.fields.value.some((s) => d(s.dirty))), r = c(() => t.fields.value.some((s) => d(s.touched)));
147
+ function ie(r) {
148
+ const e = c(() => r.fields.value.some((s) => d(s.dirty))), t = c(() => r.fields.value.some((s) => d(s.touched)));
146
149
  return {
147
150
  isDirty: e,
148
- isTouched: r
151
+ isTouched: t
149
152
  };
150
153
  }
151
- function le(t) {
152
- return t.filter(
153
- (e, r, s) => s.indexOf(e) === r
154
+ function le(r) {
155
+ return r.filter(
156
+ (e, t, s) => s.indexOf(e) === t
154
157
  );
155
158
  }
156
- function I(...t) {
157
- return t.slice(1).reduce((e, r) => {
158
- if (!e && !r)
159
+ function I(...r) {
160
+ return r.slice(1).reduce((e, t) => {
161
+ if (!e && !t)
159
162
  return;
160
- const s = ((r == null ? void 0 : r.length) ?? 0) > 0;
161
- if (!e && ((r == null ? void 0 : r.length) ?? 0) > 0)
162
- return r;
163
+ const s = ((t == null ? void 0 : t.length) ?? 0) > 0;
164
+ if (!e && ((t == null ? void 0 : t.length) ?? 0) > 0)
165
+ return t;
163
166
  if (!s)
164
167
  return e;
165
- const o = (e ?? []).concat(r);
168
+ const o = (e ?? []).concat(t);
166
169
  return le(o);
167
- }, t[0]);
170
+ }, r[0]);
168
171
  }
169
- function ce(...t) {
170
- return t.map((r) => Object.keys(r)).flat().reduce((r, s) => {
171
- const o = t.map((n) => n[s]).filter(Boolean);
172
+ function ce(...r) {
173
+ return r.map((t) => Object.keys(t)).flat().reduce((t, s) => {
174
+ const o = r.map((n) => n[s]).filter(Boolean);
172
175
  return {
173
- ...r,
176
+ ...t,
174
177
  [s]: I(...o)
175
178
  };
176
179
  }, {});
177
180
  }
178
- function S(...t) {
179
- if (!t.length)
181
+ function O(...r) {
182
+ if (!r.length)
180
183
  return {
181
184
  general: [],
182
185
  propertyErrors: {}
183
186
  };
184
- const e = t[0];
185
- return t.length === 1 ? e : t.slice(1).reduce(
186
- (r, s) => ({
187
- general: I(r.general, s.general),
188
- propertyErrors: ce(r.propertyErrors ?? {}, s.propertyErrors ?? {})
187
+ const e = r[0];
188
+ return r.length === 1 ? e : r.slice(1).reduce(
189
+ (t, s) => ({
190
+ general: I(t.general, s.general),
191
+ propertyErrors: ce(t.propertyErrors ?? {}, s.propertyErrors ?? {})
189
192
  }),
190
193
  e
191
194
  );
192
195
  }
193
- function M(t) {
196
+ function N(r) {
194
197
  var s;
195
- const e = (((s = t.general) == null ? void 0 : s.length) ?? 0) > 0, r = Object.entries(t.propertyErrors).filter(([, o]) => o == null ? void 0 : o.length).length > 0;
196
- return e || r;
198
+ const e = (((s = r.general) == null ? void 0 : s.length) ?? 0) > 0, t = Object.entries(r.propertyErrors).filter(([, o]) => o == null ? void 0 : o.length).length > 0;
199
+ return e || t;
197
200
  }
198
- function ue(t) {
199
- const e = t.issues.filter((s) => s.path.length === 0).map((s) => s.message), r = t.issues.filter((s) => s.path.length > 0).reduce((s, o) => {
201
+ function ue(r) {
202
+ const e = r.issues.filter((s) => s.path.length === 0).map((s) => s.message), t = r.issues.filter((s) => s.path.length > 0).reduce((s, o) => {
200
203
  const n = o.path.join(".");
201
204
  return {
202
205
  ...s,
@@ -205,7 +208,7 @@ function ue(t) {
205
208
  }, {});
206
209
  return {
207
210
  general: e,
208
- propertyErrors: r
211
+ propertyErrors: t
209
212
  };
210
213
  }
211
214
  const m = {
@@ -222,10 +225,10 @@ class de {
222
225
  async validate(e) {
223
226
  if (!this.schema)
224
227
  return m;
225
- const r = await this.schema.safeParseAsync(e);
226
- if (r.success)
228
+ const t = await this.schema.safeParseAsync(e);
229
+ if (t.success)
227
230
  return m;
228
- const s = ue(r.error);
231
+ const s = ue(t.error);
229
232
  return {
230
233
  isValid: !1,
231
234
  errors: {
@@ -243,13 +246,13 @@ class fe {
243
246
  if (!this.validateFn)
244
247
  return m;
245
248
  try {
246
- const r = await this.validateFn(e);
247
- return r.isValid ? m : r;
248
- } catch (r) {
249
+ const t = await this.validateFn(e);
250
+ return t.isValid ? m : t;
251
+ } catch (t) {
249
252
  return {
250
253
  isValid: !1,
251
254
  errors: {
252
- general: [r.message || "Validation error"],
255
+ general: [t.message || "Validation error"],
253
256
  propertyErrors: {}
254
257
  }
255
258
  };
@@ -257,72 +260,72 @@ class fe {
257
260
  }
258
261
  }
259
262
  class pe {
260
- constructor(e, r) {
261
- F(this, "schemaValidator");
262
- F(this, "functionValidator");
263
- this.schema = e, this.validateFn = r, this.schemaValidator = new de(this.schema), this.functionValidator = new fe(this.validateFn);
263
+ constructor(e, t) {
264
+ E(this, "schemaValidator");
265
+ E(this, "functionValidator");
266
+ this.schema = e, this.validateFn = t, this.schemaValidator = new de(this.schema), this.functionValidator = new fe(this.validateFn);
264
267
  }
265
268
  async validate(e) {
266
- const [r, s] = await Promise.all([
269
+ const [t, s] = await Promise.all([
267
270
  this.schemaValidator.validate(e),
268
271
  this.functionValidator.validate(e)
269
272
  ]);
270
273
  return {
271
- isValid: r.isValid && s.isValid,
272
- errors: S(r.errors, s.errors)
274
+ isValid: t.isValid && s.isValid,
275
+ errors: O(t.errors, s.errors)
273
276
  };
274
277
  }
275
278
  }
276
- function b(t) {
279
+ function b(r) {
277
280
  return c(() => new pe(
278
- d(t.schema),
279
- d(t.validateFn)
281
+ d(r.schema),
282
+ d(r.validateFn)
280
283
  ));
281
284
  }
282
- function he(t, e) {
283
- const r = D({
284
- validators: W([b(e)]),
285
+ function he(r, e) {
286
+ const t = P({
287
+ validators: k([b(e)]),
285
288
  isValidated: !1,
286
289
  errors: d(e.errors) ?? m.errors
287
290
  }), s = (i = m.errors) => {
288
- r.errors = S(d(e.errors) ?? m.errors, i);
291
+ t.errors = O(d(e.errors) ?? m.errors, i);
289
292
  };
290
293
  w(() => d(e.errors), async () => {
291
- if (r.isValidated) {
294
+ if (t.isValidated) {
292
295
  const i = await n();
293
296
  s(i.errors);
294
297
  } else
295
298
  s();
296
299
  }, { immediate: !0 }), w(
297
- [() => r.validators],
300
+ [() => t.validators],
298
301
  async (i) => {
299
- if (r.isValidated)
302
+ if (t.isValidated)
300
303
  if (i) {
301
304
  const a = await n();
302
- r.errors = a.errors;
305
+ t.errors = a.errors;
303
306
  } else
304
- r.errors = m.errors;
307
+ t.errors = m.errors;
305
308
  },
306
309
  { immediate: !0 }
307
- ), w(() => t.data, () => {
308
- r.isValidated && p();
310
+ ), w(() => r.data, () => {
311
+ t.isValidated && p();
309
312
  });
310
313
  const o = (i) => {
311
- const a = k(i) ? i : b(i);
312
- return r.validators.push(a), Q() && X(() => {
313
- r.validators = r.validators.filter(
314
+ const a = D(i) ? i : b(i);
315
+ return t.validators.push(a), Q() && X(() => {
316
+ t.validators = t.validators.filter(
314
317
  (u) => u !== a
315
318
  );
316
319
  }), a;
317
320
  };
318
321
  async function n() {
319
322
  const i = await Promise.all(
320
- r.validators.filter((v) => d(v) !== void 0).map((v) => d(v).validate(t.data))
323
+ t.validators.filter((v) => d(v) !== void 0).map((v) => d(v).validate(r.data))
321
324
  ), a = i.every((v) => v.isValid);
322
325
  let { errors: u } = m;
323
326
  if (!a) {
324
- const v = i.map((E) => E.errors);
325
- u = S(...v);
327
+ const v = i.map((F) => F.errors);
328
+ u = O(...v);
326
329
  }
327
330
  return {
328
331
  errors: u,
@@ -331,15 +334,15 @@ function he(t, e) {
331
334
  }
332
335
  const p = async () => {
333
336
  const i = await n();
334
- return s(i.errors), r.isValidated = !0, {
335
- isValid: !M(i.errors),
336
- errors: r.errors
337
+ return s(i.errors), t.isValidated = !0, {
338
+ isValid: !N(i.errors),
339
+ errors: t.errors
337
340
  };
338
- }, h = c(() => !M(r.errors)), V = () => {
339
- r.isValidated = !1, r.errors = d(e.errors) ?? m.errors;
341
+ }, h = c(() => !N(t.errors)), V = () => {
342
+ t.isValidated = !1, t.errors = d(e.errors) ?? m.errors;
340
343
  };
341
344
  return {
342
- ...N(r),
345
+ ...W(t),
343
346
  validateForm: p,
344
347
  defineValidator: o,
345
348
  isValid: h,
@@ -347,14 +350,14 @@ function he(t, e) {
347
350
  };
348
351
  }
349
352
  class ve {
350
- constructor(e, r) {
351
- this.path = e, this.validator = r;
353
+ constructor(e, t) {
354
+ this.path = e, this.validator = t;
352
355
  }
353
356
  async validate(e) {
354
- const r = P(e, this.path);
357
+ const t = _(e, this.path);
355
358
  if (!this.validator)
356
359
  return m;
357
- const s = await this.validator.validate(r);
360
+ const s = await this.validator.validate(t);
358
361
  return {
359
362
  isValid: s.isValid,
360
363
  errors: {
@@ -369,29 +372,29 @@ class ve {
369
372
  };
370
373
  }
371
374
  }
372
- function me(t, e, r) {
373
- const s = U(t.data, e), o = c(() => P(t.initialData.value, e)), n = (l) => ({
375
+ function me(r, e, t) {
376
+ const s = U(r.data, e), o = c(() => _(r.initialData.value, e)), n = (l) => ({
374
377
  ...l,
375
378
  path: c(() => d(l.path).replace(e + ".", "")),
376
379
  setData: (f) => {
377
380
  l.setData(f);
378
381
  }
379
382
  }), p = (l) => {
380
- const f = R(e, l), y = t.getField(f);
383
+ const f = R(e, l), y = r.getField(f);
381
384
  return y ? n(y) : {};
382
385
  }, h = (l) => {
383
- const f = R(e, l.path), y = t.defineField({
386
+ const f = R(e, l.path), y = r.defineField({
384
387
  ...l,
385
388
  path: f
386
389
  });
387
390
  return n(y);
388
- }, V = c(() => t.fields.value.filter((l) => {
391
+ }, V = c(() => r.fields.value.filter((l) => {
389
392
  const f = l.path.value;
390
393
  return f.startsWith(e + ".") || f === e;
391
- }).map((l) => n(l))), i = () => t.fields.value.filter((l) => {
394
+ }).map((l) => n(l))), i = () => r.fields.value.filter((l) => {
392
395
  const f = l.path.value;
393
396
  return f.startsWith(e + ".") || f === e;
394
- }), a = c(() => i().some((l) => l.dirty.value)), u = c(() => i().some((l) => l.touched.value)), v = c(() => t.isValid.value), E = c(() => t.isValidated.value), J = c(() => se(d(t.errors), e));
397
+ }), a = c(() => i().some((l) => l.dirty.value)), u = c(() => i().some((l) => l.touched.value)), v = c(() => r.isValid.value), F = c(() => r.isValidated.value), J = c(() => se(d(r.errors), e));
395
398
  return {
396
399
  data: s,
397
400
  fields: V,
@@ -401,35 +404,35 @@ function me(t, e, r) {
401
404
  isDirty: a,
402
405
  isTouched: u,
403
406
  isValid: v,
404
- isValidated: E,
407
+ isValidated: F,
405
408
  errors: J,
406
409
  defineValidator: (l) => {
407
- const f = k(l) ? l : b(l), y = c(
410
+ const f = D(l) ? l : b(l), y = c(
408
411
  () => new ve(e, d(f))
409
412
  );
410
- return t.defineValidator(y), f;
413
+ return r.defineValidator(y), f;
411
414
  },
412
415
  reset: () => i().forEach((l) => l.reset()),
413
- validateForm: () => t.validateForm(),
416
+ validateForm: () => r.validateForm(),
414
417
  getSubForm: (l, f) => {
415
418
  const y = R(e, l);
416
- return t.getSubForm(
419
+ return r.getSubForm(
417
420
  y,
418
421
  f
419
422
  );
420
423
  }
421
424
  };
422
425
  }
423
- function Pe(t) {
424
- const e = c(() => Object.freeze(g(t.initialData))), r = W(g(e)), s = D({
426
+ function Pe(r) {
427
+ const e = c(() => Object.freeze(g(r.initialData))), t = k(g(e)), s = P({
425
428
  initialData: e,
426
- data: r
429
+ data: t
427
430
  });
428
431
  w(e, (a) => {
429
432
  s.data = g(a);
430
433
  });
431
- const o = he(s, t), n = ne(s, o), p = ie(n), h = () => {
432
- r.value = g(e), o.reset(), n.fields.value.forEach(
434
+ const o = he(s, r), n = ne(s, o), p = ie(n), h = () => {
435
+ t.value = g(e), o.reset(), n.fields.value.forEach(
433
436
  (a) => a.reset()
434
437
  );
435
438
  };
@@ -442,12 +445,12 @@ function Pe(t) {
442
445
  ...p,
443
446
  reset: h,
444
447
  getSubForm: V,
445
- initialData: _(s, "initialData"),
446
- data: _(s, "data")
448
+ initialData: S(s, "initialData"),
449
+ data: S(s, "data")
447
450
  };
448
451
  return i;
449
452
  }
450
- const _e = /* @__PURE__ */ j({
453
+ const _e = /* @__PURE__ */ $({
451
454
  __name: "Field",
452
455
  props: {
453
456
  form: {},
@@ -456,13 +459,13 @@ const _e = /* @__PURE__ */ j({
456
459
  path: {},
457
460
  errors: {}
458
461
  },
459
- setup(t) {
460
- const e = t, r = e.form.defineField({
462
+ setup(r) {
463
+ const e = r, t = e.form.defineField({
461
464
  path: e.path
462
- }), s = D(r);
463
- return (o, n) => O(o.$slots, "default", z(B(s)));
465
+ }), s = P(t);
466
+ return (o, n) => j(o.$slots, "default", z(B(s)));
464
467
  }
465
- }), Se = /* @__PURE__ */ j({
468
+ }), Se = /* @__PURE__ */ $({
466
469
  inheritAttrs: !1,
467
470
  __name: "FormFieldWrapper",
468
471
  props: {
@@ -471,22 +474,22 @@ const _e = /* @__PURE__ */ j({
471
474
  form: {},
472
475
  path: {}
473
476
  },
474
- setup(t) {
475
- return (e, r) => {
477
+ setup(r) {
478
+ return (e, t) => {
476
479
  const s = Y("Field");
477
- return A(), $(s, {
480
+ return C(), A(s, {
478
481
  form: e.form,
479
482
  path: e.path
480
483
  }, {
481
- default: C(({ errors: o, data: n, setData: p }) => [
482
- (A(), $(x(e.component), ee({ ...e.componentProps, ...e.$attrs }, {
484
+ default: M(({ errors: o, data: n, setData: p }) => [
485
+ (C(), A(x(e.component), ee({ ...e.componentProps, ...e.$attrs }, {
483
486
  "model-value": n,
484
487
  errors: o,
485
488
  name: e.path,
486
489
  "onUpdate:modelValue": p
487
490
  }), {
488
- default: C(() => [
489
- O(e.$slots, "default")
491
+ default: M(() => [
492
+ j(e.$slots, "default")
490
493
  ]),
491
494
  _: 2
492
495
  }, 1040, ["model-value", "errors", "name", "onUpdate:modelValue"]))
@@ -495,20 +498,20 @@ const _e = /* @__PURE__ */ j({
495
498
  }, 8, ["form", "path"]);
496
499
  };
497
500
  }
498
- }), be = /* @__PURE__ */ j({
501
+ }), Oe = /* @__PURE__ */ $({
499
502
  __name: "FormPart",
500
503
  props: {
501
504
  form: {},
502
505
  path: {}
503
506
  },
504
- setup(t) {
505
- const e = t, r = c(() => e.form.getSubForm(e.path));
506
- return (s, o) => O(s.$slots, "default", z(B({ subform: r.value })));
507
+ setup(r) {
508
+ const e = r, t = c(() => e.form.getSubForm(e.path));
509
+ return (s, o) => j(s.$slots, "default", z(B({ subform: t.value })));
507
510
  }
508
511
  });
509
512
  export {
510
513
  _e as Field,
511
514
  Se as FormFieldWrapper,
512
- be as FormPart,
515
+ Oe as FormPart,
513
516
  Pe as useForm
514
517
  };
@@ -3,7 +3,7 @@ import { Paths, PickProps, SplitPath } from '../types/util';
3
3
  import { ErrorBag } from '../types/validation';
4
4
  export declare function splitPath(path: string): string[];
5
5
  export declare function getNestedValue<T, K extends Paths<T>>(obj: T, path: K | SplitPath<K>): PickProps<T, K>;
6
- export declare function setNestedValue<T, K extends Paths<T>>(obj: T, path: K | SplitPath<K>, value: PickProps<T, K>): void;
6
+ export declare function setNestedValue<T, K extends Paths<T>>(obj: MaybeRef<T>, path: K | SplitPath<K>, value: PickProps<T, K>): void;
7
7
  export declare const getLens: <T, K extends Paths<T>>(data: MaybeRef<T>, key: MaybeRef<K | SplitPath<K>>) => WritableComputedRef<PickProps<T, K>, PickProps<T, K>>;
8
8
  type JoinPath<Base extends string, Sub extends string> = `${Base}${Base extends '' ? '' : Sub extends '' ? '' : '.'}${Sub}`;
9
9
  export declare function joinPath<Base extends string, Sub extends string>(basePath: Base, subPath: Sub): JoinPath<Base, Sub>;
@@ -0,0 +1,59 @@
1
+ # FormInput Example
2
+
3
+ To define a form input component you need a plain input component that has at least the following props and emits:
4
+ ```typescript
5
+ interface Props {
6
+ modelValue: T
7
+ errors?: string[] | undefined // if the input should display errors
8
+ }
9
+ interface Emits {
10
+ 'update:modelValue': [T]
11
+ }
12
+ ```
13
+ Then we can easily use the `FormFieldWrapper` component from this package.
14
+
15
+ In the example below we use a text field `TextField.vue`.
16
+
17
+ Example:
18
+ ```vue
19
+ <!-- FormTextInput.vue -->
20
+ <template>
21
+ <FormFieldWrapper
22
+ :component="TextField"
23
+ :component-props="$props"
24
+ :path="path"
25
+ :form="form"
26
+ />
27
+ </template>
28
+
29
+ <script setup lang="ts" generic="TData extends object, TPath extends Paths<TData>">
30
+ import type { TextFieldProps } from '#components/utils/form/plainInput/TextField.vue';
31
+ import TextField from '#components/utils/form/plainInput/TextField.vue';
32
+ import { type Paths, FormFieldWrapper } from '@teamnovu/kit-vue-forms';
33
+ import type {
34
+ ExcludedFieldProps,
35
+ FormComponentProps,
36
+ } from '#types/form/FormComponentProps';
37
+
38
+ export type Props<TData extends object, TPath extends Paths<TData>> =
39
+ FormComponentProps<TData, TPath, TextFieldProps['modelValue']> & Omit<TextFieldProps, ExcludedFieldProps>;
40
+
41
+ defineProps<Props<TData, TPath>>();
42
+ </script>
43
+ ```
44
+ Note the usage of the generic types `TData` and `TPath` to make the component fully type-safe. With this, the `path` prop
45
+ will only allow valid paths of the form data. Moreover, it will throw a type error if the property at `path` of the `form`
46
+ has the wrong type. This is ensured by using the `FormComponentProps` type above.
47
+
48
+ The usage of such a form input component is as follows (assuming the input should handle the "firstName" property of the form data):
49
+ ```vue
50
+ <FormTextInput
51
+ :form="form"
52
+ path="firstName"
53
+ />
54
+ ```
55
+ Here, `form` is the Form component that was created with [`useForm`](index.md#useform). All additional props are passed
56
+ to the underlying plain input component, e.g. `label`, `placeholder`, etc.
57
+
58
+ It is recommended to use this pattern of a styled "plain input" that works with v-model and a "form input" to work with form
59
+ and path as props for all your form inputs. Like that, you can easily use the plain component outside of forms as well.
@@ -0,0 +1,212 @@
1
+ # Example
2
+
3
+ The following example shows how to use this library for a more complicated form with nested objects and arrays.
4
+ The data structure of the form is as follows:
5
+ ```typescript
6
+ {
7
+ person: {
8
+ firstName: string,
9
+ lastName: string | undefined,
10
+ address: {
11
+ street: string,
12
+ city: string,
13
+ },
14
+ hobbies: string[]
15
+ }
16
+ }
17
+ ```
18
+
19
+ We can create a form like this:
20
+
21
+ ```vue
22
+ <template>
23
+ <form @submit.prevent="submit">
24
+ <!-- Simple form inputs -->
25
+ <!-- the "label" prop is passed through the FormTextField to the underlying TextField -->
26
+ <FormTextField
27
+ :form="form"
28
+ path="person.firstName"
29
+ label="First Name"
30
+ />
31
+ <FormTextField
32
+ :form="form"
33
+ path="person.lastName"
34
+ label="Last Name"
35
+ />
36
+
37
+ <!-- Oh oh, my path was wrong here, the form type does not have a property "person.middleName" -->
38
+ <!-- I get a typescript error that this path is not allowed
39
+ <FormTextField
40
+ :form="form"
41
+ path="person.middleName"
42
+ />
43
+ -->
44
+ <!-- Oh oh, I used a Text component (which allows "string | number | null | undefined"), but the property at the path is a boolean -->
45
+ <!-- I get a typescript error that this path and form combination is not allowed
46
+ <FormTextField
47
+ :form="form"
48
+ path="person.isMale"
49
+ />
50
+ -->
51
+
52
+ <!-- Nested forms in subcomponents -->
53
+ <!-- My FormAddressField handles forms of type { street: string, city: string } -->
54
+ <!-- so we can make a subform for the address property of our main form -->
55
+ <FormPart :form="form" path="person.address" #="{ subform }">
56
+ <FormAddressField :form="subform" />
57
+ </FormPart>
58
+
59
+ <div v-for="(_, index) in unref(form.data).person.hobbies" :key="index" class="ml-4">
60
+ <!-- Note the path format of an array item. You just use the index number in the dot-path -->
61
+ <FormTextField
62
+ :form="form"
63
+ :path="`person.hobbies.${index}`"
64
+ />
65
+ </div>
66
+
67
+ <button type="button" @click="toggleComment">
68
+ Toggle Comment
69
+ </button>
70
+
71
+ <!-- If you need other data, use the data ref of the form object; don't use getField here -->
72
+ <!-- Alternatively, in this case you could use the variable "isCommentEnabled" that was defined in the script setup -->
73
+ <FormTextField
74
+ v-if="unref(form.data).commentEnabled"
75
+ :form="form"
76
+ path="comment"
77
+ />
78
+
79
+ <!-- This is a one-off special thing; I don't really want to make a FormInput out of it -->
80
+ <!-- I can use the Field component, or I could use form.getField in the script setup -->
81
+ <Field v-slot="{ data, setData, errors }" path="person.parent" :form="form">
82
+ <div>
83
+ Father: {{ data.fatherName }}
84
+ </div>
85
+ <div>
86
+ Mother: {{ data.motherName }}
87
+ </div>
88
+ <!-- the "errors" will be an array of strings of the errors corresponding to the property with the given path (here "person.parent") -->
89
+ <InputError :errors="errors" />
90
+ <button type="button" @click="() => setData({ fatherName: 'New Father', motherName: 'New Mother' })">
91
+ Change Parent Names
92
+ </button>
93
+ </Field>
94
+
95
+ <button type="submit">
96
+ Submit Form
97
+ </button>
98
+
99
+ </form>
100
+ </template>
101
+
102
+ <script setup lang="ts">
103
+ import { Field, useForm, FormPart } from '@teamnovu/kit-vue-forms';
104
+ import { z } from 'zod';
105
+ import { unref } from 'vue';
106
+ import FormTextField from '#components/utils/form/formInput/FormTextField.vue';
107
+ import FormAddressField from '#/FormAddressField.vue';
108
+ import InputError from '#components/utils/form/InputError.vue';
109
+
110
+ const form = useForm<{ // this type might be inferred automatically from the initialData; but here, it would not work, because lastName is not defined in the initialData
111
+ person: {
112
+ firstName: string
113
+ lastName?: string | undefined
114
+ address: {
115
+ street: string
116
+ city: string
117
+ }
118
+ hobbies: string[]
119
+ parent: {
120
+ fatherName: string
121
+ motherName: string
122
+ }
123
+ isMale: boolean
124
+ }
125
+ commentEnabled?: boolean
126
+ comment: string
127
+ }>({
128
+ initialData: {
129
+ person: {
130
+ firstName: '',
131
+ // I don't need to define an initial value for lastName here, because it's optional; the form will still handle it correctly
132
+ address: {
133
+ street: '',
134
+ city: '',
135
+ },
136
+ hobbies: ['cooking', 'coding'],
137
+ parent: {
138
+ fatherName: 'Hans Müller',
139
+ motherName: 'Hannah Schmidt',
140
+ },
141
+ isMale: false
142
+ },
143
+ commentEnabled: false,
144
+ comment: '',
145
+ },
146
+ // optional zod schema for validation; might also just define part of the schema if other properties should be ignored
147
+ schema: z.object({
148
+ person: z.object({
149
+ hobbies: z.array(z.string().min(1, 'Hobby cannot be empty')),
150
+ }),
151
+ }),
152
+ });
153
+
154
+ const sendToBackend = async (data) => {
155
+ // send data object to backend
156
+ };
157
+
158
+ const submit = async () => {
159
+ // validate the form
160
+ // if the zod schema is not satisfied, isValid will be false and the errors will be set on the corresponding fields
161
+ // if your TextInput component handles errors correctly, it should be directly visible
162
+ const { isValid } = await form.validateForm();
163
+
164
+ // only submit if the form is valid
165
+ if (isValid) {
166
+ await sendToBackend(unref(form.data));
167
+ }
168
+ };
169
+
170
+ // To programmatically change form values, use form.getField to get a ref and a setter function
171
+ // never modify form.data directly
172
+ const { data: isCommentEnabled, setData: setCommentEnabled } = form.getField('commentEnabled');
173
+ const toggleComment = () => {
174
+ setCommentEnabled(!unref(isCommentEnabled));
175
+ };
176
+ </script>
177
+
178
+ ```
179
+
180
+ An example of the `FormAddressField` that was used above would be:
181
+ ```vue
182
+ <!-- FormAddressField.vue -->
183
+ <template>
184
+ <div>
185
+ <FormTextField
186
+ :form="form"
187
+ path="street"
188
+ label="Street Address"
189
+ />
190
+
191
+ <FormTextField
192
+ :form="form"
193
+ path="city"
194
+ label="City"
195
+ />
196
+ </div>
197
+ </template>
198
+
199
+ <script setup lang="ts">
200
+ import type { Form } from '@teamnovu/kit-vue-forms';
201
+ import FormTextField from '#components/utils/form/formInput/FormTextField.vue';
202
+
203
+ interface Props {
204
+ form: Form<{
205
+ street: string
206
+ city: string
207
+ }>
208
+ }
209
+
210
+ defineProps<Props>();
211
+ </script>
212
+ ```
package/docs/index.md ADDED
@@ -0,0 +1,26 @@
1
+ # @teamnovu/kit-vue-forms
2
+
3
+ A library for data and error handling of forms, including validation with zod schemas.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @teamnovu/kit-vue-forms
9
+ ```
10
+
11
+ ## Purpose
12
+ This package provides composables and some data components to simplify the data management of forms and associated errors.
13
+ It was designed as a replacement of Vee-Validate with more flexibility and full type safety. We don't use a provided and injected form object,
14
+ but pass it down as a prop to guarantee type safety.
15
+
16
+ ## Usage
17
+
18
+ 1. Wrap your plain input components into `FormFieldWrapper`, see [FormInput Example](./FormInput-example.md).
19
+ 2. Inside your parent component where you want to use a form, use the composable `useForm` to create a form object.
20
+ This object contains the form data, errors, and methods to define
21
+ fields, validate the form, reset it, and create subforms for nested objects or arrays, see [here](./reference#composable-useform).
22
+ 3. Pass this form object to any form input component together with a path to the property of the form data that this input
23
+ should manage. You can also make subforms, pass the form down to child components, etc. See the examples for inspiration.
24
+ 4. See Reference documentation for all types and methods: [here](./reference.md).
25
+
26
+ An example of the most common usages can be found [here](./example.md).
package/docs/info.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "name": "📋 Vue Forms"
3
+ }
@@ -0,0 +1,136 @@
1
+ # Reference
2
+ ## Composable `useForm`
3
+ This is the main composable which creates the form. It has the following signature:
4
+ ```typescript
5
+ function useForm<T extends object>(options: {
6
+ // the initial data of the form
7
+ // reactive changes to this object will propagate to the form data
8
+ initialData: MaybeRefOrGetter<T>
9
+ // an ErrorBag object or ref of an ErrorBag object with external errors
10
+ // this is used e.g. for server side validation errors
11
+ // these errors will be merged with the internal errors of the form based on validationFn below and/or the zod schema
12
+ errors?: MaybeRef<ErrorBag | undefined>
13
+ // a zod schema of the form data
14
+ // this is validated based on the validationStrategy or by manually triggering validateForm on the form object
15
+ schema?: MaybeRef<z.ZodType>
16
+ // a custom validation function which is called with the current form data
17
+ // this is additional to the schema for custom validations
18
+ validateFn?: MaybeRef<ValidationFunction<T>>
19
+ // if the form data of a property should be reset or kept if all fields corresponding to this property are unmounted
20
+ // !! currently not implemented !!
21
+ keepValuesOnUnmount?: MaybeRef<boolean>
22
+ // when validation should be done (on touch, pre submit, etc.)
23
+ // !! currently not implemented !!
24
+ validationStrategy?: MaybeRef<ValidationStrategy>
25
+ }): Form<T>
26
+ ```
27
+ This composable returns a `Form<T>` object.
28
+
29
+ ## Type `Form<T>`
30
+ Here, `T` is the type of the form data, which is inferred from the `initialData` property of the options object passed to `useForm`.
31
+ You can also explicitly provide a type argument to `useForm<T>` if needed (useful if the initial data is an empty object `{}`).
32
+
33
+ This object has the following properties and methods:
34
+ ```typescript
35
+ interface Form<T extends object> {
36
+ // the current working data of the form
37
+ // this might differ from initialData if the user has changed some values
38
+ data: Ref<T>
39
+ // the initial data of the form as passed to useForm
40
+ initialData: Readonly<Ref<T>>
41
+
42
+ // all fields of the form that are currently managed
43
+ fields: Ref<FieldsTuple<T>>
44
+
45
+ // defines a field such that it will be managed by the form
46
+ // without this, the form will not track changes, touched state or validation for this field
47
+ // use this like a composable
48
+ defineField: <P extends Paths<T>>(options: DefineFieldOptions<PickProps<T, P>, P>) => FormField<PickProps<T, P>, P>
49
+ // gets an already defined field by its path
50
+ // note: if the field was not yet defined, it will be defined automatically
51
+ // the output is a reactive object containing the data, errors and states as well as some methods
52
+ // use this like a composable
53
+ getField: <P extends Paths<T>>(path: P) => FormField<PickProps<T, P>, P>
54
+
55
+ // true if the form has been modified from its initial state
56
+ isDirty: Ref<boolean>
57
+ // true if any field of the form has been touched (i.e. onBlur was called on any field)
58
+ isTouched: Ref<boolean>
59
+ // true if the form data is valid based on the schema and/or the validateFn
60
+ isValid: Ref<boolean>
61
+ // true if the form has been validated at least once
62
+ isValidated: Ref<boolean>
63
+ // the ErrorBag object containing all errors of the form
64
+ // this is a merge of internal errors based on schema/validateFn and external errors passed to useForm
65
+ // the errors are structured based on the paths of the form fields
66
+ errors: Ref<ErrorBag>
67
+
68
+ // defines a custom validator for the form
69
+ // with this, a subcomponent might add a validator function and/or schema to the form
70
+ // without needing access to the initial useForm call
71
+ defineValidator: <TData extends T>(options: ValidatorOptions<TData> | Ref<Validator<TData>>) => Ref<Validator<TData> | undefined>
72
+
73
+ // resets the form data and errors, as well as the dirty, touched etc. state of all fields
74
+ reset: () => void
75
+ // manually triggers validation of the form data based on schema and/or validateFn
76
+ validateForm: () => Promise<ValidationResult>
77
+
78
+ // creates a subform for a nested object or array property of the form data
79
+ // the subform is again a Form<T> object, where T is the type of the nested property
80
+ // it will contain all errors that have paths starting with the path of the subform
81
+ // changes to the subform data will propagate to the main form data and vice versa
82
+ getSubForm: <P extends EntityPaths<T>>(
83
+ path: P,
84
+ options?: SubformOptions<PickEntity<T, P>>,
85
+ ) => Form<PickEntity<T, P>>
86
+ }
87
+ ```
88
+
89
+ ## Type `ErrorBag`
90
+ The errors in the form are structured in an `ErrorBag` object. Most errors are tied to properties in the form. However,
91
+ there might be cases where there are errors, that cannot be tied to one property. To account for that the `ErrorBag` satisfies the following interface:
92
+ ```typescript
93
+ interface ErrorBag {
94
+ // an array of general error messages not tied to a specific property
95
+ general: string[] | undefined
96
+ // a record of property paths to arrays of error messages
97
+ // nested properties are dot-separated and array indices are just numbers, e.g. "person.address.street" or "person.hobbies.0.name"
98
+ // for subforms the errors will be all errors that start with the path of the subform
99
+ propertyErrors: Record<string, ValidationErrorMessage[] | undefined>
100
+ }
101
+ ```
102
+
103
+ ## Component `FormPart`
104
+ A component to define a subform part for a nested object or array property of the form data. This corresponds to the
105
+ `getSubForm` method of the `Form<T>` object, but in component form to be easily used in the template. An example usage is as follows:
106
+ ```vue
107
+ <template>
108
+ <FormPart :form="form" path="person.address" #="{ subform }">
109
+ <MyAdddressComponent :form="subform" />
110
+ </FormPart>
111
+ </template>
112
+ ```
113
+ Here, the `subform` will be a `Form` object with the type of the `address` property of the `person` object of the main form data.
114
+ Note that the errors of the subform will only contain the errors that start with the path of the subform.
115
+
116
+ ## Component `Field`
117
+ This is a helper component to access form fields. It corresponds to the `defineField` method of the `Form<T>` object,
118
+ but in component form to be easily used in the template.
119
+
120
+ An example usage is as follows:
121
+ ```vue
122
+ <template>
123
+ <Field :form="form" path="firstName" #="{ data, setData, onBlur, errors }">
124
+ <input :value="data" @input="setData" @blur="onBlur" />
125
+ <div v-if="errors.length > 0">
126
+ <span v-for="error in errors" :key="error">{{ error }}</span>
127
+ </div>
128
+ </Field>
129
+ </template>
130
+ ```
131
+
132
+ Note: it is recommended to use the `FormFieldWrapper` component and create a reusable form input component instead.
133
+
134
+ ## Component `FormFieldWrapper`
135
+ This component is a helper component to easily create form input components based on plain input components.
136
+ See the [FormInput Example](./FormInput-example.md) for details and an example implementation of a form input component.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamnovu/kit-vue-forms",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
package/src/utils/path.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { computed, isRef, reactive, shallowRef, triggerRef, unref, watch, type MaybeRef } from 'vue'
1
+ import { computed, isRef, unref, type MaybeRef } from 'vue'
2
2
  import type { Paths, PickProps, SplitPath } from '../types/util'
3
3
  import type { ErrorBag, ValidationErrors } from '../types/validation'
4
4
 
@@ -17,22 +17,27 @@ export function getNestedValue<T, K extends Paths<T>>(obj: T, path: K | SplitPat
17
17
  ) as PickProps<T, K>
18
18
  }
19
19
 
20
- export function setNestedValue<T, K extends Paths<T>>(obj: T, path: K | SplitPath<K>, value: PickProps<T, K>): void {
20
+ export function setNestedValue<T, K extends Paths<T>>(obj: MaybeRef<T>, path: K | SplitPath<K>, value: PickProps<T, K>): void {
21
21
  const keys = Array.isArray(path) ? path : splitPath(path)
22
- if (keys.length === 0) {
23
- throw new Error('Path cannot be empty')
24
- }
25
22
 
26
23
  const lastKey = keys.at(-1)!
27
- const target = keys
28
- .slice(0, -1)
29
- .reduce(
30
- (current, key) => current[key],
31
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
- obj as Record<string, any>,
33
- )
34
24
 
35
- target[lastKey] = value
25
+ if (!lastKey && isRef(obj)) {
26
+ obj.value = value
27
+ } else if (!isRef(obj)) {
28
+ // We cannot do anything here as we have nothing we can assign to
29
+ return
30
+ } else {
31
+ const target = keys
32
+ .slice(0, -1)
33
+ .reduce(
34
+ (current, key) => current[key],
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ unref(obj) as Record<string, any>,
37
+ )
38
+
39
+ target[lastKey] = value
40
+ }
36
41
  }
37
42
 
38
43
  export const getLens = <T, K extends Paths<T>>(data: MaybeRef<T>, key: MaybeRef<K | SplitPath<K>>) => {
@@ -41,7 +46,7 @@ export const getLens = <T, K extends Paths<T>>(data: MaybeRef<T>, key: MaybeRef<
41
46
  return getNestedValue(unref(data), unref(key))
42
47
  },
43
48
  set(value: PickProps<T, K>) {
44
- setNestedValue(unref(data), unref(key), value)
49
+ setNestedValue(data, unref(key), value)
45
50
  },
46
51
  })
47
52
  }