@effect-app/vue-components 2.0.0 → 2.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.
Files changed (75) hide show
  1. package/dist/types/components/OmegaForm/InputProps.d.ts +24 -0
  2. package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +1 -1
  3. package/dist/types/components/OmegaForm/OmegaInternalInput.vue.d.ts +2 -2
  4. package/dist/types/components/OmegaForm/OmegaTaggedUnion.vue.d.ts +34 -0
  5. package/dist/types/components/OmegaForm/OmegaTaggedUnionInternal.vue.d.ts +32 -0
  6. package/dist/types/components/OmegaForm/index.d.ts +3 -1
  7. package/dist/types/components/OmegaForm/useOmegaForm.d.ts +26 -0
  8. package/dist/vue-components.es.js +30 -26
  9. package/dist/vue-components.es10.js +223 -341
  10. package/dist/vue-components.es11.js +29 -33
  11. package/dist/vue-components.es12.js +357 -2
  12. package/dist/vue-components.es13.js +34 -2
  13. package/dist/vue-components.es14.js +2 -10
  14. package/dist/vue-components.es15.js +2 -5
  15. package/dist/vue-components.es16.js +10 -54
  16. package/dist/vue-components.es17.js +5 -68
  17. package/dist/vue-components.es18.js +54 -6
  18. package/dist/vue-components.es19.js +68 -6
  19. package/dist/vue-components.es2.js +20 -16
  20. package/dist/vue-components.es20.js +6 -3
  21. package/dist/vue-components.es21.js +6 -3
  22. package/dist/vue-components.es22.js +3 -2
  23. package/dist/vue-components.es23.js +3 -2
  24. package/dist/vue-components.es24.js +2 -17
  25. package/dist/vue-components.es25.js +2 -11
  26. package/dist/vue-components.es26.js +2 -136
  27. package/dist/vue-components.es27.js +4 -0
  28. package/dist/vue-components.es28.js +17 -42
  29. package/dist/vue-components.es29.js +11 -2
  30. package/dist/vue-components.es30.js +136 -2
  31. package/dist/vue-components.es32.js +44 -0
  32. package/dist/vue-components.es33.js +2 -7
  33. package/dist/vue-components.es34.js +2 -32
  34. package/dist/vue-components.es37.js +7 -23
  35. package/dist/vue-components.es38.js +32 -5
  36. package/dist/vue-components.es40.js +4 -30
  37. package/dist/vue-components.es41.js +20 -19
  38. package/dist/vue-components.es42.js +5 -12
  39. package/dist/vue-components.es43.js +21 -5
  40. package/dist/vue-components.es44.js +29 -18
  41. package/dist/vue-components.es45.js +22 -9
  42. package/dist/vue-components.es46.js +7 -26
  43. package/dist/vue-components.es47.js +5 -48
  44. package/dist/vue-components.es48.js +19 -26
  45. package/dist/vue-components.es49.js +9 -11
  46. package/dist/vue-components.es5.js +2 -2
  47. package/dist/vue-components.es50.js +26 -60
  48. package/dist/vue-components.es51.js +37 -45
  49. package/dist/vue-components.es52.js +26 -17
  50. package/dist/vue-components.es53.js +10 -32
  51. package/dist/vue-components.es54.js +65 -29
  52. package/dist/vue-components.es55.js +45 -31
  53. package/dist/vue-components.es56.js +17 -2
  54. package/dist/vue-components.es57.js +29 -40
  55. package/dist/vue-components.es58.js +29 -2
  56. package/dist/vue-components.es59.js +44 -0
  57. package/dist/vue-components.es6.js +9 -9
  58. package/dist/vue-components.es60.js +4 -0
  59. package/dist/vue-components.es61.js +46 -0
  60. package/dist/vue-components.es62.js +4 -0
  61. package/dist/vue-components.es7.js +26 -25
  62. package/dist/vue-components.es8.js +51 -198
  63. package/dist/vue-components.es9.js +17 -30
  64. package/package.json +1 -1
  65. package/src/components/OmegaForm/InputProps.ts +32 -0
  66. package/src/components/OmegaForm/OmegaFormStuff.ts +22 -3
  67. package/src/components/OmegaForm/OmegaInput.vue +4 -1
  68. package/src/components/OmegaForm/OmegaInternalInput.vue +35 -13
  69. package/src/components/OmegaForm/OmegaTaggedUnion.vue +73 -0
  70. package/src/components/OmegaForm/OmegaTaggedUnionInternal.vue +38 -0
  71. package/src/components/OmegaForm/index.ts +3 -1
  72. package/src/components/OmegaForm/useOmegaForm.ts +178 -11
  73. package/dist/vue-components.es36.js +0 -6
  74. package/dist/vue-components.es39.js +0 -23
  75. /package/dist/{vue-components.es31.js → vue-components.es35.js} +0 -0
