@effect-app/vue-components 4.0.0-beta.157 → 4.0.0-beta.159

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 (124) hide show
  1. package/dist/types/components/OmegaForm/OmegaArray.vue.d.ts +1 -1
  2. package/dist/types/components/OmegaForm/OmegaAutoGen.vue.d.ts +1 -1
  3. package/dist/types/components/OmegaForm/OmegaErrorsInternal.vue.d.ts +1 -1
  4. package/dist/types/components/OmegaForm/OmegaFormInput.vue.d.ts +1 -1
  5. package/dist/types/components/OmegaForm/OmegaInput.vue.d.ts +1 -1
  6. package/dist/types/components/OmegaForm/OmegaInternalInput.vue.d.ts +2 -1
  7. package/dist/types/components/OmegaForm/OmegaWrapper.vue.d.ts +1 -1
  8. package/dist/types/components/OmegaForm/createUseFormWithCustomInput.d.ts +2 -2
  9. package/dist/types/components/OmegaForm/errors.d.ts +33 -0
  10. package/dist/types/components/OmegaForm/getOmegaStore.d.ts +1 -1
  11. package/dist/types/components/OmegaForm/hocs.d.ts +3 -0
  12. package/dist/types/components/OmegaForm/index.d.ts +13 -3
  13. package/dist/types/components/OmegaForm/inputs.d.ts +4 -0
  14. package/dist/types/components/OmegaForm/meta/checks.d.ts +4 -0
  15. package/dist/types/components/OmegaForm/meta/createMeta.d.ts +32 -0
  16. package/dist/types/components/OmegaForm/meta/defaults.d.ts +2 -0
  17. package/dist/types/components/OmegaForm/meta/redacted.d.ts +2 -0
  18. package/dist/types/components/OmegaForm/meta/types.d.ts +56 -0
  19. package/dist/types/components/OmegaForm/meta/walker.d.ts +18 -0
  20. package/dist/types/components/OmegaForm/persistency.d.ts +58 -0
  21. package/dist/types/components/OmegaForm/submit.d.ts +60 -0
  22. package/dist/types/components/OmegaForm/types.d.ts +281 -0
  23. package/dist/types/components/OmegaForm/useOmegaForm.d.ts +6 -212
  24. package/dist/types/components/OmegaForm/validation/localized.d.ts +10 -0
  25. package/dist/vue-components.es.js +24 -16
  26. package/dist/vue-components10.es.js +4 -4
  27. package/dist/vue-components11.es.js +19 -12
  28. package/dist/vue-components12.es.js +22 -444
  29. package/dist/vue-components13.es.js +126 -3
  30. package/dist/vue-components14.es.js +61 -34
  31. package/dist/vue-components15.es.js +57 -24
  32. package/dist/vue-components16.es.js +20 -26
  33. package/dist/vue-components17.es.js +4 -6
  34. package/dist/vue-components18.es.js +78 -16
  35. package/dist/vue-components19.es.js +86 -30
  36. package/dist/vue-components20.es.js +72 -17
  37. package/dist/vue-components21.es.js +10 -19
  38. package/dist/vue-components22.es.js +54 -28
  39. package/dist/vue-components23.es.js +4 -6
  40. package/dist/vue-components24.es.js +43 -8
  41. package/dist/vue-components25.es.js +4 -37
  42. package/dist/vue-components26.es.js +83 -24
  43. package/dist/vue-components28.es.js +6 -22
  44. package/dist/vue-components29.es.js +8 -20
  45. package/dist/vue-components3.es.js +2 -2
  46. package/dist/vue-components30.es.js +267 -7
  47. package/dist/vue-components32.es.js +7 -4
  48. package/dist/vue-components33.es.js +71 -27
  49. package/dist/vue-components34.es.js +4 -4
  50. package/dist/vue-components35.es.js +50 -27
  51. package/dist/vue-components36.es.js +4 -5
  52. package/dist/vue-components37.es.js +23 -17
  53. package/dist/vue-components38.es.js +4 -55
  54. package/dist/vue-components39.es.js +57 -3
  55. package/dist/vue-components40.es.js +4 -43
  56. package/dist/vue-components41.es.js +11 -4
  57. package/dist/vue-components42.es.js +17 -79
  58. package/dist/vue-components44.es.js +8 -7
  59. package/dist/vue-components45.es.js +3 -8
  60. package/dist/vue-components46.es.js +36 -267
  61. package/dist/vue-components47.es.js +27 -0
  62. package/dist/vue-components48.es.js +27 -7
  63. package/dist/vue-components49.es.js +6 -79
  64. package/dist/vue-components50.es.js +17 -4
  65. package/dist/vue-components51.es.js +32 -69
  66. package/dist/vue-components52.es.js +17 -4
  67. package/dist/vue-components53.es.js +19 -22
  68. package/dist/vue-components54.es.js +29 -4
  69. package/dist/vue-components55.es.js +6 -58
  70. package/dist/vue-components56.es.js +8 -4
  71. package/dist/vue-components57.es.js +37 -11
  72. package/dist/vue-components58.es.js +24 -21
  73. package/dist/{vue-components27.es.js → vue-components59.es.js} +2 -2
  74. package/dist/vue-components6.es.js +11 -11
  75. package/dist/vue-components60.es.js +23 -8
  76. package/dist/vue-components61.es.js +18 -232
  77. package/dist/vue-components62.es.js +7 -31
  78. package/dist/vue-components63.es.js +19 -8
  79. package/dist/vue-components64.es.js +4 -35
  80. package/dist/vue-components65.es.js +29 -0
  81. package/dist/vue-components66.es.js +5 -0
  82. package/dist/vue-components67.es.js +29 -0
  83. package/dist/vue-components68.es.js +6 -0
  84. package/dist/vue-components69.es.js +18 -0
  85. package/dist/vue-components7.es.js +11 -26
  86. package/dist/vue-components70.es.js +40 -0
  87. package/dist/vue-components71.es.js +81 -0
  88. package/dist/vue-components72.es.js +33 -0
  89. package/dist/vue-components73.es.js +19 -0
  90. package/dist/vue-components74.es.js +48 -0
  91. package/dist/vue-components8.es.js +33 -45
  92. package/dist/vue-components9.es.js +46 -4
  93. package/package.json +7 -7
  94. package/src/components/CommandButton.vue +3 -1
  95. package/src/components/OmegaForm/OmegaArray.vue +1 -1
  96. package/src/components/OmegaForm/OmegaAutoGen.vue +2 -1
  97. package/src/components/OmegaForm/OmegaErrorsInternal.vue +1 -1
  98. package/src/components/OmegaForm/OmegaFormInput.vue +1 -1
  99. package/src/components/OmegaForm/OmegaInput.vue +6 -68
  100. package/src/components/OmegaForm/OmegaInputVuetify.vue +1 -1
  101. package/src/components/OmegaForm/OmegaInternalInput.vue +5 -11
  102. package/src/components/OmegaForm/OmegaTaggedUnion.vue +2 -1
  103. package/src/components/OmegaForm/OmegaWrapper.vue +1 -1
  104. package/src/components/OmegaForm/blockDialog.ts +10 -1
  105. package/src/components/OmegaForm/createUseFormWithCustomInput.ts +2 -1
  106. package/src/components/OmegaForm/errors.ts +136 -0
  107. package/src/components/OmegaForm/getOmegaStore.ts +1 -1
  108. package/src/components/OmegaForm/hocs.ts +19 -0
  109. package/src/components/OmegaForm/index.ts +16 -4
  110. package/src/components/OmegaForm/inputs.ts +22 -0
  111. package/src/components/OmegaForm/meta/checks.ts +81 -0
  112. package/src/components/OmegaForm/meta/createMeta.ts +138 -0
  113. package/src/components/OmegaForm/meta/defaults.ts +132 -0
  114. package/src/components/OmegaForm/meta/redacted.ts +66 -0
  115. package/src/components/OmegaForm/meta/types.ts +78 -0
  116. package/src/components/OmegaForm/meta/walker.ts +247 -0
  117. package/src/components/OmegaForm/persistency.ts +247 -0
  118. package/src/components/OmegaForm/submit.ts +128 -0
  119. package/src/components/OmegaForm/types.ts +751 -0
  120. package/src/components/OmegaForm/useOmegaForm.ts +49 -913
  121. package/src/components/OmegaForm/validation/localized.ts +202 -0
  122. package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +0 -173
  123. package/dist/vue-components31.es.js +0 -19
  124. package/src/components/OmegaForm/OmegaFormStuff.ts +0 -1422
