@effect-app/vue-components 2.1.5 → 2.2.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.
Files changed (36) hide show
  1. package/dist/types/components/OmegaForm/InputProps.d.ts +1 -0
  2. package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +9 -2
  3. package/dist/types/components/OmegaForm/OmegaInput.vue.d.ts +1 -0
  4. package/dist/types/components/OmegaForm/OmegaInternalInput.vue.d.ts +4 -2
  5. package/dist/vue-components.es10.js +148 -145
  6. package/dist/vue-components.es18.js +10 -9
  7. package/dist/vue-components.es21.js +3 -3
  8. package/dist/vue-components.es22.js +1 -1
  9. package/dist/vue-components.es23.js +1 -1
  10. package/dist/vue-components.es36.js +1 -1
  11. package/dist/vue-components.es41.js +22 -10
  12. package/dist/vue-components.es42.js +5 -23
  13. package/dist/vue-components.es43.js +21 -5
  14. package/dist/vue-components.es44.js +25 -16
  15. package/dist/vue-components.es45.js +15 -23
  16. package/dist/vue-components.es46.js +7 -17
  17. package/dist/vue-components.es47.js +5 -12
  18. package/dist/vue-components.es48.js +19 -5
  19. package/dist/vue-components.es49.js +9 -19
  20. package/dist/vue-components.es50.js +31 -9
  21. package/dist/vue-components.es51.js +42 -25
  22. package/dist/vue-components.es52.js +16 -38
  23. package/dist/vue-components.es53.js +11 -26
  24. package/dist/vue-components.es54.js +1 -1
  25. package/dist/vue-components.es56.js +1 -1
  26. package/dist/vue-components.es58.js +3 -3
  27. package/dist/vue-components.es59.js +1 -1
  28. package/dist/vue-components.es6.js +27 -23
  29. package/dist/vue-components.es7.js +33 -36
  30. package/package.json +1 -1
  31. package/src/components/OmegaForm/InputProps.ts +1 -0
  32. package/src/components/OmegaForm/OmegaFormStuff.ts +12 -5
  33. package/src/components/OmegaForm/OmegaInput.vue +11 -2
  34. package/src/components/OmegaForm/OmegaInternalInput.vue +17 -35
  35. package/src/components/OmegaForm/OmegaWrapper.vue +0 -1
  36. package/src/components/OmegaForm/useOmegaForm.ts +41 -6
@@ -1,9 +1,9 @@
1
1
  (function(){"use strict";try{if(typeof document<"u"){var i=document.createElement("style");if(i.appendChild(document.createTextNode(".omega-input .v-input__details:has(.v-messages:empty){grid-template-rows:0fr;transition:all .2s}.omega-input .v-messages:empty{min-height:0}.omega-input .v-input__details:has(.v-messages){transition:all .2s;overflow:hidden;min-height:0;display:grid;grid-template-rows:1fr}.omega-input .v-messages{transition:all .2s}.omega-input .v-messages>*{transition-duration:0s!important}.omega-input [role=alert]:has(.v-messages:empty){padding:0}.omega-input .v-btn{cursor:pointer;width:auto;appearance:none;box-shadow:none;display:block;min-width:auto;height:auto;padding:.5em .5em .5em 1em}")),document.head.appendChild(i),window.customElements){const e=window.customElements.define;window.customElements.define=function(s,t){const n=t.prototype.connectedCallback;return t.prototype.connectedCallback=function(){if(n&&n.call(this),this.shadowRoot){const a=document.createElement("style");a.appendChild(document.createTextNode(".omega-input .v-input__details:has(.v-messages:empty){grid-template-rows:0fr;transition:all .2s}.omega-input .v-messages:empty{min-height:0}.omega-input .v-input__details:has(.v-messages){transition:all .2s;overflow:hidden;min-height:0;display:grid;grid-template-rows:1fr}.omega-input .v-messages{transition:all .2s}.omega-input .v-messages>*{transition-duration:0s!important}.omega-input [role=alert]:has(.v-messages:empty){padding:0}.omega-input .v-btn{cursor:pointer;width:auto;appearance:none;box-shadow:none;display:block;min-width:auto;height:auto;padding:.5em .5em .5em 1em}")),this.shadowRoot.appendChild(a)}},e.call(window.customElements,s,t)}}}}catch(e){console.error("vite-plugin-css-injected-by-js",e)}})();