@@ -0,0 +1,73 @@
1
+ <script
2
+ setup
3
+ lang="ts"
4
+ generic="
5
+ From extends Record<PropertyKey, any>,
6
+ To extends Record<PropertyKey, any>,
7
+ Name extends DeepKeys<From>
8
+ "
9
+ >
10
+ import { type DeepKeys, type DeepValue } from "@tanstack/vue-form"
11
+ import { onMounted } from "vue"
12
+ import { type TaggedUnionOption, type TaggedUnionOptionsArray } from "./InputProps"
13
+ import { type Leaves } from "./OmegaFormStuff"
14
+ import OmegaTaggedUnionInternal from "./OmegaTaggedUnionInternal.vue"
15
+ import { type useOmegaForm } from "./useOmegaForm"
16
+
17
+ const props = defineProps<{
18
+ name: Name
19
+ form: ReturnType<typeof useOmegaForm<From, To>>
20
+ type?: "select" | "radio"
21
+ options: TaggedUnionOptionsArray<From, Name>
22
+ label?: string
23
+ }>()
24
+
25
+ // Initialize the union field on mount
26
+ onMounted(() => {
27
+ const currentValue = props.form.getFieldValue(props.name)
28
+ const meta = props.form.meta[props.name as keyof typeof props.form.meta]
29
+
30
+ if (currentValue === undefined) {
31
+ if (meta?.nullableOrUndefined === "null" || !meta?.required) {
32
+ // Initialize to null for nullable/optional unions
33
+ props.form.setFieldValue(props.name, null as DeepValue<From, Name>)
34
+ } else {
35
+ // For required unions, initialize with first non-null option
36
+ const firstOption = props.options.find((opt) => opt.value !== null)
37
+ if (firstOption && firstOption.value) {
38
+ props.form.setFieldValue(props.name, {
39
+ _tag: firstOption.value
40
+ } as DeepValue<From, Name>)
41
+ }
42
+ }
43
+ }
44
+ })
45
+ </script>
46
+
47
+ <template>
48
+ <form.Input
49
+ :name="`${name}._tag` as Leaves<From, ''>"
50
+ :label="label"
51
+ :type="type ?? 'select'"
52
+ :options="options as unknown as TaggedUnionOption<From, Name>[]"
53
+ />
54
+ <form.Field :name="name">
55
+ <template #default="{ field, state }">
56
+ <slot v-if="state.value" />
57
+ <OmegaTaggedUnionInternal
58
+ :field="field as any"
59
+ :state="state.value"
60
+ >
61
+ <template
62
+ v-for="(_, slotname) in $slots"
63
+ #[slotname]="slotProps"
64
+ >
65
+ <slot
66
+ :name="slotname"
67
+ v-bind="slotProps"
68
+ />
69
+ </template>
70
+ </OmegaTaggedUnionInternal>
71
+ </template>
72
+ </form.Field>
73
+ </template>
@@ -0,0 +1,38 @@
1
+ <template>
2
+ <slot
3
+ v-if="state?._tag"
4
+ :name="state?._tag"
5
+ v-bind="{ field, state }"
6
+ />
7
+ </template>
8
+
9
+ <script
10
+ setup
11
+ lang="ts"
12
+ generic="
13
+ From extends Record<PropertyKey, any>,
14
+ To extends Record<PropertyKey, any>,
15
+ Name extends DeepKeys<From>
16
+ "
17
+ >
18
+ import { type DeepKeys, type DeepValue } from "@tanstack/vue-form"
19
+ import { watch } from "vue"
20
+ import { type OmegaFieldInternalApi } from "./InputProps"
21
+
22
+ const props = defineProps<{
23
+ state: DeepValue<From, Name>
24
+ field: OmegaFieldInternalApi<From, Name>
25
+ }>()
26
+
27
+ // Watch for _tag changes
28
+ watch(() => props.state?._tag, (newTag, oldTag) => {
29
+ if (newTag === null) {
30
+ props.field.setValue(null as DeepValue<From, Name>)
31
+ }
32
+ if (newTag !== oldTag) {
33
+ setTimeout(() => {
34
+ props.field.validate("change")
35
+ }, 0)
36
+ }
37
+ })
38
+ </script>
@@ -1,9 +1,11 @@
1
1
  export * from "./OmegaFormStuff"