@@ -0,0 +1,247 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { isObject } from "@vueuse/core"
3
+ import { computed, type ComputedRef, onBeforeUnmount, onMounted, onUnmounted } from "vue"
4
+ import { type MetaRecord } from "./meta/types"
5
+
6
+ export type Policies = "local" | "session" | "querystring"
7
+ export type DefaultValuesPriorityUnion = "tanstack" | "persistency" | "schema"
8
+
9
+ // Backward-compatible alias for the legacy lowercased-prefix type name.
10
+ export type defaultValuesPriorityUnion = DefaultValuesPriorityUnion
11
+
12
+ export interface PersistencyConfig {
13
+ /** Order of importance:
14
+ * - "querystring": Highest priority when persisting
15
+ * - "local" and then "session": Lower priority storage options
16
+ */
17
+ policies?: ReadonlyArray<Policies>
18
+ overrideDefaultValues?: "deprecated: use defaultValuesPriority"
19
+ id?: string
20
+ keys?: ReadonlyArray<string> | "You should only use one of banKeys or keys, not both, moron"
21
+ banKeys?: ReadonlyArray<string> | "You should only use one of banKeys or keys, not both, moron"
22
+ }
23
+
24
+ export function deepMerge(target: any, source: any) {
25
+ const result = { ...target }
26
+ for (const key in source) {
27
+ if (Array.isArray(source[key])) {
28
+ // Arrays should be copied directly, not deep merged
29
+ result[key] = source[key]
30
+ } else if (source[key] && isObject(source[key])) {
31
+ result[key] = deepMerge(result[key], source[key])
32
+ } else {
33
+ result[key] = source[key]
34
+ }
35
+ }
36
+ return result
37
+ }
38
+
39
+ const includesPolicy = (arr: ReadonlyArray<Policies>, policy: Policies) => {
40
+ return arr.includes(policy)
41
+ }
42
+
43
+ export interface UsePersistencyOptions<From> {
44
+ meta: MetaRecord<From>
45
+ persistency?: PersistencyConfig
46
+ preventWindowExit?: "prevent" | "prevent-and-reset" | "nope"
47
+ defaultValuesPriority?: DefaultValuesPriorityUnion[] | readonly DefaultValuesPriorityUnion[]
48
+ /** Tanstack-provided default values (highest priority by default). */
49
+ tanstackDefaultValues?: any
50
+ /** Lazy schema-derived defaults factory. */
51
+ schemaDefaultValues: () => any
52
+ /**
53
+ * Lazy accessor for the form. Lazy because persistency is created BEFORE
54
+ * the form (its `defaultValues` are passed into `useForm`), but the
55
+ * persistence callbacks (`persistData`, `saveDataInUrl`, the
56
+ * `beforeunload` listener) only run later and need the live form.
57
+ */
58
+ getForm: () => {
59
+ store: { state: { values: any; isDirty: boolean } }
60
+ getFieldValue: (path: any) => any
61
+ }
62
+ }
63
+
64
+ export interface UsePersistencyReturn {
65
+ defaultValues: ComputedRef<any>
66
+ persistencyKey: ComputedRef<string>
67
+ persistData: () => void
68
+ saveDataInUrl: () => void
69
+ clearUrlParams: () => void
70
+ }
71
+
72
+ /**
73
+ * Encapsulates form-data persistency: loading default values from
74
+ * localStorage / sessionStorage / querystring, persisting them on unmount
75
+ * or window blur, and the optional `preventWindowExit` warning listener.
76
+ *
77
+ * The `prevent-and-reset` reset-on-success behavior is intentionally NOT
78
+ * owned here — the consumer wires that to its own form submit lifecycle.
79
+ */
80
+ export const usePersistency = <From>(opts: UsePersistencyOptions<From>): UsePersistencyReturn => {
81
+ const { getForm, meta, persistency, preventWindowExit, schemaDefaultValues, tanstackDefaultValues } = opts
82
+
83
+ const persistencyKey = computed(() => {
84
+ if (persistency?.id) {
85
+ return persistency.id
86
+ }
87
+ const path = window.location.pathname
88
+ const keys = Object.keys(meta)
89
+ return `${path}-${keys.join("-")}`
90
+ })
91
+
92
+ const clearUrlParams = () => {
93
+ const params = new URLSearchParams(window.location.search)
94
+ params.delete(persistencyKey.value)
95
+ const url = new URL(window.location.href)
96
+ url.search = params.toString()
97
+ window.history.replaceState({}, "", url.toString())
98
+ }
99
+
100
+ const defaultValues = computed(() => {
101
+ // will contain what we get from querystring or local/session storage
102
+ let persistencyDefaultValues
103
+
104
+ if (
105
+ // query string has higher priority than local/session storage
106
+ persistency?.policies
107
+ && !persistencyDefaultValues
108
+ && (includesPolicy(persistency.policies, "local")
109
+ || includesPolicy(persistency.policies, "session"))
110
+ ) {
111
+ const storage = includesPolicy(persistency.policies, "local")
112
+ ? localStorage
113
+ : sessionStorage
114
+ if (storage) {
115
+ try {
116
+ const value = JSON.parse(
117
+ storage.getItem(persistencyKey.value) || "{}"
118
+ )
119
+ storage.removeItem(persistencyKey.value)
120
+ persistencyDefaultValues = value
121
+ } catch (error) {
122
+ console.error(error)
123
+ }
124
+ }
125
+ }
126
+ if (persistency?.policies && includesPolicy(persistency.policies, "querystring")) {
127
+ try {
128
+ const params = new URLSearchParams(window.location.search)
129
+ const value = params.get(persistencyKey.value)
130
+ clearUrlParams()
131
+ if (value) {
132
+ persistencyDefaultValues = deepMerge(persistencyDefaultValues || {}, JSON.parse(value))
133
+ }
134
+ } catch (error) {
135
+ console.error(error)
136
+ }
137
+ }
138
+
139
+ // to be sure we have a valid object at the end of the gathering process
140
+ persistencyDefaultValues ??= {}
141
+
142
+ const defaults: Record<DefaultValuesPriorityUnion, any> = {
143
+ tanstack: tanstackDefaultValues || {},
144
+ persistency: persistencyDefaultValues,
145
+ schema: schemaDefaultValues()
146
+ }
147
+
148
+ return [...(opts.defaultValuesPriority || ["tanstack", "persistency", "schema"] as const)].reverse().reduce(
149
+ (acc: any, m: DefaultValuesPriorityUnion) => {
150
+ if (!Object.keys(acc).length) {
151
+ return defaults[m]
152
+ }
153
+ return deepMerge(acc, defaults[m])
154
+ },
155
+ {}
156
+ )
157
+ })
158
+
159
+ const createNestedObjectFromPaths = (paths: string[]) =>
160
+ paths.reduce((result, path) => {
161
+ const parts = path.split(".")
162
+ parts.reduce((acc, part, i) => {
163
+ if (i === parts.length - 1) {
164
+ acc[part] = getForm().getFieldValue(path as any)
165
+ } else {
166
+ acc[part] = acc[part] ?? {}
167
+ }
168
+ return acc[part]
169
+ }, result)
170
+ return result
171
+ }, {} as Record<string, any>)
172
+
173
+ const persistFilter = (p: PersistencyConfig | undefined) => {
174
+ if (!p) return
175
+ const { banKeys, keys } = p
176
+ if (Array.isArray(keys)) {
177
+ return createNestedObjectFromPaths(keys as string[])
178
+ }
179
+ if (Array.isArray(banKeys)) {
180
+ const subs = Object.keys(meta).filter((metakey) => banKeys.includes(metakey))
181
+ return createNestedObjectFromPaths(subs)
182
+ }
183
+ return getForm().store.state.values
184
+ }
185
+
186
+ const persistData = () => {
187
+ if (!persistency?.policies || persistency.policies.length === 0) {
188
+ return
189
+ }
190
+ if (
191
+ includesPolicy(persistency.policies, "local")
192
+ || includesPolicy(persistency.policies, "session")
193
+ ) {
194
+ const storage = includesPolicy(persistency.policies, "local")
195
+ ? localStorage
196
+ : sessionStorage
197
+ if (!storage) return
198
+ const values = persistFilter(persistency)
199
+ return storage.setItem(persistencyKey.value, JSON.stringify(values))
200
+ }
201
+ }
202
+
203
+ const saveDataInUrl = () => {
204
+ if (!persistency?.policies || persistency.policies.length === 0) {
205
+ return
206
+ }
207
+ if (includesPolicy(persistency.policies, "querystring")) {
208
+ const values = persistFilter(persistency)
209
+ const searchParams = new URLSearchParams(window.location.search)
210
+ searchParams.set(persistencyKey.value, JSON.stringify(values))
211
+ const url = new URL(window.location.href)
212
+ url.search = searchParams.toString()
213
+ window.history.replaceState({}, "", url.toString())
214
+ }
215
+ }
216
+
217
+ const preventWindowExitListener = (e: BeforeUnloadEvent) => {
218
+ if (getForm().store.state.isDirty) {
219
+ e.preventDefault()
220
+ }
221
+ }
222
+
223
+ onUnmounted(persistData)
224
+
225
+ onMounted(() => {
226
+ window.addEventListener("beforeunload", persistData)
227
+ window.addEventListener("blur", saveDataInUrl)
228
+ if (preventWindowExit && preventWindowExit !== "nope") {
229
+ window.addEventListener("beforeunload", preventWindowExitListener)
230
+ }
231
+ })
232
+ onBeforeUnmount(() => {
233
+ window.removeEventListener("beforeunload", persistData)
234
+ window.removeEventListener("blur", saveDataInUrl)
235
+ if (preventWindowExit && preventWindowExit !== "nope") {
236
+ window.removeEventListener("beforeunload", preventWindowExitListener)
237
+ }
238
+ })
239
+
240
+ return {
241
+ defaultValues,
242
+ persistencyKey,
243
+ persistData,
244
+ saveDataInUrl,
245
+ clearUrlParams
246
+ }
247
+ }
@@ -0,0 +1,128 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import * as api from "@opentelemetry/api"
4
+ import type { DeepKeys, StandardSchemaV1Issue, ValidationError, ValidationErrorMap } from "@tanstack/vue-form"
5
+ import { Data, Effect, Fiber, Option } from "effect-app"
6
+ import { runtimeFiberAsPromise } from "effect-app/utils"
7
+ import type { Fiber as EffectFiber } from "effect/Fiber"
8
+ import type { OmegaFormApi, OmegaFormParams } from "./types"
9
+
10
+ export class FormErrors<From> extends Data.TaggedError("FormErrors")<{
11
+ form: {
12
+ // TODO: error shapes seem off, with `undefined` etc..
13
+ errors: (Record<string, StandardSchemaV1Issue[]> | undefined)[]
14
+ errorMap: ValidationErrorMap<
15
+ undefined,
16
+ undefined,
17
+ Record<string, StandardSchemaV1Issue[]>,
18
+ undefined,
19
+ undefined,
20
+ undefined,
21
+ undefined,
22
+ undefined,
23
+ undefined,
24
+ undefined
25
+ >
26
+ }
27
+ fields: Record<DeepKeys<From>, {
28
+ errors: ValidationError[]
29
+ errorMap: ValidationErrorMap
30
+ }>
31
+ }> {}
32
+
33
+ export const wrapWithSpan = (span: api.Span | undefined, toWrap: () => any) => {
34
+ return span ? api.context.with(api.trace.setSpan(api.context.active(), span), toWrap) : toWrap()
35
+ }
36
+
37
+ export type UserOnSubmit<From, To> = (props: {
38
+ formApi: OmegaFormParams<From, To>
39
+ meta: any
40
+ value: To
41
+ }) => Promise<any> | EffectFiber<any, any> | Effect.Effect<unknown, any, never>
42
+
43
+ export type RunPromise = <A, E>(eff: Effect.Effect<A, E, never>) => Promise<A>
44
+
45
+ /**
46
+ * Wraps the user's `onSubmit` to:
47
+ * - run inside the OpenTelemetry span passed via `meta.currentSpan`
48
+ * - decode the raw form `value` (validators only validate, they don't transform)
49
+ * - normalize Promise / Effect / Fiber return values to a Promise
50
+ *
51
+ * Returns `undefined` when `userOnSubmit` is `undefined` (so callers can pass it
52
+ * directly to `useForm({ onSubmit })` without changing semantics).
53
+ */
54
+ export const wrapOnSubmit = <From, To>(
55
+ userOnSubmit: UserOnSubmit<From, To> | undefined,
56
+ decode: (value: From) => Effect.Effect<To, any, never>,
57
+ runPromise: RunPromise
58
+ ) => {
59
+ if (!userOnSubmit) return undefined
60
+ return ({ formApi, meta, value }: { formApi: OmegaFormParams<From, To>; meta: any; value: From }) =>
61
+ wrapWithSpan(meta?.currentSpan, async () => {
62
+ // validators only validate, they don't actually transform, so we have to do that manually here.
63
+ const parsedValue = await runPromise(decode(value))
64
+ const r = userOnSubmit({
65
+ formApi: formApi as OmegaFormApi<From, To>,
66
+ meta,
67
+ value: parsedValue
68
+ })
69
+ if (Fiber.isFiber(r)) {
70
+ return await runtimeFiberAsPromise(r)
71
+ }
72
+ if (Effect.isEffect(r)) {
73
+ const effectResult = await runPromise(r)
74
+ return Fiber.isFiber(effectResult)
75
+ ? await runtimeFiberAsPromise(effectResult)
76
+ : effectResult
77
+ }
78
+ return r
79
+ })
80
+ }
81
+
82
+ /**
83
+ * Builds the public submit handlers from a `useForm`-returned `form`:
84
+ * - `handleSubmit` injects the current OpenTelemetry span as `meta.currentSpan`.
85
+ * - `handleSubmitEffect` runs `handleSubmit` inside an Effect that picks up the
86
+ * ambient `Effect.currentSpan`. With `checkErrors: true`, it fails with
87
+ * `FormErrors<From>` when validation produced errors.
88
+ */
89
+ export const makeSubmitHandlers = <From, To>(
90
+ form: OmegaFormApi<From, To>
91
+ ) => {
92
+ const hs = form.handleSubmit
93
+
94
+ const handleSubmitInner: typeof form.handleSubmit = async (meta?: Record<string, any>) => {
95
+ return await hs(meta)
96
+ }
97
+
98
+ const handleSubmit = (meta?: Record<string, any>) => {
99
+ const span = api.trace.getSpan(api.context.active())
100
+ return handleSubmitInner({ currentSpan: span, ...meta })
101
+ }
102
+
103
+ const handleSubmitEffect_ = (meta?: Record<string, any>) =>
104
+ Effect.currentSpan.pipe(
105
+ Effect.option,
106
+ Effect
107
+ .flatMap((span) =>
108
+ Effect.promise(() => handleSubmitInner(Option.isSome(span) ? { currentSpan: span.value, ...meta } : meta))
109
+ )
110
+ )
111
+
112
+ const handleSubmitEffect: {
113
+ (options: { checkErrors: true; meta?: Record<string, any> }): Effect.Effect<void, FormErrors<From>>
114
+ (options?: { meta?: Record<string, any> }): Effect.Effect<void>
115
+ } = (
116
+ options?: { meta?: Record<string, any>; checkErrors?: true }
117
+ ): any =>
118
+ options?.checkErrors
119
+ ? handleSubmitEffect_(options?.meta).pipe(Effect.flatMap(Effect.fnUntraced(function*() {
120
+ const errors = form.getAllErrors()
121
+ if (Object.keys(errors.fields).length || errors.form.errors.length) {
122
+ return yield* Effect.fail(new FormErrors({ form: errors.form, fields: errors.fields }))
123
+ }
124
+ })))
125
+ : handleSubmitEffect_(options?.meta)
126
+
127
+ return { handleSubmit, handleSubmitEffect }
128
+ }