2
- import { defineComponent as C, computed as a, getCurrentInstance as x, useId as V, onMounted as _, renderSlot as P, normalizeProps as m, guardReactiveProps as M, createElementVNode as N, normalizeClass as $, createBlock as q, createCommentVNode as B, unref as O, openBlock as k, mergeProps as I } from "vue";
3
- import { useStore as L } from "@tanstack/vue-form";
4
- import S from "./vue-components.es38.js";
2
+ import { defineComponent as _, computed as n, getCurrentInstance as x, useAttrs as P, useId as N, renderSlot as q, normalizeProps as i, guardReactiveProps as B, createElementVNode as $, normalizeClass as k, createBlock as I, createCommentVNode as L, unref as M, openBlock as S, mergeProps as w } from "vue";
3
+ import { useStore as A } from "@tanstack/vue-form";
4
+ import F from "./vue-components.es38.js";
5
5
 
6
- const E = /* @__PURE__ */ C({
6
+ const Z = /* @__PURE__ */ _({
7
7
  inheritAttrs: !1,
8
8
  __name: "OmegaInternalInput",
9
9
  props: {
@@ -14,62 +14,59 @@ const E = /* @__PURE__ */ C({
14
14
  type: { default: void 0 },
15
15
  validators: { default: void 0 },
16
16
  required: { type: Boolean, default: void 0 },
17
+ inputClass: { default: void 0 },
17
18
  register: {},
18
19
  options: { default: void 0 }
19
20
  },
20
- setup(d) {
21
- const e = d, i = a(() => e.required ?? e?.meta?.required), f = x()?.appContext.components.VTextField, o = V(), r = e.field, s = L(r.store, (t) => t), p = a(() => e.type ? e.type : e.meta?.type === "string" ? e.meta.format === "email" ? "email" : "string" : e.meta?.type || "unknown");
22
- e.register(a(() => ({ name: e.field.name, label: e.label, id: o })));
23
- const c = a(() => s.value.value), g = a(() => s.value.meta.errors ?? []), u = a(
21
+ setup(u) {
22
+ const e = u, s = n(() => e.required ?? e?.meta?.required), m = x()?.appContext.components.VTextField, d = P(), p = n(() => {
23
+ const { class: t, ...r } = d;
24
+ return r;
25
+ }), l = N(), f = e.field, c = A(f.store, (t) => t), g = n(() => e.type ? e.type : e.meta?.type === "string" ? e.meta.format === "email" ? "email" : "string" : e.meta?.type || "unknown");
26
+ e.register(n(() => ({ name: e.field.name, label: e.label, id: l })));
27
+ const h = n(() => c.value.meta.errors ?? []), o = n(
24
28
  () => (
25
29
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
- g.value.map((t) => t?.message).filter(Boolean)
30
+ h.value.map((t) => t?.message).filter(Boolean)
27
31
  )
28
32
  ), v = (t) => t == null || t === !1 || t === "" || Number.isNaN(t), y = (t) => {
29
33
  v(t) && e.meta?.type !== "boolean" && e.meta?.nullableOrUndefined ? e.field.handleChange(
30
34
  e.meta.nullableOrUndefined === "undefined" ? void 0 : null
31
- ) : e.field.handleChange(t), e.field.setMeta((n) => ({ ...n, errorMap: { ...n.errorMap, onSubmit: void 0 } }));
32
- };
33
- _(() => {
34
- if (c.value === void 0) {
35
- const t = s.value.meta.isDirty;
36
- if (r.setMeta((n) => ({ ...n, isDirty: t })), i.value) return;
37
- e.meta?.nullableOrUndefined === "null" ? r.setValue(null) : e.meta?.nullableOrUndefined === "undefined" ? r.setValue(void 0) : e.meta?.type === "string" ? r.setValue("") : e.meta?.type === "number" || e.meta?.type === "boolean" && r.setValue(!1);
38
- }
39
- });
40
- const h = (t) => {
41
- const n = {
42
- get(F, b, R) {
35
+ ) : e.field.handleChange(t), e.field.setMeta((r) => ({ ...r, errorMap: { ...r.errorMap, onSubmit: void 0 } }));
36
+ }, C = (t) => {
37
+ const r = {
38
+ get(V, b, z) {
43
39
  return b === "handleChange" ? y : Reflect.get(...arguments);
44
40
  }
45
41
  };
46
- return new Proxy(t, n);
47
- }, l = a(() => ({
42
+ return new Proxy(t, r);
43
+ }, a = n(() => ({
48
44
  inputProps: {
49
- id: o,
50
- required: i.value,
45
+ id: l,
46
+ required: s.value,
51
47
  minLength: e.meta?.type === "string" && e.meta?.minLength,
52
48
  maxLength: e.meta?.type === "string" && e.meta?.maxLength,
53
49
  max: e.meta?.type === "number" && e.meta?.maximum,
54
50
  min: e.meta?.type === "number" && e.meta?.minimum,
55
- errorMessages: u.value,
56
- error: !!u.value.length,
57
- type: p.value,
58
- label: `${e.label}${i.value ? " *" : ""}`,
59
- options: e.options
51
+ errorMessages: o.value,
52
+ error: !!o.value.length,
53
+ type: g.value,
54
+ label: `${e.label}${s.value ? " *" : ""}`,
55
+ options: e.options,
56
+ inputClass: e.inputClass
60
57
  },
61
58
  state: e.state,
62
- field: h(e.field)
59
+ field: C(e.field)
63
60
  }));
64
- return (t, n) => P(t.$slots, "default", m(M({ ...l.value.inputProps, field: l.value.field, state: l.value.state })), () => [
65
- N("div", {
66
- class: $(t.$attrs.class)
61
+ return (t, r) => q(t.$slots, "default", i(B({ ...a.value.inputProps, field: a.value.field, state: a.value.state })), () => [
62
+ $("div", {
63
+ class: k(t.$attrs.class)
67
64
  }, [
68
- O(f) ? (k(), q(S, m(I({ key: 0 }, { ...t.$attrs, ...l.value })), null, 16)) : B("", !0)
65
+ M(m) ? (S(), I(F, i(w({ key: 0 }, { ...p.value, ...a.value, class: e.inputClass })), null, 16)) : L("", !0)
69
66
  ], 2)
70
67
  ]);
71
68
  }
72
69
  });
73
70
  export {
74
- E as default
71
+ Z as default
75
72
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-app/vue-components",
3
- "version": "2.1.5",
3
+ "version": "2.2.0",
4
4
  "peerDependencies": {
5
5
  "@mdi/js": "^7.4.47",
6
6
  "effect": "^3.18.0",
@@ -39,6 +39,7 @@ export type InputProps<From extends Record<PropertyKey, any>, TName extends Deep
39
39
  error: boolean
40
40
  label: string
41
41
  type: string
42
+ inputClass: string | undefined | null
42
43
  }
43
44
  field: OmegaFieldInternalApi<From, TName>
44
45
  /** be sure to use this state and not `field.state` as it is not reactive */
@@ -15,16 +15,23 @@ export type Leaves<T, Path extends string = ""> = T extends ReadonlyArray<infer
15
15
  }[keyof T]
16
16
 
17
17
  // Helper type to make array indices flexible - accepts both [number] and numeric literals [0], [1], etc.
18
- export type FlexibleArrayPath<T extends string> = T extends `${infer Before}[number]${infer After}`
19
- ? T | `${Before}[${number}]${FlexibleArrayPath<After>}`
20
- : T
18
+ // Simplified: if there's a [] in the path, just use TName to avoid excessive type complexity
19
+ export type FlexibleArrayPath<T extends string> = T extends `${string}[${string}]${string}` ? T
20
+ : never
21
21
 
22
22
  export type BaseProps<From, TName extends DeepKeys<From> = DeepKeys<From>> = {
23
23
  /** Will fallback to i18n when not specified */
24
24
  label?: string
25
25
  validators?: FieldValidators<From>
26
- // disabled FlexibleArrayPath as it causes excessive complexity in type resolution inside user projects
27
- name: TName // & FlexibleArrayPath<Leaves<From>>
26
+ // Use FlexibleArrayPath: if name contains [], just use TName; otherwise intersect with Leaves<From>
27
+ name: FlexibleArrayPath<TName> extends never ? Leaves<From> : TName
28
+ /**
29
+ * Optional class to apply to the input element.
30
+ * - If a string is provided, it will be used instead of the general class
31
+ * - If null is provided, no class will be applied (neither inputClass nor general class)
32
+ * - If undefined (not provided), the general class will be used
33
+ */
34
+ inputClass?: string | null
28
35
  }
29
36
 
30
37
  export type TypesWithOptions = "radio" | "select" | "multiple" | "autocomplete" | "autocompletemultiple"
@@ -10,7 +10,7 @@
10
10
  <template #default="{ field, state }">
11
11
  <OmegaInternalInput
12
12
  v-if="meta"
13
- v-bind="{ ...$attrs, ...$props }"
13
+ v-bind="{ ...$attrs, ...$props, inputClass: computedClass }"
14
14
  :field="field"
15
15
  :state="state"
16
16
  :register="form.registerField"
@@ -35,7 +35,7 @@
35
35
  "
36
36
  >
37
37
  import { type DeepKeys } from "@tanstack/vue-form"
38
- import { computed, inject, type Ref } from "vue"
38
+ import { computed, inject, type Ref, useAttrs } from "vue"
39
39
  import { useIntl } from "../../utils"
40
40
  import { type FieldMeta, generateInputStandardSchemaFromFieldMeta, type OmegaInputPropsBase } from "./OmegaFormStuff"
41
41
  import OmegaInternalInput from "./OmegaInternalInput.vue"
@@ -49,6 +49,15 @@ defineOptions({
49
49
  inheritAttrs: false
50
50
  })
51
51
 
52
+ const attrs = useAttrs()
53
+
54
+ // Compute the class to use based on inputClass prop
55
+ const computedClass = computed(() => {
56
+ if (props.inputClass === null) return undefined
57
+ if (props.inputClass !== undefined) return props.inputClass
58
+ return attrs.class as string | undefined
59
+ })
60
+
52
61
  const getMetaFromArray = inject<Ref<(name: string) => FieldMeta | null> | null>(
53
62
  "getMetaFromArray",
54
63
  null
@@ -3,7 +3,7 @@
3
3
  <div :class="$attrs.class">
4
4
  <OmegaInputVuetify
5
5
  v-if="vuetified"
6
- v-bind="{ ...$attrs, ...inputProps }"
6
+ v-bind="{ ...attrsWithoutClass, ...inputProps, class: props.inputClass }"
7
7
  />
8
8
  </div>
9
9
  </slot>
@@ -14,8 +14,8 @@
14
14
  lang="ts"
15
15
  generic="From extends Record<PropertyKey, any>, Name extends DeepKeys<From>"
16
16
  >
17
- import { type DeepKeys, type DeepValue, useStore } from "@tanstack/vue-form"
18
- import { computed, type ComputedRef, getCurrentInstance, onMounted, useId } from "vue"
17
+ import { type DeepKeys, useStore } from "@tanstack/vue-form"
18
+ import { computed, type ComputedRef, getCurrentInstance, useAttrs, useId } from "vue"
19
19
  import type { InputProps, OmegaFieldInternalApi } from "./InputProps"
20
20
  import type { FieldValidators, MetaRecord, NestedKeyOf, TypeOverride } from "./OmegaFormStuff"
21
21
  import OmegaInputVuetify from "./OmegaInputVuetify.vue"
@@ -33,6 +33,7 @@ const props = withDefaults(
33
33
  type?: TypeOverride
34
34
  validators?: FieldValidators<From>
35
35
  required?: boolean
36
+ inputClass?: string | null
36
37
 
37
38
  register: (
38
39
  field: ComputedRef<{
@@ -49,7 +50,8 @@ const props = withDefaults(
49
50
  required: undefined,
50
51
  type: undefined,
51
52
  options: undefined,
52
- validators: undefined
53
+ validators: undefined,
54
+ inputClass: undefined
53
55
  }
54
56
  )
55
57
 
@@ -57,6 +59,13 @@ const isRequired = computed(() => props.required ?? props?.meta?.required)
57
59
 
58
60
  const instance = getCurrentInstance()
59
61
  const vuetified = instance?.appContext.components["VTextField"]
62
+ const attrs = useAttrs()
63
+
64
+ // Create attrs without the class property to avoid duplication
65
+ const attrsWithoutClass = computed(() => {
66
+ const { class: _, ...rest } = attrs
67
+ return rest
68
+ })
60
69
 
61
70
  const id = useId()
62
71
 
@@ -75,7 +84,6 @@ const fieldType = computed(() => {
75
84
 
76
85
  props.register(computed(() => ({ name: props.field.name, label: props.label, id })))
77
86
 
78
- const fieldValue = computed(() => fieldState.value.value)
79
87
  // workaround strange tanstack form issue where the errors key becomes undefined ???
80
88
  const _errors = computed(() => fieldState.value.meta.errors ?? [])
81
89
  const errors = computed(() =>
@@ -112,35 +120,8 @@ const handleChange: OmegaFieldInternalApi<From, Name>["handleChange"] = (value)
112
120
  props.field.setMeta((m) => ({ ...m, errorMap: { ...m.errorMap, onSubmit: undefined } }))
113
121
  }
114
122
 
115
- // TODO: it would be cleaner when default values are handled in the form initialization via Schema or by the one using the form component..
116
- onMounted(() => {
117
- // Initialize field value on mount if it doesn't exist
118
- if (fieldValue.value === undefined) {
119
- const isDirty = fieldState.value.meta.isDirty
120
- // make sure we restore the previous dirty state..
121
- fieldApi.setMeta((_) => ({ ..._, isDirty }))
122
-
123
- if (isRequired.value) return
124
-
125
- // Set appropriate default value based on field type and nullability
126
- if (props.meta?.nullableOrUndefined === "null") {
127
- fieldApi.setValue(null as DeepValue<From, Name>)
128
- } else if (props.meta?.nullableOrUndefined === "undefined") {
129
- fieldApi.setValue(undefined as DeepValue<From, Name>)
130
- } else {
131
- // For required fields, initialize with appropriate empty value
132
- if (props.meta?.type === "string") {
133
- fieldApi.setValue("" as DeepValue<From, Name>)
134
- } else if (props.meta?.type === "number") {
135
- // Don't initialize number fields to avoid setting them to 0
136
- // Leave as undefined so validation will catch it
137
- } else if (props.meta?.type === "boolean") {
138
- fieldApi.setValue(false as DeepValue<From, Name>)
139
- }
140
- // For other types, leave undefined so validation will catch missing required fields
141
- }
142
- }
143
- })
123
+ // Note: Default value normalization (converting empty strings to null/undefined for nullable fields)
124
+ // is now handled at the form level in useOmegaForm, not here in the component
144
125
 
145
126
  const wrapField = (field: OmegaFieldInternalApi<From, Name>) => {
146
127
  const handler3 = {
@@ -168,7 +149,8 @@ const inputProps: ComputedRef<InputProps<From, Name>> = computed(() => ({
168
149
  error: !!errors.value.length,
169
150
  type: fieldType.value,
170
151
  label: `${props.label}${isRequired.value ? " *" : ""}`,
171
- options: props.options
152
+ options: props.options,
153
+ inputClass: props.inputClass
172
154
  },
173
155
 
174
156
  state: props.state,
@@ -30,7 +30,6 @@
30
30
  */
31
31
  /* eslint-disable @typescript-eslint/no-explicit-any */
32
32
  import { useStore } from "@tanstack/vue-form"
33
- import { defineSlots } from "vue"
34
33
  import { usePreventClose } from "./blockDialog"
35
34
  import { getOmegaStore } from "./getOmegaStore"
36
35
  import { type DefaultTypeProps, type OmegaFormApi, type OmegaFormState } from "./OmegaFormStuff"
@@ -783,14 +783,50 @@ export const useOmegaForm = <
783
783
  return target
784
784
  }
785
785
 
786
+ // Normalize default values based on schema metadata
787
+ // Convert empty strings to null/undefined for nullable fields
788
+ // Also initialize missing nullable fields with null/undefined
789
+ const normalizeDefaultValues = (values: Partial<From>): Partial<From> => {
790
+ const normalized: any = { ...values }
791
+
792
+ // Process all fields in the schema metadata
793
+ for (const key in meta) {
794
+ const fieldMeta = meta[key as keyof typeof meta]
795
+ const value = normalized[key]
796
+
797
+ // Check if the value is falsy (but not boolean false or zero)
798
+ const isFalsyButNotZero = value == null || value === false || value === "" || Number.isNaN(value)
799
+ const isFalsy = isFalsyButNotZero && value !== false && value !== 0
800
+
801
+ if (
802
+ fieldMeta
803
+ && !fieldMeta.required
804
+ && fieldMeta.nullableOrUndefined
805
+ && fieldMeta.type !== "boolean"
806
+ ) {
807
+ // If value is missing or falsy, set to null or undefined based on schema
808
+ if (value === undefined || isFalsy) {
809
+ normalized[key] = fieldMeta.nullableOrUndefined === "undefined" ? undefined : null
810
+ }
811
+ }
812
+ }
813
+
814
+ return normalized
815
+ }
816
+
786
817
  const defaultValues = computed(() => {
818
+ // Normalize tanstack default values at the beginning
819
+ const normalizedTanstackDefaults = tanstackFormOptions?.defaultValues
820
+ ? normalizeDefaultValues(tanstackFormOptions.defaultValues)
821
+ : undefined
822
+
787
823
  if (
788
- tanstackFormOptions?.defaultValues
824
+ normalizedTanstackDefaults
789
825
  && !omegaConfig?.persistency?.overrideDefaultValues
790
826
  ) {
791
827
  // defaultValues from tanstack are not partial,
792
- // so if ovverrideDefaultValues is false we simply return them
793
- return tanstackFormOptions?.defaultValues
828
+ // so if ovverrideDefaultValues is false we return the normalized values
829
+ return normalizedTanstackDefaults
794
830
  }
795
831
 
796
832
  // we are here because there are no default values from tankstack
@@ -839,12 +875,11 @@ export const useOmegaForm = <
839
875
  // to be sure we have a valid object at the end of the gathering process
840
876
  defValuesPatch ??= {}
841
877
 
842
- if (tanstackFormOptions?.defaultValues == undefined) {
878
+ if (!normalizedTanstackDefaults) {
843
879
  // we just return what we gathered from the query/storage
844
880
  return defValuesPatch
845
881
  } else {
846
- const startingDefValues = tanstackFormOptions?.defaultValues
847
- return deepMerge(startingDefValues, defValuesPatch)
882
+ return deepMerge(normalizedTanstackDefaults, defValuesPatch)
848
883
  }
849
884
  })
850
885