2
2
  export { type OmegaConfig, type OmegaFormReturn, useOmegaForm } from "./useOmegaForm"
3
3
 
4
- export { type InputProps, type MergedInputProps } from "./InputProps"
4
+ export { type ExtractTagValue, type ExtractUnionBranch, type InputProps, type MergedInputProps, type TaggedUnionOption, type TaggedUnionOptionsArray, type TaggedUnionProps } from "./InputProps"
5
5
  export { default as OmegaInput } from "./OmegaInput.vue"
6
6
  export { default as OmegaVuetifyInput } from "./OmegaInternalInput.vue"
7
+ export { default as OmegaTaggedUnion } from "./OmegaTaggedUnion.vue"
8
+ export { default as OmegaTaggedUnionInternal } from "./OmegaTaggedUnionInternal.vue"
7
9
 
8
10
  export { useOnClose, usePreventClose } from "./blockDialog"
9
11
 
@@ -13,6 +13,7 @@ import OmegaAutoGen from "./OmegaAutoGen.vue"
13
13
  import OmegaErrorsInternal from "./OmegaErrorsInternal.vue"
14
14
  import { BaseProps, DefaultTypeProps, type FormProps, generateMetaFromSchema, type MetaRecord, type NestedKeyOf, OmegaArrayProps, OmegaAutoGenMeta, OmegaError, type OmegaFormApi, OmegaFormState } from "./OmegaFormStuff"
15
15
  import OmegaInput from "./OmegaInput.vue"
16
+ import OmegaTaggedUnion from "./OmegaTaggedUnion.vue"
16
17
  import OmegaForm from "./OmegaWrapper.vue"
17
18
 
18
19
  type keysRule<T> =
