@effect-app/vue-components 0.0.1
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.
- package/README.md +184 -0
- package/dist/types/components/OmegaForm/OmegaErrors.vue.d.ts +2 -0
- package/dist/types/components/OmegaForm/OmegaErrorsContext.d.ts +35 -0
- package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +85 -0
- package/dist/types/components/OmegaForm/OmegaInput.vue.d.ts +38 -0
- package/dist/types/components/OmegaForm/OmegaInternalInput.vue.d.ts +25 -0
- package/dist/types/components/OmegaForm/OmegaWrapper.vue.d.ts +43 -0
- package/dist/types/components/OmegaForm/getOmegaStore.d.ts +3 -0
- package/dist/types/components/OmegaForm/index.d.ts +52 -0
- package/dist/types/components/OmegaForm/useOmegaForm.d.ts +6 -0
- package/dist/types/components/TestComponent.vue.d.ts +6 -0
- package/dist/types/components/index.d.ts +3 -0
- package/dist/types/constants/index.d.ts +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/utils/index.d.ts +7 -0
- package/dist/vue-components.css +1 -0
- package/dist/vue-components.es.js +624 -0
- package/package.json +48 -0
- package/src/assets/.keep +0 -0
- package/src/components/OmegaForm/OmegaErrors.vue +143 -0
- package/src/components/OmegaForm/OmegaErrorsContext.ts +64 -0
- package/src/components/OmegaForm/OmegaFormStuff.ts +575 -0
- package/src/components/OmegaForm/OmegaInput.vue +91 -0
- package/src/components/OmegaForm/OmegaInternalInput.vue +216 -0
- package/src/components/OmegaForm/OmegaWrapper.vue +137 -0
- package/src/components/OmegaForm/getOmegaStore.ts +32 -0
- package/src/components/OmegaForm/index.ts +29 -0
- package/src/components/OmegaForm/useOmegaForm.ts +61 -0
- package/src/components/TestComponent.vue +15 -0
- package/src/components/index.ts +6 -0
- package/src/components/style.css +3 -0
- package/src/constants/index.ts +1 -0
- package/src/env.d.ts +8 -0
- package/src/index.ts +17 -0
- package/src/utils/index.ts +12 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
import { pipe, S, Option, type Record, type Effect } from "effect-app"
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
import {
|
|
4
|
+
type FormAsyncValidateOrFn,
|
|
5
|
+
type FormValidateOrFn,
|
|
6
|
+
type StandardSchemaV1,
|
|
7
|
+
type FormApi,
|
|
8
|
+
type VueFormApi,
|
|
9
|
+
type FieldComponent,
|
|
10
|
+
type FormOptions,
|
|
11
|
+
type DeepKeys,
|
|
12
|
+
type FieldValidateOrFn,
|
|
13
|
+
type FieldAsyncValidateOrFn,
|
|
14
|
+
type FormState,
|
|
15
|
+
} from "@tanstack/vue-form"
|
|
16
|
+
import type { Component } from "vue"
|
|
17
|
+
import { useIntl } from "../../utils"
|
|
18
|
+
|
|
19
|
+
export type TypeOverride =
|
|
20
|
+
| "string"
|
|
21
|
+
| "number"
|
|
22
|
+
| "select"
|
|
23
|
+
| "multiple"
|
|
24
|
+
| "boolean"
|
|
25
|
+
|
|
26
|
+
export interface OmegaError {
|
|
27
|
+
label: string
|
|
28
|
+
inputId: string
|
|
29
|
+
errors: readonly string[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const isArrayOfString = S.NonEmptyArray(S.String)
|
|
33
|
+
|
|
34
|
+
export type FormProps<To, From> = Omit<
|
|
35
|
+
FormOptions<
|
|
36
|
+
To,
|
|
37
|
+
FormValidateOrFn<To> | undefined,
|
|
38
|
+
FormValidateOrFn<To> | undefined,
|
|
39
|
+
StandardSchemaV1<To, From>,
|
|
40
|
+
FormValidateOrFn<To> | undefined,
|
|
41
|
+
FormAsyncValidateOrFn<To> | undefined,
|
|
42
|
+
FormValidateOrFn<To> | undefined,
|
|
43
|
+
FormAsyncValidateOrFn<To> | undefined,
|
|
44
|
+
FormAsyncValidateOrFn<To> | undefined,
|
|
45
|
+
FormAsyncValidateOrFn<To> | undefined
|
|
46
|
+
>,
|
|
47
|
+
"onSubmit"
|
|
48
|
+
> & {
|
|
49
|
+
onSubmit?: (props: {
|
|
50
|
+
formApi: OmegaFormParams<To, From>
|
|
51
|
+
meta: any
|
|
52
|
+
value: From
|
|
53
|
+
}) => Promise<any> | any
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type OmegaFormParams<To, From> = FormApi<
|
|
57
|
+
To,
|
|
58
|
+
FormValidateOrFn<To> | undefined,
|
|
59
|
+
FormValidateOrFn<To> | undefined,
|
|
60
|
+
StandardSchemaV1<To, From>,
|
|
61
|
+
FormValidateOrFn<To> | undefined,
|
|
62
|
+
FormAsyncValidateOrFn<To> | undefined,
|
|
63
|
+
FormValidateOrFn<To> | undefined,
|
|
64
|
+
FormAsyncValidateOrFn<To> | undefined,
|
|
65
|
+
FormAsyncValidateOrFn<To> | undefined,
|
|
66
|
+
FormAsyncValidateOrFn<To> | undefined
|
|
67
|
+
>
|
|
68
|
+
|
|
69
|
+
export type OmegaFormState<To, From> = FormState<
|
|
70
|
+
To,
|
|
71
|
+
FormValidateOrFn<To> | undefined,
|
|
72
|
+
FormValidateOrFn<To> | undefined,
|
|
73
|
+
StandardSchemaV1<To, From>,
|
|
74
|
+
FormValidateOrFn<To> | undefined,
|
|
75
|
+
FormAsyncValidateOrFn<To> | undefined,
|
|
76
|
+
FormValidateOrFn<To> | undefined,
|
|
77
|
+
FormAsyncValidateOrFn<To> | undefined,
|
|
78
|
+
FormAsyncValidateOrFn<To> | undefined
|
|
79
|
+
>
|
|
80
|
+
|
|
81
|
+
export type OmegaFormApi<To, From> = OmegaFormParams<To, From> &
|
|
82
|
+
VueFormApi<
|
|
83
|
+
To,
|
|
84
|
+
FormValidateOrFn<To> | undefined,
|
|
85
|
+
FormValidateOrFn<To> | undefined,
|
|
86
|
+
StandardSchemaV1<To, From>,
|
|
87
|
+
FormValidateOrFn<To> | undefined,
|
|
88
|
+
FormAsyncValidateOrFn<To> | undefined,
|
|
89
|
+
FormValidateOrFn<To> | undefined,
|
|
90
|
+
FormAsyncValidateOrFn<To> | undefined,
|
|
91
|
+
FormAsyncValidateOrFn<To> | undefined,
|
|
92
|
+
FormAsyncValidateOrFn<To> | undefined
|
|
93
|
+
>
|
|
94
|
+
|
|
95
|
+
export type FormComponent<T, S> = FieldComponent<
|
|
96
|
+
T,
|
|
97
|
+
FormValidateOrFn<T> | undefined,
|
|
98
|
+
FormValidateOrFn<T> | undefined,
|
|
99
|
+
StandardSchemaV1<T, S>,
|
|
100
|
+
FormValidateOrFn<T> | undefined,
|
|
101
|
+
FormAsyncValidateOrFn<T> | undefined,
|
|
102
|
+
FormValidateOrFn<T> | undefined,
|
|
103
|
+
FormAsyncValidateOrFn<T> | undefined,
|
|
104
|
+
FormAsyncValidateOrFn<T> | undefined,
|
|
105
|
+
FormAsyncValidateOrFn<T> | undefined
|
|
106
|
+
> &
|
|
107
|
+
Component
|
|
108
|
+
|
|
109
|
+
export type FormType<T, S> = OmegaFormApi<T, S> & {
|
|
110
|
+
Field: Component
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type PrefixFromDepth<
|
|
114
|
+
K extends string | number,
|
|
115
|
+
_TDepth extends any[],
|
|
116
|
+
> = K
|
|
117
|
+
|
|
118
|
+
export type PrefixObjectAccessor<
|
|
119
|
+
T extends object,
|
|
120
|
+
TDepth extends any[] = [],
|
|
121
|
+
> = {
|
|
122
|
+
[K in keyof T]-?: K extends string | number
|
|
123
|
+
?
|
|
124
|
+
| PrefixFromDepth<K, TDepth>
|
|
125
|
+
| `${PrefixFromDepth<K, TDepth>}${DeepKeys<T[K], [TDepth]>}`
|
|
126
|
+
: never
|
|
127
|
+
}[keyof T]
|
|
128
|
+
|
|
129
|
+
export type NestedKeyOf<T> = T extends object ? PrefixObjectAccessor<T> : never
|
|
130
|
+
|
|
131
|
+
export type FieldValidators<T> = {
|
|
132
|
+
onChangeAsync?: FieldAsyncValidateOrFn<T, any, any>
|
|
133
|
+
onChange?: FieldValidateOrFn<T, any, any>
|
|
134
|
+
onBlur?: FieldValidateOrFn<T, any, any>
|
|
135
|
+
onBlurAsync?: FieldAsyncValidateOrFn<T, any, any>
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Field metadata type definitions
|
|
139
|
+
export type BaseFieldMeta = {
|
|
140
|
+
required: boolean
|
|
141
|
+
nullableOrUndefined?: false | "undefined" | "null"
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export type StringFieldMeta = BaseFieldMeta & {
|
|
145
|
+
type: "string"
|
|
146
|
+
maxLength?: number
|
|
147
|
+
minLength?: number
|
|
148
|
+
format?: string
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export type NumberFieldMeta = BaseFieldMeta & {
|
|
152
|
+
type: "number"
|
|
153
|
+
minimum?: number
|
|
154
|
+
maximum?: number
|
|
155
|
+
exclusiveMinimum?: number
|
|
156
|
+
exclusiveMaximum?: number
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export type SelectFieldMeta = BaseFieldMeta & {
|
|
160
|
+
type: "select"
|
|
161
|
+
members: any[]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export type MultipleFieldMeta = BaseFieldMeta & {
|
|
165
|
+
type: "multiple"
|
|
166
|
+
members: any[]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export type BooleanFieldMeta = BaseFieldMeta & {
|
|
170
|
+
type: "boolean"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export type UnknownFieldMeta = BaseFieldMeta & {
|
|
174
|
+
type: "unknown"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export type FieldMeta =
|
|
178
|
+
| StringFieldMeta
|
|
179
|
+
| NumberFieldMeta
|
|
180
|
+
| SelectFieldMeta
|
|
181
|
+
| MultipleFieldMeta
|
|
182
|
+
| BooleanFieldMeta
|
|
183
|
+
| UnknownFieldMeta
|
|
184
|
+
|
|
185
|
+
export type MetaRecord<T = string> = {
|
|
186
|
+
[K in NestedKeyOf<T>]?: FieldMeta
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export type FilterItems = {
|
|
190
|
+
items: readonly [string, ...string[]]
|
|
191
|
+
message:
|
|
192
|
+
| string
|
|
193
|
+
| Effect.Effect<string, never, never>
|
|
194
|
+
| { readonly message: string | Effect.Effect<string> }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
type CreateMeta = {
|
|
198
|
+
parent?: string
|
|
199
|
+
meta?: Record<string, any>
|
|
200
|
+
nullableOrUndefined?: false | "undefined" | "null"
|
|
201
|
+
} & (
|
|
202
|
+
| {
|
|
203
|
+
propertySignatures: readonly S.AST.PropertySignature[]
|
|
204
|
+
property?: never
|
|
205
|
+
}
|
|
206
|
+
| {
|
|
207
|
+
propertySignatures?: never
|
|
208
|
+
property: S.AST.AST
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
const getNullableOrUndefined = (property: S.AST.AST) => {
|
|
213
|
+
return (
|
|
214
|
+
S.AST.isUnion(property) &&
|
|
215
|
+
property.types.find(_ => _._tag === "UndefinedKeyword" || _ === S.Null.ast)
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const isNullableOrUndefined = (property: false | S.AST.AST | undefined) => {
|
|
220
|
+
if (!property || !S.AST.isUnion(property)) return false
|
|
221
|
+
if (property.types.find(_ => _._tag === "UndefinedKeyword"))
|
|
222
|
+
return "undefined"
|
|
223
|
+
if (property.types.find(_ => _ === S.Null.ast)) return "null"
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const createMeta = <T = any>(
|
|
228
|
+
{ meta = {}, parent = "", property, propertySignatures }: CreateMeta,
|
|
229
|
+
acc: Partial<MetaRecord<T>> = {},
|
|
230
|
+
): MetaRecord<T> | FieldMeta => {
|
|
231
|
+
if (property && property._tag === "Transformation") {
|
|
232
|
+
return createMeta<T>({
|
|
233
|
+
parent,
|
|
234
|
+
meta,
|
|
235
|
+
property: property.from,
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (property?._tag === "TypeLiteral" && "propertySignatures" in property) {
|
|
240
|
+
return createMeta<T>({
|
|
241
|
+
meta,
|
|
242
|
+
propertySignatures: property.propertySignatures,
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (propertySignatures) {
|
|
247
|
+
for (const p of propertySignatures) {
|
|
248
|
+
const key = parent ? `${parent}.${p.name.toString()}` : p.name.toString()
|
|
249
|
+
const nullableOrUndefined = isNullableOrUndefined(p.type)
|
|
250
|
+
const isRequired = meta["required"] ?? !nullableOrUndefined
|
|
251
|
+
|
|
252
|
+
let typeToProcess = p.type
|
|
253
|
+
if (S.AST.isUnion(p.type)) {
|
|
254
|
+
typeToProcess = p.type.types.find(
|
|
255
|
+
t => t._tag !== "UndefinedKeyword" && t !== S.Null.ast,
|
|
256
|
+
)!
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if ("propertySignatures" in typeToProcess) {
|
|
260
|
+
Object.assign(
|
|
261
|
+
acc,
|
|
262
|
+
createMeta<T>({
|
|
263
|
+
parent: key,
|
|
264
|
+
propertySignatures: typeToProcess.propertySignatures,
|
|
265
|
+
meta: { required: isRequired, nullableOrUndefined },
|
|
266
|
+
}),
|
|
267
|
+
)
|
|
268
|
+
} else {
|
|
269
|
+
const newMeta = createMeta<T>({
|
|
270
|
+
parent: key,
|
|
271
|
+
property: p.type,
|
|
272
|
+
meta: { required: isRequired, nullableOrUndefined },
|
|
273
|
+
})
|
|
274
|
+
acc[key as NestedKeyOf<T>] = newMeta as FieldMeta
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return acc
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (property) {
|
|
281
|
+
const nullableOrUndefined = getNullableOrUndefined(property)
|
|
282
|
+
|
|
283
|
+
if (!Object.hasOwnProperty.call(meta, "required")) {
|
|
284
|
+
meta["required"] = !nullableOrUndefined
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (S.AST.isUnion(property)) {
|
|
288
|
+
const nonNullType = property.types.find(
|
|
289
|
+
t => t._tag !== "UndefinedKeyword" && t !== S.Null.ast,
|
|
290
|
+
)!
|
|
291
|
+
|
|
292
|
+
if ("propertySignatures" in nonNullType) {
|
|
293
|
+
return createMeta<T>({
|
|
294
|
+
propertySignatures: nonNullType.propertySignatures,
|
|
295
|
+
parent,
|
|
296
|
+
meta,
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (property.types.every(S.AST.isLiteral)) {
|
|
301
|
+
return {
|
|
302
|
+
...meta,
|
|
303
|
+
type: "select",
|
|
304
|
+
members: property.types.map(t => t.literal),
|
|
305
|
+
} as FieldMeta
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
...meta,
|
|
310
|
+
...createMeta<T>({
|
|
311
|
+
parent,
|
|
312
|
+
meta,
|
|
313
|
+
property: nonNullType,
|
|
314
|
+
}),
|
|
315
|
+
} as FieldMeta
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (S.AST.isTupleType(property)) {
|
|
319
|
+
return {
|
|
320
|
+
...meta,
|
|
321
|
+
type: "multiple",
|
|
322
|
+
members: property.elements,
|
|
323
|
+
} as FieldMeta
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const JSONAnnotation = S.AST.getAnnotation(
|
|
327
|
+
property,
|
|
328
|
+
S.AST.JSONSchemaAnnotationId,
|
|
329
|
+
).pipe(Option.getOrElse(() => ({}))) as Record<string, unknown>
|
|
330
|
+
|
|
331
|
+
meta = { ...meta, ...JSONAnnotation }
|
|
332
|
+
|
|
333
|
+
if ("from" in property) {
|
|
334
|
+
return createMeta<T>({
|
|
335
|
+
parent,
|
|
336
|
+
meta,
|
|
337
|
+
property: property.from,
|
|
338
|
+
})
|
|
339
|
+
} else {
|
|
340
|
+
meta["type"] = S.AST.getAnnotation(
|
|
341
|
+
property,
|
|
342
|
+
S.AST.TitleAnnotationId,
|
|
343
|
+
).pipe(
|
|
344
|
+
Option.getOrElse(() => {
|
|
345
|
+
return "unknown"
|
|
346
|
+
}),
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return meta as FieldMeta
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return acc
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const flattenMeta = <From, To>(
|
|
357
|
+
schema: S.Schema<From, To, never>,
|
|
358
|
+
): MetaRecord<To> => {
|
|
359
|
+
const ast = schema.ast
|
|
360
|
+
const result: MetaRecord<To> = {}
|
|
361
|
+
|
|
362
|
+
if (ast._tag === "Transformation" || ast._tag === "Refinement") {
|
|
363
|
+
return flattenMeta(S.make(ast.from))
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if ("propertySignatures" in ast) {
|
|
367
|
+
const meta = createMeta<To>({
|
|
368
|
+
propertySignatures: ast.propertySignatures,
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
if (Object.values(meta).every(value => value && "type" in value)) {
|
|
372
|
+
return meta as MetaRecord<To>
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const flattenObject = (
|
|
376
|
+
obj: Record<string, any>,
|
|
377
|
+
parentKey: string = "",
|
|
378
|
+
) => {
|
|
379
|
+
for (const key in obj) {
|
|
380
|
+
const newKey = parentKey ? `${parentKey}.${key}` : key
|
|
381
|
+
if (obj[key] && typeof obj[key] === "object" && "type" in obj[key]) {
|
|
382
|
+
result[newKey as NestedKeyOf<To>] = obj[key] as FieldMeta
|
|
383
|
+
} else if (obj[key] && typeof obj[key] === "object") {
|
|
384
|
+
flattenObject(obj[key], newKey)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
flattenObject(meta)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return result
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export const duplicateSchema = <From, To>(
|
|
396
|
+
schema: S.Schema<From, To, never>,
|
|
397
|
+
) => {
|
|
398
|
+
return S.extend(schema, S.Struct({}))
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export const generateMetaFromSchema = <From, To>(
|
|
402
|
+
schema: S.Schema<From, To, never>,
|
|
403
|
+
): {
|
|
404
|
+
schema: S.Schema<From, To, never>
|
|
405
|
+
meta: MetaRecord<To>
|
|
406
|
+
filterItems?: FilterItems
|
|
407
|
+
} => {
|
|
408
|
+
const meta = flattenMeta(schema)
|
|
409
|
+
|
|
410
|
+
const filterItems = pipe(
|
|
411
|
+
schema.ast,
|
|
412
|
+
Option.liftPredicate(s => s._tag === "Refinement" && "filter" in s),
|
|
413
|
+
Option.flatMap(s => S.AST.getJSONSchemaAnnotation(s)),
|
|
414
|
+
Option.filter(s => "items" in s),
|
|
415
|
+
Option.filterMap(({ items }) =>
|
|
416
|
+
S.decodeUnknownOption(isArrayOfString)(items),
|
|
417
|
+
),
|
|
418
|
+
Option.zipWith(
|
|
419
|
+
S.AST.getMessageAnnotation(schema.ast),
|
|
420
|
+
(items, message) => ({
|
|
421
|
+
items,
|
|
422
|
+
message: message("" as unknown as S.ParseResult.ParseIssue),
|
|
423
|
+
}),
|
|
424
|
+
),
|
|
425
|
+
Option.getOrUndefined,
|
|
426
|
+
)
|
|
427
|
+
return { schema, meta, filterItems }
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export const generateInputStandardSchemaFromFieldMeta = (
|
|
431
|
+
meta: FieldMeta,
|
|
432
|
+
): StandardSchemaV1<any, any> => {
|
|
433
|
+
const { trans } = useIntl()
|
|
434
|
+
let schema: S.Schema<any, any, never>
|
|
435
|
+
switch (meta.type) {
|
|
436
|
+
case "string":
|
|
437
|
+
schema = S.String.annotations({
|
|
438
|
+
message: () => trans("validation.empty"),
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
if (meta.format === "email") {
|
|
442
|
+
schema = S.compose(
|
|
443
|
+
schema,
|
|
444
|
+
S.Email.annotations({
|
|
445
|
+
message: () => trans("validation.email.invalid"),
|
|
446
|
+
}),
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (meta.required) {
|
|
451
|
+
schema.annotations({
|
|
452
|
+
message: () => trans("validation.empty"),
|
|
453
|
+
})
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (meta.maxLength) {
|
|
457
|
+
schema = schema.pipe(S.maxLength(meta.maxLength)).annotations({
|
|
458
|
+
message: () =>
|
|
459
|
+
trans("validation.string.maxLength", {
|
|
460
|
+
maxLength: meta.maxLength,
|
|
461
|
+
}),
|
|
462
|
+
})
|
|
463
|
+
}
|
|
464
|
+
if (meta.minLength) {
|
|
465
|
+
schema = schema.pipe(S.minLength(meta.minLength)).annotations({
|
|
466
|
+
message: () =>
|
|
467
|
+
trans("validation.string.minLength", {
|
|
468
|
+
minLength: meta.minLength,
|
|
469
|
+
}),
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
break
|
|
473
|
+
|
|
474
|
+
case "number":
|
|
475
|
+
schema = S.Number.annotations({
|
|
476
|
+
message: () => trans("validation.empty"),
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
if (meta.required) {
|
|
480
|
+
schema.annotations({
|
|
481
|
+
message: () => trans("validation.empty"),
|
|
482
|
+
})
|
|
483
|
+
}
|
|
484
|
+
if (meta.minimum) {
|
|
485
|
+
schema = schema.pipe(S.greaterThanOrEqualTo(meta.minimum)).annotations({
|
|
486
|
+
message: () =>
|
|
487
|
+
trans("validation.number.min", {
|
|
488
|
+
minimum: meta.minimum,
|
|
489
|
+
isExclusive: true,
|
|
490
|
+
}),
|
|
491
|
+
})
|
|
492
|
+
}
|
|
493
|
+
if (meta.maximum) {
|
|
494
|
+
schema = schema.pipe(S.lessThanOrEqualTo(meta.maximum)).annotations({
|
|
495
|
+
message: () =>
|
|
496
|
+
trans("validation.number.max", {
|
|
497
|
+
maximum: meta.maximum,
|
|
498
|
+
isExclusive: true,
|
|
499
|
+
}),
|
|
500
|
+
})
|
|
501
|
+
}
|
|
502
|
+
if (meta.exclusiveMinimum) {
|
|
503
|
+
schema = schema.pipe(S.greaterThan(meta.exclusiveMinimum)).annotations({
|
|
504
|
+
message: () =>
|
|
505
|
+
trans("validation.number.min", {
|
|
506
|
+
minimum: meta.exclusiveMinimum,
|
|
507
|
+
isExclusive: false,
|
|
508
|
+
}),
|
|
509
|
+
})
|
|
510
|
+
}
|
|
511
|
+
if (meta.exclusiveMaximum) {
|
|
512
|
+
schema = schema.pipe(S.lessThan(meta.exclusiveMaximum)).annotations({
|
|
513
|
+
message: () =>
|
|
514
|
+
trans("validation.number.max", {
|
|
515
|
+
maximum: meta.exclusiveMaximum,
|
|
516
|
+
isExclusive: false,
|
|
517
|
+
}),
|
|
518
|
+
})
|
|
519
|
+
}
|
|
520
|
+
break
|
|
521
|
+
case "select":
|
|
522
|
+
schema = S.Literal(...meta.members).annotations({
|
|
523
|
+
message: () => ({
|
|
524
|
+
message: trans("validation.not_a_valid", {
|
|
525
|
+
type: "select",
|
|
526
|
+
message: meta.members.join(", "),
|
|
527
|
+
}),
|
|
528
|
+
override: true,
|
|
529
|
+
}),
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
break
|
|
533
|
+
|
|
534
|
+
case "multiple":
|
|
535
|
+
schema = S.Array(S.String).annotations({
|
|
536
|
+
message: () =>
|
|
537
|
+
trans("validation.not_a_valid", {
|
|
538
|
+
type: "multiple",
|
|
539
|
+
message: meta.members.join(", "),
|
|
540
|
+
}),
|
|
541
|
+
})
|
|
542
|
+
break
|
|
543
|
+
|
|
544
|
+
case "boolean":
|
|
545
|
+
schema = S.Boolean
|
|
546
|
+
break
|
|
547
|
+
// todo: switch must be exhaustive or have default case, otherwise falls through with schema undefined.
|
|
548
|
+
|
|
549
|
+
case "unknown":
|
|
550
|
+
schema = S.Unknown
|
|
551
|
+
break
|
|
552
|
+
}
|
|
553
|
+
if (!meta.required) {
|
|
554
|
+
schema = S.NullishOr(schema)
|
|
555
|
+
} else {
|
|
556
|
+
schema.pipe(
|
|
557
|
+
S.annotations({
|
|
558
|
+
message: () => trans("validation.empty"),
|
|
559
|
+
}),
|
|
560
|
+
)
|
|
561
|
+
}
|
|
562
|
+
const result = S.standardSchemaV1(schema)
|
|
563
|
+
return result
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export const nullableInput = <A, I, R>(
|
|
567
|
+
schema: S.Schema<A, I, R>,
|
|
568
|
+
defaultValue: () => A,
|
|
569
|
+
) =>
|
|
570
|
+
S.NullOr(schema).pipe(
|
|
571
|
+
S.transform(S.typeSchema(schema), {
|
|
572
|
+
decode: input => input ?? defaultValue(),
|
|
573
|
+
encode: input => input,
|
|
574
|
+
}),
|
|
575
|
+
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="form.Field"
|
|
4
|
+
:name="name"
|
|
5
|
+
:validators="{
|
|
6
|
+
onChange: schema,
|
|
7
|
+
...validators,
|
|
8
|
+
}"
|
|
9
|
+
>
|
|
10
|
+
<template
|
|
11
|
+
#default="{
|
|
12
|
+
field,
|
|
13
|
+
}: {
|
|
14
|
+
// TODO: exact type
|
|
15
|
+
field: FieldApi<
|
|
16
|
+
any,
|
|
17
|
+
any,
|
|
18
|
+
any,
|
|
19
|
+
any,
|
|
20
|
+
any,
|
|
21
|
+
any,
|
|
22
|
+
any,
|
|
23
|
+
any,
|
|
24
|
+
any,
|
|
25
|
+
any,
|
|
26
|
+
any,
|
|
27
|
+
any,
|
|
28
|
+
any,
|
|
29
|
+
any,
|
|
30
|
+
any,
|
|
31
|
+
any,
|
|
32
|
+
any,
|
|
33
|
+
any,
|
|
34
|
+
any
|
|
35
|
+
>
|
|
36
|
+
}"
|
|
37
|
+
>
|
|
38
|
+
<slot
|
|
39
|
+
:field="field"
|
|
40
|
+
:label="label"
|
|
41
|
+
:options="options"
|
|
42
|
+
:meta="meta"
|
|
43
|
+
:type="type"
|
|
44
|
+
>
|
|
45
|
+
<OmegaInternalInput
|
|
46
|
+
:field="field"
|
|
47
|
+
:label="label"
|
|
48
|
+
:options="options"
|
|
49
|
+
:meta="meta"
|
|
50
|
+
:type="type"
|
|
51
|
+
/>
|
|
52
|
+
</slot>
|
|
53
|
+
</template>
|
|
54
|
+
</component>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<script setup lang="ts" generic="From, To">
|
|
58
|
+
import { computed } from "vue"
|
|
59
|
+
import {
|
|
60
|
+
generateInputStandardSchemaFromFieldMeta,
|
|
61
|
+
type FieldValidators,
|
|
62
|
+
type FormType,
|
|
63
|
+
type MetaRecord,
|
|
64
|
+
type NestedKeyOf,
|
|
65
|
+
type TypeOverride,
|
|
66
|
+
} from "./OmegaFormStuff"
|
|
67
|
+
import OmegaInternalInput from "./OmegaInternalInput.vue"
|
|
68
|
+
import type { FieldApi } from "@tanstack/vue-form"
|
|
69
|
+
|
|
70
|
+
const props = defineProps<{
|
|
71
|
+
form: FormType<From, To> & {
|
|
72
|
+
meta: MetaRecord<To>
|
|
73
|
+
}
|
|
74
|
+
name: NestedKeyOf<To>
|
|
75
|
+
validators?: FieldValidators<From>
|
|
76
|
+
label: string
|
|
77
|
+
options?: { title: string; value: string }[]
|
|
78
|
+
type?: TypeOverride
|
|
79
|
+
}>()
|
|
80
|
+
|
|
81
|
+
const meta = computed(() => {
|
|
82
|
+
return props.form.meta[props.name]
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const schema = computed(() => {
|
|
86
|
+
if (!meta.value) {
|
|
87
|
+
throw new Error("Meta is undefined")
|
|
88
|
+
}
|
|
89
|
+
return generateInputStandardSchemaFromFieldMeta(meta.value)
|
|
90
|
+
})
|
|
91
|
+
</script>
|