@effect-app/vue-components 4.0.0-beta.21 → 4.0.0-beta.22

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,194 +1,34 @@
1
- import { defineComponent as k, resolveComponent as b, createElementBlock as v, openBlock as a, createBlock as n, createCommentVNode as u, resolveDynamicComponent as g, mergeProps as o, createSlots as d, withCtx as r, renderSlot as m, normalizeProps as s, guardReactiveProps as P, unref as C, Fragment as V, renderList as U } from "vue";
2
- import { getInputType as c } from "./vue-components.es12.js";
3
- const w = /* @__PURE__ */ k({
4
- inheritAttrs: !1,
5
- __name: "OmegaInputVuetify",
1
+ import { defineComponent as m, createElementBlock as d, openBlock as u, withModifiers as f, createElementVNode as l, unref as s, renderSlot as a } from "vue";
2
+ import { useStore as b } from "@tanstack/vue-form";
3
+ import { usePreventClose as p } from "./vue-components.es11.js";
4
+ import { getOmegaStore as c } from "./vue-components.es54.js";
5
+ const S = ["disabled"], V = /* @__PURE__ */ m({
6
+ __name: "OmegaWrapper",
6
7
  props: {
7
- inputProps: {},
8
- field: {},
9
- state: {}
8
+ form: {},
9
+ disabled: { type: Boolean },
10
+ subscribe: {}
10
11
  },
11
- emits: ["focus", "blur"],
12
- setup(e) {
13
- return (l, i) => {
14
- const y = b("v-text-field"), p = b("v-textarea"), f = b("v-radio"), h = b("v-radio-group"), q = b("v-select"), $ = b("v-autocomplete");
15
- return a(), v("div", {
16
- class: "omega-input",
17
- onFocusout: i[4] || (i[4] = (t) => l.$emit("blur", t)),
18
- onFocusin: i[5] || (i[5] = (t) => l.$emit("focus", t))
12
+ setup(o) {
13
+ const e = o, i = b(
14
+ e.form.store,
15
+ (t) => t.isSubmitting
16
+ ), n = c(
17
+ e.form,
18
+ e.subscribe
19
+ );
20
+ return e.form.ignorePreventCloseEvents || p(() => e.form.useStore((t) => t.isDirty)), (t, r) => (u(), d("form", {
21
+ novalidate: "",
22
+ onSubmit: r[0] || (r[0] = f((v) => o.form.handleSubmit(), ["prevent", "stop"]))
23
+ }, [
24
+ l("fieldset", {
25
+ disabled: s(i) || o.disabled
19
26
  }, [
20
- e.inputProps.type === "boolean" || e.inputProps.type === "switch" ? (a(), n(g(e.inputProps.type === "boolean" ? "v-checkbox" : "v-switch"), o({
21
- key: 0,
22
- id: e.inputProps.id,
23
- name: e.field.name,
24
- label: e.inputProps.label,
25
- "error-messages": e.inputProps.errorMessages,
26
- error: e.inputProps.error,
27
- ripple: ""
28
- }, l.$attrs, {
29
- "model-value": e.state.value,
30
- onChange: i[0] || (i[0] = (t) => e.field.handleChange(t.target.checked))
31
- }), d({ _: 2 }, [
32
- l.$slots.label ? {
33
- name: "label",
34
- fn: r(() => [
35
- m(l.$slots, "label", s(P({ required: e.inputProps.required, id: e.inputProps.id, label: e.inputProps.label })))
36
- ]),
37
- key: "0"
38
- } : void 0
39
- ]), 1040, ["id", "name", "label", "error-messages", "error", "model-value"])) : u("", !0),
40
- e.inputProps.type === "email" || e.inputProps.type === "string" || e.inputProps.type === "password" || e.inputProps.type === "date" ? (a(), n(y, o({
41
- key: 1,
42
- id: e.inputProps.id,
43
- required: e.inputProps.required,
44
- "min-length": e.inputProps.minLength,
45
- "max-length": e.inputProps.maxLength,
46
- type: C(c)(e.inputProps.type),
47
- name: e.field.name,
48
- label: e.inputProps.label,
49
- "error-messages": e.inputProps.errorMessages,
50
- error: e.inputProps.error
51
- }, l.$attrs, {
52
- "model-value": e.state.value,
53
- "onUpdate:modelValue": e.field.handleChange
54
- }), d({ _: 2 }, [
55
- l.$slots.label ? {
56
- name: "label",
57
- fn: r(() => [
58
- m(l.$slots, "label", s(P({ required: e.inputProps.required, id: e.inputProps.id, label: e.inputProps.label })))
59
- ]),
60
- key: "0"
61
- } : void 0
62
- ]), 1040, ["id", "required", "min-length", "max-length", "type", "name", "label", "error-messages", "error", "model-value", "onUpdate:modelValue"])) : u("", !0),
63
- e.inputProps.type === "text" ? (a(), n(p, o({
64
- key: 2,
65
- id: e.inputProps.id,
66
- required: e.inputProps.required,
67
- "min-length": e.inputProps.minLength,
68
- "max-length": e.inputProps.maxLength,
69
- name: e.field.name,
70
- label: e.inputProps.label,
71
- "error-messages": e.inputProps.errorMessages,
72
- error: e.inputProps.error
73
- }, l.$attrs, {
74
- "model-value": e.state.value,
75
- "onUpdate:modelValue": e.field.handleChange
76
- }), d({ _: 2 }, [
77
- l.$slots.label ? {
78
- name: "label",
79
- fn: r(() => [
80
- m(l.$slots, "label", s(P({ required: e.inputProps.required, id: e.inputProps.id, label: e.inputProps.label })))
81
- ]),
82
- key: "0"
83
- } : void 0
84
- ]), 1040, ["id", "required", "min-length", "max-length", "name", "label", "error-messages", "error", "model-value", "onUpdate:modelValue"])) : u("", !0),
85
- e.inputProps.type === "number" || e.inputProps.type === "range" ? (a(), n(g(e.inputProps.type === "range" ? "v-slider" : "v-text-field"), o({
86
- key: 3,
87
- id: e.inputProps.id,
88
- required: e.inputProps.required,
89
- min: e.inputProps.min,
90
- max: e.inputProps.max,
91
- type: e.inputProps.type,
92
- name: e.field.name,
93
- label: e.inputProps.label,
94
- "error-messages": e.inputProps.errorMessages,
95
- error: e.inputProps.error
96
- }, l.$attrs, {
97
- "model-value": e.state.value,
98
- "onUpdate:modelValue": i[1] || (i[1] = (t) => {
99
- t || t === 0 ? e.field.handleChange(Number(t)) : e.field.handleChange(void 0);
100
- })
101
- }), d({ _: 2 }, [
102
- l.$slots.label ? {
103
- name: "label",
104
- fn: r(() => [
105
- m(l.$slots, "label", s(P({ required: e.inputProps.required, id: e.inputProps.id, label: e.inputProps.label })))
106
- ]),
107
- key: "0"
108
- } : void 0
109
- ]), 1040, ["id", "required", "min", "max", "type", "name", "label", "error-messages", "error", "model-value"])) : u("", !0),
110
- e.inputProps.type === "radio" ? (a(), n(h, o({
111
- key: 4,
112
- id: e.inputProps.id,
113
- name: e.field.name,
114
- label: e.inputProps.label,
115
- "error-messages": e.inputProps.errorMessages,
116
- error: e.inputProps.error
117
- }, l.$attrs, {
118
- "model-value": e.state.value,
119
- "onUpdate:modelValue": e.field.handleChange
120
- }), d({
121
- default: r(() => [
122
- (a(!0), v(V, null, U(e.inputProps.options, (t) => (a(), n(f, {
123
- key: t.value,
124
- label: t.title,
125
- value: t.value
126
- }, null, 8, ["label", "value"]))), 128))
127
- ]),
128
- _: 2
129
- }, [
130
- l.$slots.label ? {
131
- name: "label",
132
- fn: r(() => [
133
- m(l.$slots, "label", s(P({ required: e.inputProps.required, id: e.inputProps.id, label: e.inputProps.label })))
134
- ]),
135
- key: "0"
136
- } : void 0
137
- ]), 1040, ["id", "name", "label", "error-messages", "error", "model-value", "onUpdate:modelValue"])) : u("", !0),
138
- e.inputProps.type === "select" || e.inputProps.type === "multiple" ? (a(), n(q, o({
139
- key: 5,
140
- id: e.inputProps.id,
141
- clearable: e.inputProps.type === "select",
142
- required: e.inputProps.required,
143
- multiple: e.inputProps.type === "multiple",
144
- chips: e.inputProps.type === "multiple",
145
- name: e.field.name,
146
- label: e.inputProps.label,
147
- items: e.inputProps.options,
148
- "error-messages": e.inputProps.errorMessages,
149
- error: e.inputProps.error
150
- }, l.$attrs, {
151
- "model-value": e.state.value,
152
- onClear: i[2] || (i[2] = (t) => e.field.handleChange(void 0)),
153
- "onUpdate:modelValue": e.field.handleChange
154
- }), d({ _: 2 }, [
155
- l.$slots.label ? {
156
- name: "label",
157
- fn: r(() => [
158
- m(l.$slots, "label", s(P({ required: e.inputProps.required, id: e.inputProps.id, label: e.inputProps.label })))
159
- ]),
160
- key: "0"
161
- } : void 0
162
- ]), 1040, ["id", "clearable", "required", "multiple", "chips", "name", "label", "items", "error-messages", "error", "model-value", "onUpdate:modelValue"])) : u("", !0),
163
- e.inputProps.type === "autocomplete" || e.inputProps.type === "autocompletemultiple" ? (a(), n($, o({
164
- key: 6,
165
- id: e.inputProps.id,
166
- clearable: e.inputProps.type === "autocomplete",
167
- multiple: e.inputProps.type === "autocompletemultiple",
168
- required: e.inputProps.required,
169
- name: e.field.name,
170
- label: e.inputProps.label,
171
- items: e.inputProps.options,
172
- "error-messages": e.inputProps.errorMessages,
173
- error: e.inputProps.error,
174
- chips: e.inputProps.type === "autocompletemultiple"
175
- }, l.$attrs, {
176
- "model-value": e.state.value,
177
- onClear: i[3] || (i[3] = (t) => e.field.handleChange(void 0)),
178
- "onUpdate:modelValue": e.field.handleChange
179
- }), d({ _: 2 }, [
180
- l.$slots.label ? {
181
- name: "label",
182
- fn: r(() => [
183
- m(l.$slots, "label", s(P({ required: e.inputProps.required, id: e.inputProps.id, label: e.inputProps.label })))
184
- ]),
185
- key: "0"
186
- } : void 0
187
- ]), 1040, ["id", "clearable", "multiple", "required", "name", "label", "items", "error-messages", "error", "chips", "model-value", "onUpdate:modelValue"])) : u("", !0)
188
- ], 32);
189
- };
27
+ a(t.$slots, "default", { subscribedValues: s(n) }, void 0, !0)
28
+ ], 8, S)
29
+ ], 32));
190
30
  }
191
31
  });
192
32
  export {
193
- w as default
33
+ V as default
194
34
  };
@@ -1,5 +1,5 @@
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 o from "./vue-components.es39.js";
2
+ import o from "./vue-components.es30.js";
3
3
 
4
4
  export {
5
5
  o as default
@@ -1,7 +1,7 @@
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
2
  import { defineComponent as k, computed as a, getCurrentInstance as q, useAttrs as B, useSlots as S, useId as w, renderSlot as p, normalizeProps as s, guardReactiveProps as f, createElementVNode as F, normalizeClass as I, createBlock as L, createCommentVNode as A, unref as O, openBlock as R, mergeProps as V, createSlots as z, withCtx as T } from "vue";
3
3
  import { useStore as c } from "@tanstack/vue-form";
4
- import U from "./vue-components.es39.js";
4
+ import U from "./vue-components.es30.js";
5
5
 
6
6
  const J = /* @__PURE__ */ k({
7
7
  inheritAttrs: !1,
@@ -18,8 +18,8 @@ const J = /* @__PURE__ */ k({
18
18
  register: {},
19
19
  options: { default: void 0 }
20
20
  },
21
- setup(g) {
22
- const e = g, l = a(() => e.required ?? e?.meta?.required), v = q()?.appContext.components.VTextField, o = B(), y = S(), h = a(() => {
21
+ setup(v) {
22
+ const e = v, l = a(() => e.required ?? e?.meta?.required), g = q()?.appContext.components.VTextField, o = B(), y = S(), h = a(() => {
23
23
  const { class: t, ...n } = o;
24
24
  return n;
25
25
  }), b = a(() => o.class), i = w(), m = e.field;
@@ -48,8 +48,8 @@ const J = /* @__PURE__ */ k({
48
48
  required: l.value,
49
49
  minLength: e.meta?.type === "string" && e.meta?.minLength,
50
50
  maxLength: e.meta?.type === "string" && e.meta?.maxLength,
51
- max: e.meta?.type === "number" && (e.meta?.maximum ?? (typeof e.meta?.exclusiveMaximum == "number" && e.meta.exclusiveMaximum - 1)),
52
- min: e.meta?.type === "number" && (e.meta?.minimum ?? (typeof e.meta?.exclusiveMinimum == "number" && e.meta.exclusiveMinimum + 1)),
51
+ max: e.meta?.type === "number" ? e.meta?.maximum ?? (typeof e.meta?.exclusiveMaximum == "number" ? e.meta.exclusiveMaximum - 1 : void 0) : void 0,
52
+ min: e.meta?.type === "number" ? e.meta?.minimum ?? (typeof e.meta?.exclusiveMinimum == "number" ? e.meta.exclusiveMinimum + 1 : void 0) : void 0,
53
53
  errorMessages: u.value,
54
54
  error: !!u.value.length,
55
55
  type: x.value,
@@ -65,7 +65,7 @@ const J = /* @__PURE__ */ k({
65
65
  F("div", {
66
66
  class: I(b.value)
67
67
  }, [
68
- O(v) ? (R(), L(U, s(V({ key: 0 }, { ...h.value, ...r.value, class: e.inputClass })), z({ _: 2 }, [
68
+ O(g) ? (R(), L(U, s(V({ key: 0 }, { ...h.value, ...r.value, class: e.inputClass })), z({ _: 2 }, [
69
69
  t.$slots.label ? {
70
70
  name: "label",
71
71
  fn: T((d) => [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-app/vue-components",
3
- "version": "4.0.0-beta.21",
3
+ "version": "4.0.0-beta.22",
4
4
  "peerDependencies": {
5
5
  "@mdi/js": "^7.4.47",
6
6
  "effect": "^4.0.0-beta.36",
@@ -45,7 +45,11 @@
45
45
  "types": "./dist/types/index.d.ts",
46
46
  "import": "./dist/vue-components.es.js"
47
47
  },
48
- "./dist/vue-components.css": "./dist/vue-components.css"
48
+ "./dist/vue-components.css": "./dist/vue-components.css",
49
+ "./reset.css": {
50
+ "development": "./src/reset.css",
51
+ "default": "./dist/reset.css"
52
+ }
49
53
  },
50
54
  "dependencies": {
51
55
  "@opentelemetry/api": "^1.9.0",
@@ -53,13 +57,13 @@
53
57
  "highlight.js": "^11.11.1",
54
58
  "mitt": "^3.0.1",
55
59
  "vue3-highlightjs": "^1.0.5",
56
- "@effect-app/vue": "4.0.0-beta.21",
57
- "effect-app": "4.0.0-beta.21"
60
+ "@effect-app/vue": "4.0.0-beta.22",
61
+ "effect-app": "4.0.0-beta.22"
58
62
  },
59
63
  "scripts": {
60
64
  "check": "vue-tsc",
61
65
  "build": "pnpm build:run",
62
- "build:run": "rimraf dist && vue-tsc && vite build",
66
+ "build:run": "rimraf dist && vue-tsc && vite build && cp src/reset.css dist/reset.css",
63
67
  "docs:dev": "vitepress dev docs",
64
68
  "docs:build": "vitepress build docs",
65
69
  "docs:serve": "vitepress serve docs",
@@ -2,11 +2,25 @@ import { Effect, Option, type Record, S } from "effect-app"
2
2
  /* eslint-disable @typescript-eslint/no-explicit-any */
3
3
  import { type DeepKeys, type DeepValue, type FieldAsyncValidateOrFn, type FieldValidateOrFn, type FormApi, type FormAsyncValidateOrFn, type FormOptions, type FormState, type FormValidateOrFn, type StandardSchemaV1, type VueFormApi } from "@tanstack/vue-form"
4
4
  import { isObject } from "@vueuse/core"
5
- import type * as Fiber from "effect/Fiber"
5
+ import type { Fiber as EffectFiber } from "effect/Fiber"
6
6
  import { getTransformationFrom, useIntl } from "../../utils"
7
7
  import { type OmegaFieldInternalApi } from "./InputProps"
8
8
  import { type OF, type OmegaFormReturn } from "./useOmegaForm"
9
9
 
10
+ const legacyTagWarningEmittedFor = new Set<string>()
11
+ type GlobalThisWithOptionalProcess = typeof globalThis & {
12
+ process?: {
13
+ env?: {
14
+ NODE_ENV?: string
15
+ }
16
+ }
17
+ }
18
+
19
+ const isDevelopmentEnvironment = () => {
20
+ const process = (globalThis as GlobalThisWithOptionalProcess).process
21
+ return process?.env?.NODE_ENV !== "production"
22
+ }
23
+
10
24
  export type FieldPath<T> = unknown extends T ? string
11
25
  // technically we cannot have primitive at the root
12
26
  : T extends string | boolean | number | null | undefined | symbol | bigint ? ""
@@ -144,7 +158,7 @@ export type FormProps<From, To> =
144
158
  formApi: OmegaFormParams<From, To>
145
159
  meta: any
146
160
  value: To
147
- }) => Promise<any> | Fiber.Fiber<any, any> | Effect.Effect<unknown, any, never>
161
+ }) => Promise<any> | EffectFiber<any, any> | Effect.Effect<unknown, any, never>
148
162
  }
149
163
 
150
164
  export type OmegaFormParams<From, To> = FormApi<
@@ -268,6 +282,10 @@ export type BooleanFieldMeta = BaseFieldMeta & {
268
282
  type: "boolean"
269
283
  }
270
284
 
285
+ export type DateFieldMeta = BaseFieldMeta & {
286
+ type: "date"
287
+ }
288
+
271
289
  export type UnknownFieldMeta = BaseFieldMeta & {
272
290
  type: "unknown"
273
291
  }
@@ -278,6 +296,7 @@ export type FieldMeta =
278
296
  | SelectFieldMeta
279
297
  | MultipleFieldMeta
280
298
  | BooleanFieldMeta
299
+ | DateFieldMeta
281
300
  | UnknownFieldMeta
282
301
 
283
302
  export type MetaRecord<T = string> = {
@@ -321,6 +340,17 @@ const unwrapDeclaration = (property: S.AST.AST): S.AST.AST => {
321
340
 
322
341
  const isNullishType = (property: S.AST.AST) => S.AST.isUndefined(property) || S.AST.isNull(property)
323
342
 
343
+ /**
344
+ * Unwrap a single-element Union to its inner type if it's a Literal.
345
+ * After AST.toType, S.Struct({ _tag: S.Literal("X") }) produces Union([Literal("X")])
346
+ * instead of bare Literal("X") like S.TaggedStruct does.
347
+ * TODO: remove after manual _tag deprecation
348
+ */
349
+ const unwrapSingleLiteralUnion = (ast: S.AST.AST): S.AST.AST =>
350
+ S.AST.isUnion(ast) && ast.types.length === 1 && S.AST.isLiteral(ast.types[0]!)
351
+ ? ast.types[0]!
352
+ : ast
353
+
324
354
  const getNullableOrUndefined = (property: S.AST.AST) =>
325
355
  S.AST.isUnion(property)
326
356
  ? property.types.find((_) => isNullishType(_))
@@ -360,13 +390,8 @@ const getJsonSchemaAnnotation = (property: S.AST.AST): Record<string, unknown> =
360
390
  return jsonSchema && typeof jsonSchema === "object" ? jsonSchema as Record<string, unknown> : {}
361
391
  }
362
392
 
363
- const getDefaultFromAst = (property: S.AST.AST) => {
364
- const link = property.context?.defaultValue?.[0] as any
365
-
366
- if (!link?.transformation?.decode?.run) {
367
- return undefined
368
- }
369
-
393
+ const extractDefaultFromLink = (link: any): unknown | undefined => {
394
+ if (!link?.transformation?.decode?.run) return undefined
370
395
  try {
371
396
  const result = Effect.runSync(link.transformation.decode.run(Option.none())) as Option.Option<unknown>
372
397
  return Option.isSome(result) ? result.value : undefined
@@ -375,6 +400,21 @@ const getDefaultFromAst = (property: S.AST.AST) => {
375
400
  }
376
401
  }
377
402
 
403
+ const getDefaultFromAst = (property: S.AST.AST) => {
404
+ // 1. Check withDefaultConstructor (stored in context.defaultValue)
405
+ const constructorLink = property.context?.defaultValue?.[0]
406
+ const constructorDefault = extractDefaultFromLink(constructorLink)
407
+ if (constructorDefault !== undefined) return constructorDefault
408
+
409
+ // 2. Check withDecodingDefault (stored in encoding)
410
+ const encodingLink = property.encoding?.[0]
411
+ if (encodingLink && property.context?.isOptional) {
412
+ return extractDefaultFromLink(encodingLink)
413
+ }
414
+
415
+ return undefined
416
+ }
417
+
378
418
  const getCheckMetas = (property: S.AST.AST): Array<Record<string, any>> => {
379
419
  const checks = property.checks ?? []
380
420
 
@@ -426,6 +466,10 @@ const getFieldMetadataFromAst = (property: S.AST.AST) => {
426
466
  case "isLessThanOrEqualTo":
427
467
  base.maximum = check.maximum
428
468
  break
469
+ case "isBetween":
470
+ base.minimum = check.minimum
471
+ base.maximum = check.maximum
472
+ break
429
473
  case "isGreaterThan":
430
474
  base.exclusiveMinimum = check.exclusiveMinimum
431
475
  break
@@ -436,6 +480,11 @@ const getFieldMetadataFromAst = (property: S.AST.AST) => {
436
480
  }
437
481
  } else if (S.AST.isBoolean(property)) {
438
482
  base.type = "boolean"
483
+ } else if (
484
+ S.AST.isDeclaration(property)
485
+ && (property.annotations as any)?.typeConstructor?._tag === "Date"
486
+ ) {
487
+ base.type = "date"
439
488
  } else {
440
489
  base.type = "unknown"
441
490
  }
@@ -503,14 +552,28 @@ export const createMeta = <T = any>(
503
552
  // - All other fields maintain their normal required status based on their own types
504
553
  const isNullableDiscriminatedUnion = nullableOrUndefined && nonNullTypes.length > 1
505
554
 
506
- Object.assign(
507
- acc,
508
- createMeta<T>({
509
- parent: key,
510
- propertySignatures: nonNullType.propertySignatures,
511
- meta: isNullableDiscriminatedUnion ? { _isNullableDiscriminatedUnion: true } : {}
512
- })
513
- )
555
+ const branchMeta = createMeta<T>({
556
+ parent: key,
557
+ propertySignatures: nonNullType.propertySignatures,
558
+ meta: isNullableDiscriminatedUnion ? { _isNullableDiscriminatedUnion: true } : {}
559
+ })
560
+
561
+ // Merge branch metadata, combining select members for shared discriminator fields
562
+ for (const [metaKey, metaValue] of Object.entries(branchMeta)) {
563
+ const existing = acc[metaKey as NestedKeyOf<T>] as FieldMeta | undefined
564
+ if (
565
+ existing && existing.type === "select" && (metaValue as any)?.type === "select"
566
+ ) {
567
+ existing.members = [
568
+ ...existing.members,
569
+ ...(metaValue as SelectFieldMeta).members.filter(
570
+ (m: any) => !existing.members.includes(m)
571
+ )
572
+ ]
573
+ } else {
574
+ acc[metaKey as NestedKeyOf<T>] = metaValue as FieldMeta
575
+ }
576
+ }
514
577
  }
515
578
  }
516
579
  } else {
@@ -689,7 +752,20 @@ export const createMeta = <T = any>(
689
752
 
690
753
  if (S.AST.isUnion(property)) {
691
754
  const unwrappedTypes = unwrapNestedUnions(property.types).map(unwrapDeclaration)
692
- const nonNullType = unwrappedTypes.find((t) => !isNullishType(t))!
755
+ const nonNullTypes = unwrappedTypes.filter((t) => !isNullishType(t))
756
+
757
+ // Unwrap single-element unions when the literal is a boolean
758
+ // (effect-app's S.Literal wraps as S.Literals([x]) → Union([Literal(x)]))
759
+ // Don't unwrap string/number literals — they may be discriminator values in a union
760
+ if (
761
+ nonNullTypes.length === 1
762
+ && S.AST.isLiteral(nonNullTypes[0]!)
763
+ && typeof nonNullTypes[0]!.literal === "boolean"
764
+ ) {
765
+ return createMeta<T>({ parent, meta, property: nonNullTypes[0]! })
766
+ }
767
+
768
+ const nonNullType = nonNullTypes[0]!
693
769
 
694
770
  if (S.AST.isObjects(nonNullType)) {
695
771
  return createMeta<T>({
@@ -699,11 +775,13 @@ export const createMeta = <T = any>(
699
775
  })
700
776
  }
701
777
 
702
- if (unwrappedTypes.every((_) => isNullishType(_) || S.AST.isLiteral(_))) {
778
+ // TODO: remove after manual _tag deprecation — unwrap legacy S.Struct({ _tag: S.Literal("X") }) pattern
779
+ const resolvedTypes = unwrappedTypes.map(unwrapSingleLiteralUnion)
780
+ if (resolvedTypes.every((_) => isNullishType(_) || S.AST.isLiteral(_))) {
703
781
  return {
704
782
  ...meta,
705
783
  type: "select",
706
- members: unwrappedTypes.filter(S.AST.isLiteral).map((t) => t.literal)
784
+ members: resolvedTypes.filter(S.AST.isLiteral).map((t) => t.literal)
707
785
  } as FieldMeta
708
786
  }
709
787
 
@@ -781,9 +859,25 @@ const metadataFromAst = <From, To>(
781
859
  )
782
860
 
783
861
  let tagValue: string | null = null
784
- if (tagProp && S.AST.isLiteral(tagProp.type)) {
785
- tagValue = tagProp.type.literal as string
862
+ // TODO: remove after manual _tag deprecation — unwrap legacy S.Struct({ _tag: S.Literal("X") }) pattern
863
+ const resolvedTagType = tagProp ? unwrapSingleLiteralUnion(tagProp.type) : null
864
+ if (resolvedTagType && S.AST.isLiteral(resolvedTagType)) {
865
+ tagValue = resolvedTagType.literal as string
786
866
  discriminatorValues.push(tagValue)
867
+ // Warn if the tag was wrapped in a single-element Union (legacy pattern)
868
+ if (
869
+ tagProp
870
+ && S.AST.isUnion(tagProp.type)
871
+ && isDevelopmentEnvironment()
872
+ && tagValue != null
873
+ && !legacyTagWarningEmittedFor.has(tagValue)
874
+ ) {
875
+ legacyTagWarningEmittedFor.add(tagValue)
876
+ console.warn(
877
+ `[OmegaForm] Union member with _tag "${tagValue}" uses S.Struct({ _tag: S.Literal("${tagValue}"), ... }). `
878
+ + `Please migrate to S.TaggedStruct("${tagValue}", { ... }) for cleaner AST handling.`
879
+ )
880
+ }
787
881
  }
788
882
 
789
883
  // Create metadata for this member's properties
@@ -979,6 +1073,10 @@ export const generateInputStandardSchemaFromFieldMeta = (
979
1073
  schema = S.Boolean
980
1074
  break
981
1075
 
1076
+ case "date":
1077
+ schema = S.Date
1078
+ break
1079
+
982
1080
  case "unknown":
983
1081
  schema = S.Unknown
984
1082
  break
@@ -996,11 +1094,6 @@ export const generateInputStandardSchemaFromFieldMeta = (
996
1094
  return result
997
1095
  }
998
1096
 
999
- export const nullableInput = <A, I, R>(
1000
- schema: S.Codec<A, I, R>,
1001
- _defaultValue: () => A
1002
- ) => S.NullOr(schema) as any
1003
-
1004
1097
  export type OmegaAutoGenMeta<
1005
1098
  From extends Record<PropertyKey, any>,
1006
1099
  To extends Record<PropertyKey, any>,
@@ -1086,21 +1179,18 @@ export const defaultsValueFromSchema = (
1086
1179
  const result: Record<string, any> = {}
1087
1180
 
1088
1181
  for (const [key, fieldSchema] of Object.entries(schema.fields)) {
1089
- // Check if this field has a defaultValue in its AST
1090
- const fieldAst = (fieldSchema as any)?.ast
1091
- if (fieldAst?.defaultValue) {
1092
- try {
1093
- result[key] = fieldAst.defaultValue()
1094
- continue
1095
- } catch {
1096
- // If defaultValue() throws, fall through to recursive processing
1097
- }
1182
+ const fieldDefault = getDefaultFromAst((fieldSchema as any)?.ast)
1183
+ if (fieldDefault !== undefined) {
1184
+ result[key] = fieldDefault
1185
+ continue
1098
1186
  }
1099
1187
 
1100
1188
  // Recursively process the field
1101
1189
  const fieldValue = defaultsValueFromSchema(fieldSchema as any, record[key] || {})
1102
1190
  if (fieldValue !== undefined) {
1103
1191
  result[key] = fieldValue
1192
+ } else if (isNullableOrUndefined((fieldSchema as any).ast) === "undefined") {
1193
+ result[key] = undefined
1104
1194
  }
1105
1195
  }
1106
1196
 
@@ -1118,11 +1208,11 @@ export const defaultsValueFromSchema = (
1118
1208
  if (hasFields(member)) {
1119
1209
  // Check each field and give precedence to ones with default values
1120
1210
  Object.entries(member.fields).forEach(([key, fieldSchema]) => {
1121
- const fieldAst: any = fieldSchema.ast
1122
- const existingFieldAst: any = acc[key]?.ast
1211
+ const fieldDefault = getDefaultFromAst(fieldSchema.ast)
1212
+ const existingDefault = acc[key] ? getDefaultFromAst(acc[key].ast) : undefined
1123
1213
 
1124
1214
  // If field doesn't exist yet, or new field has default and existing doesn't, use new field
1125
- if (!acc[key] || (fieldAst?.defaultValue && !existingFieldAst?.defaultValue)) {
1215
+ if (!acc[key] || (fieldDefault !== undefined && existingDefault === undefined)) {
1126
1216
  acc[key] = fieldSchema
1127
1217
  }
1128
1218
  // If both have defaults or neither have defaults, keep the first one (existing)
@@ -1167,6 +1257,8 @@ export const defaultsValueFromSchema = (
1167
1257
 
1168
1258
  if (propValue !== undefined) {
1169
1259
  result[key] = propValue
1260
+ } else if (isNullableOrUndefined(propType) === "undefined") {
1261
+ result[key] = undefined
1170
1262
  }
1171
1263
  }
1172
1264
 
@@ -165,11 +165,13 @@ const inputProps: ComputedRef<InputProps<From, Name>> = computed(() => ({
165
165
  minLength: props.meta?.type === "string" && props.meta?.minLength,
166
166
  maxLength: props.meta?.type === "string" && props.meta?.maxLength,
167
167
  max: (props.meta?.type === "number")
168
- && (props.meta?.maximum
169
- ?? (typeof props.meta?.exclusiveMaximum === "number" && props.meta.exclusiveMaximum - 1)),
168
+ ? (props.meta?.maximum
169
+ ?? (typeof props.meta?.exclusiveMaximum === "number" ? props.meta.exclusiveMaximum - 1 : undefined))
170
+ : undefined,
170
171
  min: (props.meta?.type === "number")
171
- && (props.meta?.minimum
172
- ?? (typeof props.meta?.exclusiveMinimum === "number" && props.meta.exclusiveMinimum + 1)),
172
+ ? (props.meta?.minimum
173
+ ?? (typeof props.meta?.exclusiveMinimum === "number" ? props.meta.exclusiveMinimum + 1 : undefined))
174
+ : undefined,
173
175
  errorMessages: errors.value,
174
176
  error: !!errors.value.length,
175
177
  type: fieldType.value,