@@ -75,20 +76,61 @@ const eHoc = (errorProps: {
75
76
  const { fieldMap, form } = errorProps
76
77
  const generalErrors = form.useStore((state) => state.errors)
77
78
  const fieldMeta = form.useStore((state) => state.fieldMeta)
78
- const errors = computed(() =>
79
- Array.filterMap(
79
+ const errorMap = form.useStore((state) => state.errorMap)
80
+
81
+ const errors = computed(() => {
82
+ // Collect errors from fieldMeta (field-level errors for registered fields)
83
+ const fieldErrors = Array.filterMap(
80
84
  Object
81
85
  .entries(fieldMeta.value),
82
- ([key, m]): Option.Option<OmegaError> =>
83
- ((m as any).errors ?? []).length && fieldMap.value.get(key)?.id
84
- ? Option.some({
85
- label: fieldMap.value.get(key)!.label,
86
- inputId: fieldMap.value.get(key)!.id,
87
- errors: ((m as any).errors ?? []).map((e: any) => e.message).filter(Boolean)
88
- })
89
- : Option.none()
86
+ ([key, m]): Option.Option<OmegaError> => {
87
+ const fieldErrors = (m as any).errors ?? []
88
+ if (!fieldErrors.length) return Option.none()
89
+
90
+ const fieldInfo = fieldMap.value.get(key)
91
+ // Only show errors for fields that are currently mounted (registered in fieldMap)
92
+ if (!fieldInfo) return Option.none()
93
+
94
+ return Option.some({
95
+ label: fieldInfo.label,
96
+ inputId: fieldInfo.id,
97
+ // Only show the first error
98
+ errors: [fieldErrors[0]?.message].filter(Boolean)
99
+ })
100
+ }
90
101
  )
91
- )
102
+
103
+ // Collect errors from errorMap.onSubmit ONLY for fields that are NOT registered
104
+ // (registered fields already have their errors in fieldMeta)
105
+ const submitErrors: OmegaError[] = []
106
+ if (errorMap.value.onSubmit) {
107
+ for (const [_, issues] of Object.entries(errorMap.value.onSubmit)) {
108
+ if (Array.isArray(issues) && issues.length) {
109
+ for (const issue of issues) {
110
+ const issAny: any = issue
111
+ if (issAny?.path && Array.isArray(issAny.path) && issAny.path.length) {
112
+ // Use the path from the issue to identify the field
113
+ const fieldPath = issAny.path.join(".")
114
+ // Only add errors for fields that are NOT registered (not in fieldMap)
115
+ // Registered fields will already have their errors from fieldMeta
116
+ if (!fieldMap.value.has(fieldPath)) {
117
+ submitErrors.push({
118
+ label: fieldPath,
119
+ inputId: fieldPath,
120
+ errors: [issAny.message].filter(Boolean)
121
+ })
122
+ // Only show first error per field, so break after adding
123
+ break
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+
131
+ // Combine both error sources (no need to check for duplicates since they're mutually exclusive)
132
+ return [...fieldErrors, ...submitErrors]
133
+ })
92
134
 
93
135
  return {
94
136
  generalErrors,
@@ -121,6 +163,16 @@ export type OmegaConfig<T> = {
121
163
 
122
164
  ignorePreventCloseEvents?: boolean
123
165
 
166
+ /**
167
+ * Prevents browser window/tab exit when form has unsaved changes.
168
+ * Shows native browser "Leave site?" dialog.
169
+ *
170
+ * @remarks
171
+ * - Opt-in only: Must explicitly enable
172
+ * - Independent from data persistence feature
173
+ */
174
+ preventWindowExit?: "prevent" | "prevent-and-reset" | "nope"
175
+
124
176
  input?: any
125
177
  }
126
178
 
@@ -219,6 +271,90 @@ export interface OmegaFormReturn<
219
271
  ) => import("vue").VNode & {
220
272
  __ctx?: Awaited<typeof __VLS_setup>
221
273
  }
274
+ TaggedUnion: <Name extends DeepKeys<From>>(
275
+ __VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"],
276
+ __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>,
277
+ __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"],
278
+ __VLS_setup?: Promise<{
279
+ props:
280
+ & __VLS_PrettifyLocal<
281
+ & Pick<
282
+ & Partial<{}>
283
+ & Omit<
284
+ {} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps,
285
+ never
286
+ >,
287
+ never
288
+ >
289
+ & {
290
+ name: Name
291
+ type?: "select" | "radio"
292
+ options: import("./InputProps").TaggedUnionOptionsArray<From, Name>
293
+ label?: string
294
+ }
295
+ & {}
296
+ >
297
+ & import("vue").PublicProps
298
+ expose(exposed: import("vue").ShallowUnwrapRef<{}>): void
299
+ attrs: any
300
+ slots: Record<
301
+ string,
302
+ (props: {
303
+ field: import("@tanstack/vue-form").FieldApi<
304
+ From,
305
+ Name,
306
+ DeepValue<From, Name>,
307
+ any,
308
+ any,
309
+ any,
310
+ any,
311
+ any,
312
+ any,
313
+ any,
314
+ any,
315
+ any,
316
+ any,
317
+ any,
318
+ any,
319
+ any,
320
+ any,
321
+ any,
322
+ any,
323
+ any,
324
+ any,
325
+ any,
326
+ any
327
+ >
328
+ state: import("@tanstack/vue-form").FieldState<
329
+ From,
330
+ Name,
331
+ DeepValue<From, Name>,
332
+ any,
333
+ any,
334
+ any,
335
+ any,
336
+ any,
337
+ any,
338
+ any,
339
+ any,
340
+ any,
341
+ any,
342
+ any,
343
+ any,
344
+ any,
345
+ any,
346
+ any,
347
+ any,
348
+ any,
349
+ any
350
+ >
351
+ }) => any
352
+ >
353
+ emit: {}
354
+ }>
355
+ ) => import("vue").VNode & {
356
+ __ctx?: Awaited<typeof __VLS_setup>
357
+ }
222
358
  Array: (
223
359
  __VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"],
224
360
  __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>,
@@ -829,17 +965,47 @@ export const useOmegaForm = <
829
965
  }
830
966
  }
831
967
 
968
+ const preventWindowExit = (e: BeforeUnloadEvent) => {
969
+ if (form.store.state.isDirty) {
970
+ e.preventDefault()
971
+ }
972
+ }
973
+
832
974
  onUnmounted(persistData)
833
975
 
834
976
  onMounted(() => {
835
977
  window.addEventListener("beforeunload", persistData)
836
978
  window.addEventListener("blur", saveDataInUrl)
979
+ if (omegaConfig?.preventWindowExit && omegaConfig.preventWindowExit !== "nope") {
980
+ window.addEventListener("beforeunload", preventWindowExit)
981
+ }
837
982
  })
838
983
  onBeforeUnmount(() => {
839
984
  window.removeEventListener("beforeunload", persistData)
840
985
  window.removeEventListener("blur", saveDataInUrl)
986
+ if (omegaConfig?.preventWindowExit && omegaConfig.preventWindowExit !== "nope") {
987
+ window.removeEventListener("beforeunload", preventWindowExit)
988
+ }
841
989
  })
842
990
 
991
+ // Watch for successful form submissions and auto-reset if prevent-and-reset is enabled
992
+ // We put it as a side effect, so we don't overwhelm submit handler and we can support
993
+ // effects submission more freely
994
+ if (omegaConfig?.preventWindowExit === "prevent-and-reset") {
995
+ const isSubmitting = form.useStore((state) => state.isSubmitting)
996
+ const submissionAttempts = form.useStore((state) => state.submissionAttempts)
997
+ const canSubmit = form.useStore((state) => state.canSubmit)
998
+ const values = form.useStore((state) => state.values)
999
+
1000
+ watch([isSubmitting, submissionAttempts], ([currentlySubmitting, attempts], [wasSubmitting]) => {
1001
+ // Detect successful submission: was submitting, now not submitting, and submission count increased
1002
+ if (wasSubmitting && !currentlySubmitting && attempts > 0 && canSubmit.value) {
1003
+ // Reset with current values to mark them as the new baseline
1004
+ form.reset(values.value)
1005
+ }
1006
+ })
1007
+ }
1008
+
843
1009
  const handleSubmitEffect_ = (meta?: Record<string, any>) =>
844
1010
  Effect.currentSpan.pipe(
845
1011
  Effect.option,
@@ -891,6 +1057,7 @@ export const useOmegaForm = <
891
1057
  errorContext,
892
1058
  Form: fHoc(formWithExtras)(OmegaForm as any) as any,
893
1059
  Input: fHoc(formWithExtras)(omegaConfig?.input ?? OmegaInput) as any,
1060
+ TaggedUnion: fHoc(formWithExtras)(OmegaTaggedUnion) as any,
894
1061
  Field: form.Field,
895
1062
  Errors: eHoc(errorContext)(OmegaErrorsInternal) as any,
896
1063
  Array: fHoc(formWithExtras)(OmegaArray) as any,
@@ -1,6 +0,0 @@
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.es26.js";
3
-
4
- export {
5
- o as default
6
- };
@@ -1,23 +0,0 @@
1
- import { NoopTracer as o } from "./vue-components.es40.js";
2
- var s = new o(), c = (
3
- /** @class */
4
- (function() {
5
- function r(t, e, n, a) {
6
- this._provider = t, this.name = e, this.version = n, this.options = a;
7
- }
8
- return r.prototype.startSpan = function(t, e, n) {
9
- return this._getTracer().startSpan(t, e, n);
10
- }, r.prototype.startActiveSpan = function(t, e, n, a) {
11
- var i = this._getTracer();
12
- return Reflect.apply(i.startActiveSpan, i, arguments);
13
- }, r.prototype._getTracer = function() {
14
- if (this._delegate)
15
- return this._delegate;
16
- var t = this._provider.getDelegateTracer(this.name, this.version, this.options);
17
- return t ? (this._delegate = t, this._delegate) : s;
18
- }, r;
19
- })()
20
- );
21
- export {
22
- c as ProxyTracer
23
- };