@teamnovu/kit-vue-forms 0.0.20 → 0.1.0

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,7 +1,7 @@
1
1
  import { Form } from '../types/form.ts';
2
2
  import { Paths, PickProps } from '../types/util.ts';
3
3
  import { UseFieldOptions } from '../composables/useField.ts';
4
- import { VNodeProps, AllowedComponentProps, ComponentCustomProps, PublicProps, ShallowUnwrapRef, VNode } from 'vue';
4
+ import { VNodeProps, AllowedComponentProps, ComponentCustomProps, PublicProps, ShallowUnwrapRef, Ref, VNode } from 'vue';
5
5
  import { ValidationErrors, ErrorMessage } from '../index.js';
6
6
  export interface FieldProps<TData extends object, TPath extends string> extends UseFieldOptions<PickProps<TData, TPath>, TPath> {
7
7
  form: Form<TData>;
@@ -12,12 +12,12 @@ declare const _default: <TData extends object, TPath extends Paths<TData>>(__VLS
12
12
  attrs: any;
13
13
  slots: {
14
14
  default?(_: {
15
- data: PickProps<TData, TPath>;
16
- path: TPath;
17
- initialValue: PickProps<TData, TPath>;
18
- errors: ValidationErrors;
19
- touched: boolean;
20
- dirty: boolean;
15
+ data: Ref<PickProps<TData, TPath>, PickProps<TData, TPath>>;
16
+ path: Ref<TPath, TPath>;
17
+ initialValue: Readonly< Ref<PickProps<TData, TPath>, PickProps<TData, TPath>>>;
18
+ errors: Ref<ValidationErrors>;
19
+ touched: Ref<boolean>;
20
+ dirty: Ref<boolean>;
21
21
  setData: (newData: PickProps<TData, TPath>) => void;
22
22
  onBlur: () => void;
23
23
  onFocus: () => void;
@@ -2,7 +2,7 @@ import { FieldsTuple, FormDataDefault, FormField } from '../types/form';
2
2
  import { Paths, PickProps } from '../types/util';
3
3
  import { UseFieldOptions } from './useField';
4
4
  import { ValidationState } from './useValidation';
5
- type FieldRegistryCache<T> = Record<Paths<T>, FormField<any, string>>;
5
+ import { ComputedRef } from 'vue';
6
6
  export type ResolvedFormField<T, K extends Paths<T>> = FormField<PickProps<T, K>, K>;
7
7
  export type DefineFieldOptions<F, K extends string> = Pick<UseFieldOptions<F, K>, 'path'>;
8
8
  interface FormState<T extends FormDataDefault, TIn extends FormDataDefault = T> {
@@ -10,9 +10,8 @@ interface FormState<T extends FormDataDefault, TIn extends FormDataDefault = T>
10
10
  initialData: TIn;
11
11
  }
12
12
  export declare function useFieldRegistry<T extends FormDataDefault>(formState: FormState<T>, validationState: ValidationState<T>): {
13
- fields: FieldRegistryCache<T>;
13
+ fields: ComputedRef<FieldsTuple<T>>;
14
14
  getField: <K extends Paths<T>>(path: K) => ResolvedFormField<T, K>;
15
- getFields: <TData extends T>() => FieldsTuple<TData>;
16
15
  registerField: <K extends Paths<T>>(field: ResolvedFormField<T, K>) => void;
17
16
  defineField: <K extends Paths<T>>(options: DefineFieldOptions<PickProps<T, K>, K>) => ResolvedFormField<T, K>;
18
17
  };
package/dist/index.js CHANGED
@@ -1,158 +1,157 @@
1
1
  var T = Object.defineProperty;
2
2
  var G = (e, r, t) => r in e ? T(e, r, { enumerable: !0, configurable: !0, writable: !0, value: t }) : e[r] = t;
3
- var D = (e, r, t) => G(e, typeof r != "symbol" ? r + "" : r, t);
4
- import { toValue as L, toRaw as Z, computed as u, unref as d, reactive as E, toRefs as N, toRef as _, ref as W, watch as y, isRef as z, getCurrentScope as q, onBeforeUnmount as H, defineComponent as S, renderSlot as j, normalizeProps as M, guardReactiveProps as B, resolveComponent as Q, createBlock as O, openBlock as $, withCtx as A, resolveDynamicComponent as X, mergeProps as Y } from "vue";
5
- import { cloneDeep as x } from "lodash-es";
3
+ var R = (e, r, t) => G(e, typeof r != "symbol" ? r + "" : r, t);
4
+ import { toValue as L, toRaw as Z, computed as c, unref as d, reactive as F, toRefs as N, markRaw as q, toRef as _, ref as W, watch as g, isRef as z, getCurrentScope as H, onBeforeUnmount as Q, defineComponent as S, renderSlot as j, normalizeProps as M, guardReactiveProps as k, resolveComponent as X, createBlock as O, openBlock as $, withCtx as A, resolveDynamicComponent as Y, mergeProps as x } from "vue";
5
+ import { cloneDeep as rr } from "lodash-es";
6
6
  import "zod";
7
- function g(e) {
7
+ function y(e) {
8
8
  const r = L(e), t = Z(r);
9
- return x(t);
9
+ return rr(t);
10
10
  }
11
- function K(e) {
11
+ function B(e) {
12
12
  return e === "" ? [] : e.split(/\s*\.\s*/).filter(Boolean);
13
13
  }
14
14
  function w(e, r) {
15
- return (Array.isArray(r) ? r : K(r)).reduce(
16
- (s, a) => s == null ? void 0 : s[a],
15
+ return (Array.isArray(r) ? r : B(r)).reduce(
16
+ (a, s) => a == null ? void 0 : a[s],
17
17
  e
18
18
  );
19
19
  }
20
- function rr(e, r, t) {
21
- const s = Array.isArray(r) ? r : K(r);
22
- if (s.length === 0)
20
+ function er(e, r, t) {
21
+ const a = Array.isArray(r) ? r : B(r);
22
+ if (a.length === 0)
23
23
  throw new Error("Path cannot be empty");
24
- const a = s.at(-1), o = s.slice(0, -1).reduce(
25
- (h, c) => h[c],
24
+ const s = a.at(-1), o = a.slice(0, -1).reduce(
25
+ (i, h) => i[h],
26
26
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
27
  e
28
28
  );
29
- o[a] = t;
29
+ o[s] = t;
30
30
  }
31
- const U = (e, r) => u({
31
+ const K = (e, r) => c({
32
32
  get() {
33
33
  return w(d(e), d(r));
34
34
  },
35
35
  set(t) {
36
- rr(d(e), d(r), t);
36
+ er(d(e), d(r), t);
37
37
  }
38
38
  });
39
- function F(e, r) {
39
+ function E(e, r) {
40
40
  return !e && !r ? "" : !e && r ? r : !r && e ? e : `${e}.${r}`;
41
41
  }
42
- function er(e, r) {
42
+ function tr(e, r) {
43
43
  if (!r)
44
44
  return e;
45
- const t = `${r}.`, s = Object.fromEntries(
46
- Object.entries(e.propertyErrors).filter(([a]) => a.startsWith(t)).map(
47
- ([a, o]) => [a.slice(t.length), o]
45
+ const t = `${r}.`, a = Object.fromEntries(
46
+ Object.entries(e.propertyErrors).filter(([s]) => s.startsWith(t)).map(
47
+ ([s, o]) => [s.slice(t.length), o]
48
48
  )
49
49
  );
50
50
  return {
51
51
  general: e.general,
52
52
  // Keep general errors
53
- propertyErrors: s
53
+ propertyErrors: a
54
54
  };
55
55
  }
56
- function tr(e) {
57
- const r = E({
56
+ function ar(e) {
57
+ const r = F({
58
58
  value: e.value,
59
59
  path: e.path,
60
- initialValue: u(() => Object.freeze(g(e.initialValue))),
60
+ initialValue: c(() => Object.freeze(y(e.initialValue))),
61
61
  errors: e.errors,
62
62
  touched: !1
63
- }), t = u(() => JSON.stringify(r.value) !== JSON.stringify(r.initialValue)), s = (f) => {
63
+ }), t = c(() => JSON.stringify(r.value) !== JSON.stringify(r.initialValue)), a = (f) => {
64
64
  r.value = f;
65
- }, a = () => {
65
+ }, s = () => {
66
66
  r.touched = !0;
67
67
  }, o = () => {
68
- }, h = () => {
69
- r.value = g(r.initialValue), r.touched = !1, r.errors = [];
70
- }, c = (f) => {
68
+ }, i = () => {
69
+ r.value = y(r.initialValue), r.touched = !1, r.errors = [];
70
+ }, h = (f) => {
71
71
  r.errors = f;
72
- }, n = () => {
72
+ }, l = () => {
73
73
  r.errors = [];
74
- }, l = N(r);
74
+ }, u = N(r);
75
75
  return {
76
- data: l.value,
77
- path: l.path,
78
- initialValue: l.initialValue,
79
- errors: l.errors,
80
- touched: l.touched,
76
+ data: u.value,
77
+ path: u.path,
78
+ initialValue: u.initialValue,
79
+ errors: u.errors,
80
+ touched: u.touched,
81
81
  dirty: t,
82
- setData: s,
83
- onBlur: a,
82
+ setData: a,
83
+ onBlur: s,
84
84
  onFocus: o,
85
- reset: h,
86
- setErrors: c,
87
- clearErrors: n
85
+ reset: i,
86
+ setErrors: h,
87
+ clearErrors: l
88
88
  };
89
89
  }
90
90
  function sr(e, r) {
91
- const t = {}, s = (c) => {
92
- const n = d(c.path);
93
- t[n] = c;
94
- }, a = (c) => {
95
- if (!t[c]) {
96
- const n = tr({
97
- path: c,
98
- value: U(_(e, "data"), c),
99
- initialValue: u(() => w(e.initialData, c)),
100
- errors: u({
91
+ const t = F({}), a = (i) => {
92
+ const h = d(i.path);
93
+ t[h] = q(i);
94
+ }, s = (i) => {
95
+ if (!t[i]) {
96
+ const h = ar({
97
+ path: i,
98
+ value: K(_(e, "data"), i),
99
+ initialValue: c(() => w(e.initialData, i)),
100
+ errors: c({
101
101
  get() {
102
- return r.errors.value.propertyErrors[c] || [];
102
+ return r.errors.value.propertyErrors[i] || [];
103
103
  },
104
104
  set(l) {
105
- r.errors.value.propertyErrors[c] = l;
105
+ r.errors.value.propertyErrors[i] = l;
106
106
  }
107
107
  })
108
108
  });
109
- return s(n), n;
109
+ return a(h), h;
110
110
  }
111
- return t[c];
112
- };
111
+ return t[i];
112
+ }, o = (i) => s(i.path);
113
113
  return {
114
- fields: t,
115
- getField: a,
116
- getFields: () => Object.values(t),
117
- registerField: s,
118
- defineField: (c) => a(c.path)
114
+ fields: c(() => Object.values(t)),
115
+ getField: s,
116
+ registerField: a,
117
+ defineField: o
119
118
  };
120
119
  }
121
- function ar(e) {
122
- const r = u(() => e.getFields().some((s) => d(s.dirty))), t = u(() => e.getFields().some((s) => d(s.touched)));
120
+ function or(e) {
121
+ const r = c(() => e.fields.value.some((a) => d(a.dirty))), t = c(() => e.fields.value.some((a) => d(a.touched)));
123
122
  return {
124
123
  isDirty: r,
125
124
  isTouched: t
126
125
  };
127
126
  }
128
- function or(e) {
127
+ function nr(e) {
129
128
  return e.filter(
130
- (r, t, s) => s.indexOf(r) === t
129
+ (r, t, a) => a.indexOf(r) === t
131
130
  );
132
131
  }
133
- function k(...e) {
132
+ function U(...e) {
134
133
  return e.slice(1).reduce((r, t) => {
135
134
  if (!r && !t)
136
135
  return;
137
- const s = ((t == null ? void 0 : t.length) ?? 0) > 0;
136
+ const a = ((t == null ? void 0 : t.length) ?? 0) > 0;
138
137
  if (!r && ((t == null ? void 0 : t.length) ?? 0) > 0)
139
138
  return t;
140
- if (!s)
139
+ if (!a)
141
140
  return r;
142
- const a = (r ?? []).concat(t);
143
- return or(a);
141
+ const s = (r ?? []).concat(t);
142
+ return nr(s);
144
143
  }, e[0]);
145
144
  }
146
- function nr(...e) {
147
- return e.map((t) => Object.keys(t)).flat().reduce((t, s) => {
148
- const a = e.map((o) => o[s]).filter(Boolean);
145
+ function ir(...e) {
146
+ return e.map((t) => Object.keys(t)).flat().reduce((t, a) => {
147
+ const s = e.map((o) => o[a]).filter(Boolean);
149
148
  return {
150
149
  ...t,
151
- [s]: k(...a)
150
+ [a]: U(...s)
152
151
  };
153
152
  }, {});
154
153
  }
155
- function R(...e) {
154
+ function D(...e) {
156
155
  if (!e.length)
157
156
  return {
158
157
  general: [],
@@ -160,24 +159,24 @@ function R(...e) {
160
159
  };
161
160
  const r = e[0];
162
161
  return e.length === 1 ? r : e.slice(1).reduce(
163
- (t, s) => ({
164
- general: k(t.general, s.general),
165
- propertyErrors: nr(t.propertyErrors ?? {}, s.propertyErrors ?? {})
162
+ (t, a) => ({
163
+ general: U(t.general, a.general),
164
+ propertyErrors: ir(t.propertyErrors ?? {}, a.propertyErrors ?? {})
166
165
  }),
167
166
  r
168
167
  );
169
168
  }
170
169
  function C(e) {
171
- var s;
172
- const r = (((s = e.general) == null ? void 0 : s.length) ?? 0) > 0, t = Object.entries(e.propertyErrors).filter(([, a]) => a == null ? void 0 : a.length).length > 0;
170
+ var a;
171
+ const r = (((a = e.general) == null ? void 0 : a.length) ?? 0) > 0, t = Object.entries(e.propertyErrors).filter(([, s]) => s == null ? void 0 : s.length).length > 0;
173
172
  return r || t;
174
173
  }
175
- function ir(e) {
176
- const r = e.issues.filter((s) => s.path.length === 0).map((s) => s.message), t = e.issues.filter((s) => s.path.length > 0).reduce((s, a) => {
177
- const o = a.path.join(".");
174
+ function lr(e) {
175
+ const r = e.issues.filter((a) => a.path.length === 0).map((a) => a.message), t = e.issues.filter((a) => a.path.length > 0).reduce((a, s) => {
176
+ const o = s.path.join(".");
178
177
  return {
179
- ...s,
180
- [o]: [...s[o] ?? [], a.message]
178
+ ...a,
179
+ [o]: [...a[o] ?? [], s.message]
181
180
  };
182
181
  }, {});
183
182
  return {
@@ -192,7 +191,7 @@ const m = {
192
191
  propertyErrors: {}
193
192
  }
194
193
  };
195
- class lr {
194
+ class ur {
196
195
  constructor(r) {
197
196
  this.schema = r;
198
197
  }
@@ -202,12 +201,12 @@ class lr {
202
201
  const t = await this.schema.safeParseAsync(r);
203
202
  if (t.success)
204
203
  return m;
205
- const s = ir(t.error);
204
+ const a = lr(t.error);
206
205
  return {
207
206
  isValid: !1,
208
207
  errors: {
209
- general: s.general ?? [],
210
- propertyErrors: s.propertyErrors ?? {}
208
+ general: a.general ?? [],
209
+ propertyErrors: a.propertyErrors ?? {}
211
210
  }
212
211
  };
213
212
  }
@@ -233,91 +232,91 @@ class cr {
233
232
  }
234
233
  }
235
234
  }
236
- class ur {
235
+ class dr {
237
236
  constructor(r, t) {
238
- D(this, "schemaValidator");
239
- D(this, "functionValidator");
240
- this.schema = r, this.validateFn = t, this.schemaValidator = new lr(this.schema), this.functionValidator = new cr(this.validateFn);
237
+ R(this, "schemaValidator");
238
+ R(this, "functionValidator");
239
+ this.schema = r, this.validateFn = t, this.schemaValidator = new ur(this.schema), this.functionValidator = new cr(this.validateFn);
241
240
  }
242
241
  async validate(r) {
243
- const [t, s] = await Promise.all([
242
+ const [t, a] = await Promise.all([
244
243
  this.schemaValidator.validate(r),
245
244
  this.functionValidator.validate(r)
246
245
  ]);
247
246
  return {
248
- isValid: t.isValid && s.isValid,
249
- errors: R(t.errors, s.errors)
247
+ isValid: t.isValid && a.isValid,
248
+ errors: D(t.errors, a.errors)
250
249
  };
251
250
  }
252
251
  }
253
252
  function b(e) {
254
- return u(() => new ur(
253
+ return c(() => new dr(
255
254
  d(e.schema),
256
255
  d(e.validateFn)
257
256
  ));
258
257
  }
259
- function dr(e, r) {
260
- const t = E({
258
+ function fr(e, r) {
259
+ const t = F({
261
260
  validators: W([b(r)]),
262
261
  isValidated: !1,
263
262
  errors: d(r.errors) ?? m.errors
264
263
  });
265
- y(() => d(r.errors), async () => {
266
- const n = await a();
267
- o(n.errors);
268
- }, { immediate: !0 }), y(
264
+ g(() => d(r.errors), async () => {
265
+ const l = await s();
266
+ o(l.errors);
267
+ }, { immediate: !0 }), g(
269
268
  [() => t.validators],
270
- async (n) => {
269
+ async (l) => {
271
270
  if (t.isValidated)
272
- if (n) {
273
- const l = await a();
274
- t.errors = l.errors;
271
+ if (l) {
272
+ const u = await s();
273
+ t.errors = u.errors;
275
274
  } else
276
275
  t.errors = m.errors;
277
276
  },
278
277
  { immediate: !0 }
279
- ), y(() => e.data, () => {
280
- t.isValidated && h();
278
+ ), g(() => e.data, () => {
279
+ t.isValidated && i();
281
280
  });
282
- const s = (n) => {
283
- const l = z(n) ? n : b(n);
284
- return t.validators.push(l), q() && H(() => {
281
+ const a = (l) => {
282
+ const u = z(l) ? l : b(l);
283
+ return t.validators.push(u), H() && Q(() => {
285
284
  t.validators = t.validators.filter(
286
- (f) => f !== l
285
+ (f) => f !== u
287
286
  );
288
- }), l;
287
+ }), u;
289
288
  };
290
- async function a() {
291
- const n = await Promise.all(
289
+ async function s() {
290
+ const l = await Promise.all(
292
291
  t.validators.filter((v) => d(v) !== void 0).map((v) => d(v).validate(e.data))
293
- ), l = n.every((v) => v.isValid);
292
+ ), u = l.every((v) => v.isValid);
294
293
  let { errors: f } = m;
295
- if (!l) {
296
- const v = n.map((P) => P.errors);
297
- f = R(...v);
294
+ if (!u) {
295
+ const v = l.map((P) => P.errors);
296
+ f = D(...v);
298
297
  }
299
298
  return {
300
299
  errors: f,
301
- isValid: l
300
+ isValid: u
302
301
  };
303
302
  }
304
- const o = (n) => {
305
- t.errors = R(d(r.errors) ?? m.errors, n);
306
- }, h = async () => {
307
- const n = await a();
308
- return o(n.errors), t.isValidated = !0, {
309
- isValid: !C(n.errors),
303
+ const o = (l) => {
304
+ t.errors = D(d(r.errors) ?? m.errors, l);
305
+ }, i = async () => {
306
+ const l = await s();
307
+ return o(l.errors), t.isValidated = !0, {
308
+ isValid: !C(l.errors),
310
309
  errors: t.errors
311
310
  };
312
- }, c = u(() => !C(t.errors));
311
+ }, h = c(() => !C(t.errors));
313
312
  return {
314
313
  ...N(t),
315
- validateForm: h,
316
- defineValidator: s,
317
- isValid: c
314
+ validateForm: i,
315
+ defineValidator: a,
316
+ isValid: h
318
317
  };
319
318
  }
320
- class fr {
319
+ class pr {
321
320
  constructor(r, t) {
322
321
  this.path = r, this.validator = t;
323
322
  }
@@ -325,14 +324,14 @@ class fr {
325
324
  const t = w(r, this.path);
326
325
  if (!this.validator)
327
326
  return m;
328
- const s = await this.validator.validate(t);
327
+ const a = await this.validator.validate(t);
329
328
  return {
330
- isValid: s.isValid,
329
+ isValid: a.isValid,
331
330
  errors: {
332
- general: s.errors.general || [],
333
- propertyErrors: s.errors.propertyErrors ? Object.fromEntries(
334
- Object.entries(s.errors.propertyErrors).map(([a, o]) => [
335
- F(this.path, a),
331
+ general: a.errors.general || [],
332
+ propertyErrors: a.errors.propertyErrors ? Object.fromEntries(
333
+ Object.entries(a.errors.propertyErrors).map(([s, o]) => [
334
+ E(this.path, s),
336
335
  o
337
336
  ])
338
337
  ) : {}
@@ -340,50 +339,50 @@ class fr {
340
339
  };
341
340
  }
342
341
  }
343
- function pr(e, r, t) {
344
- const s = U(e.data, r), a = u(() => w(e.initialData.value, r)), o = (i) => ({
345
- ...i,
346
- path: u(() => d(i.path).replace(r + ".", "")),
342
+ function hr(e, r, t) {
343
+ const a = K(e.data, r), s = c(() => w(e.initialData.value, r)), o = (n) => ({
344
+ ...n,
345
+ path: c(() => d(n.path).replace(r + ".", "")),
347
346
  setData: (p) => {
348
- i.setData(p);
347
+ n.setData(p);
349
348
  }
350
- }), h = (i) => {
351
- const p = F(r, i), V = e.getField(p);
349
+ }), i = (n) => {
350
+ const p = E(r, n), V = e.getField(p);
352
351
  return V ? o(V) : {};
353
- }, c = (i) => {
354
- const p = F(r, i.path), V = e.defineField({
355
- ...i,
352
+ }, h = (n) => {
353
+ const p = E(r, n.path), V = e.defineField({
354
+ ...n,
356
355
  path: p
357
356
  });
358
357
  return o(V);
359
- }, n = () => e.getFields().filter((i) => {
360
- const p = i.path.value;
358
+ }, l = c(() => e.fields.value.filter((n) => {
359
+ const p = n.path.value;
361
360
  return p.startsWith(r + ".") || p === r;
362
- }).map((i) => o(i)), l = () => e.getFields().filter((i) => {
363
- const p = i.path.value;
361
+ }).map((n) => o(n))), u = () => e.fields.value.filter((n) => {
362
+ const p = n.path.value;
364
363
  return p.startsWith(r + ".") || p === r;
365
- }), f = u(() => l().some((i) => i.dirty.value)), v = u(() => l().some((i) => i.touched.value)), P = u(() => e.isValid.value), I = u(() => e.isValidated.value), J = u(() => er(d(e.errors), r));
364
+ }), f = c(() => u().some((n) => n.dirty.value)), v = c(() => u().some((n) => n.touched.value)), P = c(() => e.isValid.value), I = c(() => e.isValidated.value), J = c(() => tr(d(e.errors), r));
366
365
  return {
367
- data: s,
368
- initialData: a,
369
- defineField: c,
370
- getField: h,
371
- getFields: n,
366
+ data: a,
367
+ fields: l,
368
+ initialData: s,
369
+ defineField: h,
370
+ getField: i,
372
371
  isDirty: f,
373
372
  isTouched: v,
374
373
  isValid: P,
375
374
  isValidated: I,
376
375
  errors: J,
377
- defineValidator: (i) => {
378
- const p = z(i) ? i : b(i), V = u(
379
- () => new fr(r, d(p))
376
+ defineValidator: (n) => {
377
+ const p = z(n) ? n : b(n), V = c(
378
+ () => new pr(r, d(p))
380
379
  );
381
380
  return e.defineValidator(V), p;
382
381
  },
383
- reset: () => l().forEach((i) => i.reset()),
382
+ reset: () => u().forEach((n) => n.reset()),
384
383
  validateForm: () => e.validateForm(),
385
- getSubForm: (i, p) => {
386
- const V = F(r, i);
384
+ getSubForm: (n, p) => {
385
+ const V = E(r, n);
387
386
  return e.getSubForm(
388
387
  V,
389
388
  p
@@ -391,34 +390,34 @@ function pr(e, r, t) {
391
390
  }
392
391
  };
393
392
  }
394
- function wr(e) {
395
- const r = u(() => Object.freeze(g(e.initialData))), t = W(g(r)), s = E({
393
+ function Pr(e) {
394
+ const r = c(() => Object.freeze(y(e.initialData))), t = W(y(r)), a = F({
396
395
  initialData: r,
397
396
  data: t
398
397
  });
399
- y(r, (f) => {
400
- s.data = g(f);
398
+ g(r, (f) => {
399
+ a.data = y(f);
401
400
  });
402
- const a = dr(s, e), o = sr(s, a), h = ar(o), c = () => {
403
- t.value = g(r), o.getFields().forEach(
401
+ const s = fr(a, e), o = sr(a, s), i = or(o), h = () => {
402
+ t.value = y(r), o.fields.value.forEach(
404
403
  (f) => f.reset()
405
404
  );
406
405
  };
407
- function n(f, v) {
408
- return pr(l, f);
406
+ function l(f, v) {
407
+ return hr(u, f);
409
408
  }
410
- const l = {
409
+ const u = {
411
410
  ...o,
412
- ...a,
413
- ...h,
414
- reset: c,
415
- getSubForm: n,
416
- initialData: _(s, "initialData"),
417
- data: _(s, "data")
411
+ ...s,
412
+ ...i,
413
+ reset: h,
414
+ getSubForm: l,
415
+ initialData: _(a, "initialData"),
416
+ data: _(a, "data")
418
417
  };
419
- return l;
418
+ return u;
420
419
  }
421
- const Pr = /* @__PURE__ */ S({
420
+ const Rr = /* @__PURE__ */ S({
422
421
  __name: "Field",
423
422
  props: {
424
423
  form: {},
@@ -430,10 +429,10 @@ const Pr = /* @__PURE__ */ S({
430
429
  setup(e) {
431
430
  const r = e, t = r.form.defineField({
432
431
  path: r.path
433
- }), s = E(t);
434
- return (a, o) => j(a.$slots, "default", M(B(s)));
432
+ });
433
+ return (a, s) => j(a.$slots, "default", M(k(d(t))));
435
434
  }
436
- }), Dr = /* @__PURE__ */ S({
435
+ }), _r = /* @__PURE__ */ S({
437
436
  inheritAttrs: !1,
438
437
  __name: "FormFieldWrapper",
439
438
  props: {
@@ -444,17 +443,17 @@ const Pr = /* @__PURE__ */ S({
444
443
  },
445
444
  setup(e) {
446
445
  return (r, t) => {
447
- const s = Q("Field");
448
- return $(), O(s, {
446
+ const a = X("Field");
447
+ return $(), O(a, {
449
448
  form: r.form,
450
449
  path: r.path
451
450
  }, {
452
- default: A(({ errors: a, data: o, setData: h }) => [
453
- ($(), O(X(r.component), Y({ ...r.componentProps, ...r.$attrs }, {
451
+ default: A(({ errors: s, data: o, setData: i }) => [
452
+ ($(), O(Y(r.component), x({ ...r.componentProps, ...r.$attrs }, {
454
453
  "model-value": o,
455
- errors: a,
454
+ errors: s,
456
455
  name: r.path,
457
- "onUpdate:modelValue": h
456
+ "onUpdate:modelValue": i
458
457
  }), {
459
458
  default: A(() => [
460
459
  j(r.$slots, "default")
@@ -466,20 +465,20 @@ const Pr = /* @__PURE__ */ S({
466
465
  }, 8, ["form", "path"]);
467
466
  };
468
467
  }
469
- }), _r = /* @__PURE__ */ S({
468
+ }), Dr = /* @__PURE__ */ S({
470
469
  __name: "FormPart",
471
470
  props: {
472
471
  form: {},
473
472
  path: {}
474
473
  },
475
474
  setup(e) {
476
- const r = e, t = u(() => r.form.getSubForm(r.path));
477
- return (s, a) => j(s.$slots, "default", M(B({ subform: t.value })));
475
+ const r = e, t = c(() => r.form.getSubForm(r.path));
476
+ return (a, s) => j(a.$slots, "default", M(k({ subform: t.value })));
478
477
  }
479
478
  });
480
479
  export {
481
- Pr as Field,
482
- Dr as FormFieldWrapper,
483
- _r as FormPart,
484
- wr as useForm
480
+ Rr as Field,
481
+ _r as FormFieldWrapper,
482
+ Dr as FormPart,
483
+ Pr as useForm
485
484
  };
@@ -26,9 +26,9 @@ export type AnyField<T> = FormField<PickProps<T, Paths<T>>, Paths<T>>;
26
26
  export interface Form<T extends FormDataDefault> {
27
27
  data: Ref<T>;
28
28
  initialData: Readonly<Ref<T>>;
29
+ fields: Ref<FieldsTuple<T>>;
29
30
  defineField: <P extends Paths<T>>(options: DefineFieldOptions<PickProps<T, P>, P>) => FormField<PickProps<T, P>, P>;
30
31
  getField: <P extends Paths<T>>(path: P) => FormField<PickProps<T, P>, P>;
31
- getFields: <TData extends T>() => FieldsTuple<TData>;
32
32
  isDirty: Ref<boolean>;
33
33
  isTouched: Ref<boolean>;
34
34
  isValid: Ref<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamnovu/kit-vue-forms",
3
- "version": "0.0.20",
3
+ "version": "0.1.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,12 +1,11 @@
1
1
  <template>
2
- <slot v-bind="slotData" />
2
+ <slot v-bind="field" />
3
3
  </template>
4
4
 
5
5
  <script
6
6
  setup lang="ts"
7
7
  generic="TData extends object, TPath extends Paths<TData>"
8
8
  >
9
- import { reactive } from 'vue'
10
9
  import type { Form } from '../types/form.ts'
11
10
  import type { Paths, PickProps } from '../types/util.ts'
12
11
  import type { UseFieldOptions } from '../composables/useField.ts'
@@ -20,6 +19,4 @@ const props = defineProps<FieldProps<TData, TPath>>()
20
19
  const field = props.form.defineField({
21
20
  path: props.path,
22
21
  })
23
-
24
- const slotData = reactive(field)
25
22
  </script>
@@ -1,4 +1,4 @@
1
- import { computed, reactive, toRefs, watch, type MaybeRef, type MaybeRefOrGetter, type WritableComputedRef } from 'vue'
1
+ import { computed, reactive, toRefs, type MaybeRef, type MaybeRefOrGetter, type WritableComputedRef } from 'vue'
2
2
  import type { FormField } from '../types/form'
3
3
  import type { ValidationErrorMessage, ValidationErrors } from '../types/validation'
4
4
  import { cloneRefValue } from '../utils/general'
@@ -1,4 +1,4 @@
1
- import { computed, toRef, unref } from 'vue'
1
+ import { computed, markRaw, reactive, toRef, unref, watch } from 'vue'
2
2
  import type { FieldsTuple, FormDataDefault, FormField } from '../types/form'
3
3
  import type { Paths, PickProps } from '../types/util'
4
4
  import { getLens, getNestedValue } from '../utils/path'
@@ -21,11 +21,11 @@ export function useFieldRegistry<T extends FormDataDefault>(
21
21
  formState: FormState<T>,
22
22
  validationState: ValidationState<T>,
23
23
  ) {
24
- const fields = {} as FieldRegistryCache<T>
24
+ const fields = reactive({}) as FieldRegistryCache<T>
25
25
 
26
26
  const registerField = <K extends Paths<T>>(field: ResolvedFormField<T, K>) => {
27
27
  const path = unref(field.path) as Paths<T>
28
- fields[path] = field
28
+ fields[path] = markRaw(field)
29
29
  }
30
30
 
31
31
  const getField = <K extends Paths<T>>(path: K): ResolvedFormField<T, K> => {
@@ -52,10 +52,6 @@ export function useFieldRegistry<T extends FormDataDefault>(
52
52
  return fields[path] as ResolvedFormField<T, K>
53
53
  }
54
54
 
55
- const getFields = <TData extends T>() => {
56
- return Object.values(fields) as FieldsTuple<TData>
57
- }
58
-
59
55
  const defineField = <K extends Paths<T>>(options: DefineFieldOptions<PickProps<T, K>, K>): ResolvedFormField<T, K> => {
60
56
  const field = getField(options.path)
61
57
 
@@ -66,9 +62,8 @@ export function useFieldRegistry<T extends FormDataDefault>(
66
62
  }
67
63
 
68
64
  return {
69
- fields,
65
+ fields: computed(() => Object.values(fields) as FieldsTuple<T>),
70
66
  getField,
71
- getFields,
72
67
  registerField,
73
68
  defineField,
74
69
  }
@@ -31,12 +31,12 @@ export function useForm<T extends FormDataDefault>(options: UseFormOptions<T>) {
31
31
  })
32
32
 
33
33
  const validationState = useValidation(state, options)
34
- const fields = useFieldRegistry(state, validationState)
35
- const formState = useFormState(fields)
34
+ const fieldRegistry = useFieldRegistry(state, validationState)
35
+ const formState = useFormState(fieldRegistry)
36
36
 
37
37
  const reset = () => {
38
38
  data.value = cloneRefValue(initialData)
39
- fields.getFields().forEach(
39
+ fieldRegistry.fields.value.forEach(
40
40
  (field: AnyField<T>) => field.reset(),
41
41
  )
42
42
  }
@@ -49,7 +49,7 @@ export function useForm<T extends FormDataDefault>(options: UseFormOptions<T>) {
49
49
  }
50
50
 
51
51
  const formInterface: Form<T> = {
52
- ...fields,
52
+ ...fieldRegistry,
53
53
  ...validationState,
54
54
  ...formState,
55
55
  reset,
@@ -6,11 +6,11 @@ export function useFormState<T extends FormDataDefault>(
6
6
  formFieldRegistry: FieldRegistry<T>,
7
7
  ) {
8
8
  const isDirty = computed(() => {
9
- return formFieldRegistry.getFields().some((field: AnyField<T>) => unref(field.dirty))
9
+ return formFieldRegistry.fields.value.some((field: AnyField<T>) => unref(field.dirty))
10
10
  })
11
11
 
12
12
  const isTouched = computed(() => {
13
- return formFieldRegistry.getFields().some((field: AnyField<T>) => unref(field.touched))
13
+ return formFieldRegistry.fields.value.some((field: AnyField<T>) => unref(field.touched))
14
14
  })
15
15
 
16
16
  return {
@@ -99,18 +99,18 @@ export function createSubformInterface<
99
99
  return adaptMainFormField<P>(mainField)
100
100
  }
101
101
 
102
- const getFields = <P extends SP>() => {
103
- return (mainForm.getFields() as FormField<PickProps<T, ScopedMainPaths>, ScopedMainPaths>[])
102
+ const fields = computed(<P extends SP>() => {
103
+ return (mainForm.fields.value as FormField<PickProps<T, ScopedMainPaths>, ScopedMainPaths>[])
104
104
  .filter((field) => {
105
105
  const fieldPath = field.path.value
106
106
  return fieldPath.startsWith(path + '.') || fieldPath === path
107
107
  })
108
108
  .map(field => adaptMainFormField(field)) as FieldsTuple<ST, P>
109
- }
109
+ })
110
110
 
111
111
  // Helper function to get all fields without type parameter
112
112
  const getAllSubformFields = () => {
113
- return (mainForm.getFields() as FormField<PickProps<T, ScopedMainPaths>, ScopedMainPaths>[])
113
+ return (mainForm.fields.value as FormField<PickProps<T, ScopedMainPaths>, ScopedMainPaths>[])
114
114
  .filter((field) => {
115
115
  const fieldPath = field.path.value
116
116
  return fieldPath.startsWith(path + '.') || fieldPath === path
@@ -156,10 +156,10 @@ export function createSubformInterface<
156
156
 
157
157
  return {
158
158
  data: data,
159
+ fields,
159
160
  initialData,
160
161
  defineField,
161
162
  getField,
162
- getFields,
163
163
  isDirty,
164
164
  isTouched,
165
165
  isValid,
package/src/types/form.ts CHANGED
@@ -37,10 +37,11 @@ export interface Form<T extends FormDataDefault> {
37
37
  data: Ref<T>
38
38
  initialData: Readonly<Ref<T>>
39
39
 
40
+ fields: Ref<FieldsTuple<T>>
41
+
40
42
  // Field operations
41
43
  defineField: <P extends Paths<T>>(options: DefineFieldOptions<PickProps<T, P>, P>) => FormField<PickProps<T, P>, P>
42
44
  getField: <P extends Paths<T>>(path: P) => FormField<PickProps<T, P>, P>
43
- getFields: <TData extends T>() => FieldsTuple<TData>
44
45
 
45
46
  // State properties
46
47
  isDirty: Ref<boolean>
package/src/utils/path.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { computed, unref, type MaybeRef } from 'vue'
1
+ import { computed, isRef, reactive, shallowRef, triggerRef, unref, watch, 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
 
@@ -187,7 +187,7 @@ describe('Integration Tests', () => {
187
187
  form.defineField({ path: 'email' })
188
188
 
189
189
  // Check registry
190
- expect(form.getFields().length).toBe(2)
190
+ expect(form.fields.value.length).toBe(2)
191
191
 
192
192
  // Get specific field
193
193
  const retrievedNameField = form.getField('name')
@@ -564,7 +564,7 @@ describe('Nested Path Handling', () => {
564
564
  }
565
565
 
566
566
  expect(fields.length).toBeGreaterThan(0)
567
- expect(form.getFields().length).toBeGreaterThan(0)
567
+ expect(form.fields.value.length).toBeGreaterThan(0)
568
568
  })
569
569
  })
570
570
 
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect } from 'vitest'
2
- import { ref, nextTick } from 'vue'
2
+ import { watch, ref, nextTick, effectScope } from 'vue'
3
3
  import { useField } from '../src/composables/useField'
4
+ import { cloneDeep } from 'lodash-es'
4
5
 
5
6
  describe('useField', () => {
6
7
  it('should initialize field with path', () => {
@@ -144,4 +145,25 @@ describe('useField', () => {
144
145
  field.setData(['a', 'b', 'c', 'd'])
145
146
  expect(field.dirty.value).toBe(true)
146
147
  })
148
+
149
+ it('should trigger reactivity', { timeout: 500 }, () => new Promise((resolve) => {
150
+ effectScope().run(() => {
151
+ const initialValue = ref(['a', 'b', 'c'])
152
+ const field = useField({
153
+ path: 'array',
154
+ value: initialValue,
155
+ initialValue: cloneDeep(initialValue.value),
156
+ })
157
+
158
+ expect(field.data.value).toEqual(initialValue.value)
159
+ expect(field.dirty.value).toBe(false)
160
+
161
+ watch(initialValue, () => {
162
+ resolve(true)
163
+ }, { once: true, deep: true })
164
+
165
+ field.setData(['a', 'b', 'c', 'd'])
166
+ expect(field.dirty.value).toBe(true)
167
+ })
168
+ }))
147
169
  })
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest'
2
- import { ref, nextTick } from 'vue'
2
+ import { ref, nextTick, effectScope, watch, unref, reactive, toRef } from 'vue'
3
3
  import { useForm } from '../src/composables/useForm'
4
4
  import { z } from 'zod'
5
5
 
@@ -87,7 +87,7 @@ describe('useForm', () => {
87
87
 
88
88
  expect(nameField.path.value).toBe('name')
89
89
  expect(emailField.path.value).toBe('email')
90
- expect(form.getFields().length).toBe(2)
90
+ expect(form.fields.value.length).toBe(2)
91
91
  })
92
92
 
93
93
  it('should get registered fields', () => {
package/PLAN.md DELETED
@@ -1,211 +0,0 @@
1
- # DEPRECATED
2
-
3
- # Vue Forms Library - MVP Plan
4
-
5
- ## Overview
6
-
7
- A type-safe Vue 3 form library with immutable state management, validation, and subform support. The library uses a direct form instance pattern (no provide/inject) to maintain full type safety throughout the component tree.
8
-
9
- ## Core Architecture
10
-
11
- ### Direct Form Instance Pattern
12
- - Forms are created with `createForm<T>()` and passed directly to components
13
- - Full TypeScript type safety maintained throughout the component tree
14
- - No context loss from provide/inject patterns
15
-
16
- ### Subform Extraction Pattern
17
- ```typescript
18
- // Reusable components work with subforms
19
- const addressSubform = form.getSubform('address')
20
- // addressSubform has type FormInstance<Address>
21
-
22
- <AddressForm :form="addressSubform" />
23
- ```
24
-
25
- This allows components to be reusable across different parent forms while maintaining type safety.
26
-
27
- ## API Design
28
-
29
- ### Form Creation
30
- ```typescript
31
- const form = createForm<UserData>({
32
- initialData: { name: '', email: '', address: { street: '', city: '' } },
33
- validationSchema: userSchema,
34
- strategy: 'onTouch'
35
- })
36
- ```
37
-
38
- ### Form Instance Interface
39
- ```typescript
40
- interface FormInstance<T> {
41
- // Data access
42
- getValue<K extends keyof T>(path: K): T[K]
43
- setValue<K extends keyof T>(path: K, value: T[K]): void
44
-
45
- // Field management
46
- getField<K extends keyof T>(path: K): FieldInstance<T[K]>
47
-
48
- // Subform extraction
49
- getSubform<K extends keyof T>(path: K): FormInstance<T[K]>
50
-
51
- // State queries
52
- isDirty(): boolean
53
- isTouched(): boolean
54
- isValid(): boolean
55
-
56
- // Operations
57
- validate(): Promise<ValidationResult>
58
- reset(): void
59
- submit(): Promise<void>
60
-
61
- // Error access
62
- getErrors(path?: keyof T): ErrorMessage[]
63
- hasErrors(path?: keyof T): boolean
64
- }
65
- ```
66
-
67
- ### Field Instance Interface
68
- ```typescript
69
- interface FieldInstance<T> {
70
- // Reactive value with getter/setter
71
- value: Ref<T>
72
-
73
- // State
74
- touched: Ref<boolean>
75
- dirty: Ref<boolean>
76
- errors: Ref<ErrorMessage[]>
77
-
78
- // Handlers
79
- onBlur(): void
80
- onFocus(): void
81
-
82
- // Field-specific validation
83
- validate(): Promise<ValidationResult>
84
- }
85
- ```
86
-
87
- ## Directory Structure
88
-
89
- ```
90
- src/
91
- ├── types/
92
- │ ├── form.ts # Form instance types
93
- │ ├── field.ts # Field instance types
94
- │ ├── validation.ts # Validation types
95
- │ └── index.ts
96
- ├── core/
97
- │ ├── form-instance.ts # Form instance implementation
98
- │ ├── field-instance.ts # Field instance implementation
99
- │ ├── subform-instance.ts # Subform implementation
100
- │ └── validation-engine.ts # Validation logic
101
- ├── factories/
102
- │ └── create-form.ts # Main form factory
103
- ├── utils/
104
- │ ├── immutable.ts # Immutable helpers
105
- │ ├── path.ts # Object path utilities
106
- │ └── type-helpers.ts # TypeScript utility types
107
- └── index.ts # Main exports
108
- ```
109
-
110
- ## Core Features
111
-
112
- ### 1. Immutable State Management
113
- - Store initial data as immutable
114
- - Working copy is immutable from outside (readonly)
115
- - Computed getter/setter on working copy
116
- - Full clone on creation
117
-
118
- ### 2. Form State Tracking
119
- - **Touched**: Field has been interacted with at least once
120
- - **Dirty**: Computed property checking if value ≠ initial state
121
- - State propagation from subforms to parent forms
122
-
123
- ### 3. Validation System
124
- - **Strategies**: `onTouch`, `onFormOpen`, `none`, `preSubmit`
125
- - **Zod Integration**: Primary validation with Zod schemas
126
- - **Backend Validation**: API errors merged with Zod errors
127
- - **Error Bag Structure**:
128
- ```typescript
129
- {
130
- general: ErrorMessage[],
131
- propertyErrors: Record<string, ErrorMessage[]>
132
- }
133
- ```
134
-
135
- ### 4. Subform Support
136
- - Extract subforms with `getSubform(path)`
137
- - Type-safe subform instances
138
- - Automatic value propagation from subforms to parent
139
- - Reusable components across different parent forms
140
-
141
- ### 5. Array Functionality (Future)
142
- - `pushValue()`, `removeValue()`, `getKeys()`
143
- - Similar to vee-validate's field array API
144
-
145
- ## MVP Implementation Order
146
-
147
- ### Phase 1: Core Infrastructure
148
- 1. **Project Setup**
149
- - Package.json with proper exports
150
- - TypeScript configuration
151
- - Vite build setup
152
- - Basic documentation structure
153
-
154
- 2. **Type System**
155
- - Core interfaces and types
156
- - Validation types
157
- - Error handling types
158
-
159
- 3. **Basic Form Instance**
160
- - Form creation factory
161
- - Basic field management
162
- - Immutable state handling
163
-
164
- ### Phase 2: Essential Features
165
- 1. **Field Management**
166
- - Field instance implementation
167
- - Touch and dirty state tracking
168
- - Basic event handlers
169
-
170
- 2. **Validation Engine**
171
- - Zod integration
172
- - Error collection and formatting
173
- - Basic validation strategies
174
-
175
- 3. **Subform System**
176
- - Subform extraction
177
- - Type-safe subform instances
178
- - Value propagation
179
-
180
- ### Phase 3: Polish & Testing
181
- 1. **Edge Cases**
182
- - Error handling
183
- - Reset functionality
184
- - Validation edge cases
185
-
186
- 2. **Documentation**
187
- - API documentation
188
- - Usage examples
189
- - Migration guides
190
-
191
- 3. **Testing**
192
- - Unit tests
193
- - Integration tests
194
- - Type safety tests
195
-
196
- ## Key Benefits
197
-
198
- - **Type Safety**: Full TypeScript support with no type loss
199
- - **Immutable State**: Predictable state management
200
- - **Reusable Components**: Subforms enable component reuse
201
- - **Validation Flexibility**: Multiple validation strategies
202
- - **Performance**: Reactive updates only where needed
203
- - **Developer Experience**: Excellent IDE support and debugging
204
-
205
- ## Notes
206
-
207
- - No provide/inject pattern to avoid type information loss
208
- - Direct form instance passing maintains type safety
209
- - Subform extraction enables component reusability
210
- - Validation strategies can be overridden per field
211
- - Backend validation seamlessly integrates with Zod