@effect-app/vue-components 4.0.0-beta.22 → 4.0.0-beta.221
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 +13 -9
- package/dist/reset.css +39 -38
- package/dist/types/components/CommandButton.vue.d.ts +6 -4
- package/dist/types/components/OmegaForm/OmegaArray.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaAutoGen.vue.d.ts +2 -2
- package/dist/types/components/OmegaForm/OmegaErrorsInternal.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaFormInput.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaInput.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaInternalInput.vue.d.ts +2 -1
- package/dist/types/components/OmegaForm/OmegaWrapper.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/createUseFormWithCustomInput.d.ts +2 -2
- package/dist/types/components/OmegaForm/errors.d.ts +33 -0
- package/dist/types/components/OmegaForm/getOmegaStore.d.ts +1 -1
- package/dist/types/components/OmegaForm/hocs.d.ts +3 -0
- package/dist/types/components/OmegaForm/index.d.ts +13 -3
- package/dist/types/components/OmegaForm/inputs.d.ts +4 -0
- package/dist/types/components/OmegaForm/meta/checks.d.ts +4 -0
- package/dist/types/components/OmegaForm/meta/createMeta.d.ts +33 -0
- package/dist/types/components/OmegaForm/meta/defaults.d.ts +2 -0
- package/dist/types/components/OmegaForm/meta/redacted.d.ts +2 -0
- package/dist/types/components/OmegaForm/meta/types.d.ts +56 -0
- package/dist/types/components/OmegaForm/meta/walker.d.ts +18 -0
- package/dist/types/components/OmegaForm/persistency.d.ts +58 -0
- package/dist/types/components/OmegaForm/submit.d.ts +60 -0
- package/dist/types/components/OmegaForm/types.d.ts +283 -0
- package/dist/types/components/OmegaForm/useOmegaForm.d.ts +7 -213
- package/dist/types/components/OmegaForm/validation/localized.d.ts +10 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/utils/index.d.ts +8 -8
- package/dist/vue-components.es.js +29 -44
- package/dist/vue-components10.es.js +5 -0
- package/dist/vue-components100.es.js +269 -0
- package/dist/vue-components102.es.js +8 -0
- package/dist/vue-components103.es.js +73 -0
- package/dist/vue-components104.es.js +5 -0
- package/dist/vue-components105.es.js +52 -0
- package/dist/vue-components106.es.js +5 -0
- package/dist/vue-components107.es.js +24 -0
- package/dist/vue-components108.es.js +5 -0
- package/dist/vue-components109.es.js +59 -0
- package/dist/vue-components11.es.js +20 -0
- package/dist/vue-components110.es.js +5 -0
- package/dist/vue-components111.es.js +12 -0
- package/dist/vue-components112.es.js +22 -0
- package/dist/vue-components114.es.js +9 -0
- package/dist/vue-components115.es.js +4 -0
- package/dist/vue-components116.es.js +38 -0
- package/dist/vue-components117.es.js +27 -0
- package/dist/vue-components118.es.js +28 -0
- package/dist/vue-components119.es.js +7 -0
- package/dist/vue-components12.es.js +27 -0
- package/dist/vue-components120.es.js +18 -0
- package/dist/vue-components121.es.js +36 -0
- package/dist/vue-components122.es.js +18 -0
- package/dist/vue-components123.es.js +21 -0
- package/dist/vue-components124.es.js +30 -0
- package/dist/vue-components125.es.js +7 -0
- package/dist/vue-components126.es.js +9 -0
- package/dist/vue-components127.es.js +38 -0
- package/dist/vue-components128.es.js +25 -0
- package/dist/vue-components129.es.js +128 -0
- package/dist/vue-components13.es.js +70 -0
- package/dist/vue-components130.es.js +24 -0
- package/dist/vue-components131.es.js +21 -0
- package/dist/vue-components132.es.js +9 -0
- package/dist/vue-components133.es.js +19 -0
- package/dist/vue-components134.es.js +5 -0
- package/dist/vue-components135.es.js +29 -0
- package/dist/vue-components136.es.js +5 -0
- package/dist/vue-components137.es.js +43 -0
- package/dist/vue-components138.es.js +82 -0
- package/dist/vue-components139.es.js +33 -0
- package/dist/vue-components14.es.js +15 -0
- package/dist/vue-components140.es.js +19 -0
- package/dist/vue-components141.es.js +48 -0
- package/dist/vue-components15.es.js +29 -0
- package/dist/vue-components16.es.js +53 -0
- package/dist/vue-components17.es.js +87 -0
- package/dist/vue-components18.es.js +4 -0
- package/dist/vue-components19.es.js +4 -0
- package/dist/vue-components2.es.js +20 -0
- package/dist/vue-components20.es.js +19 -0
- package/dist/vue-components21.es.js +73 -0
- package/dist/vue-components22.es.js +14 -0
- package/dist/vue-components23.es.js +26 -0
- package/dist/vue-components24.es.js +252 -0
- package/dist/vue-components25.es.js +65 -0
- package/dist/vue-components26.es.js +68 -0
- package/dist/vue-components27.es.js +8 -0
- package/dist/vue-components28.es.js +12 -0
- package/dist/vue-components29.es.js +6 -0
- package/dist/vue-components3.es.js +86 -0
- package/dist/vue-components30.es.js +28 -0
- package/dist/vue-components31.es.js +75 -0
- package/dist/vue-components32.es.js +109 -0
- package/dist/vue-components33.es.js +57 -0
- package/dist/vue-components34.es.js +7 -0
- package/dist/vue-components35.es.js +4 -0
- package/dist/vue-components36.es.js +6 -0
- package/dist/vue-components37.es.js +688 -0
- package/dist/vue-components38.es.js +7 -0
- package/dist/vue-components39.es.js +5 -0
- package/dist/vue-components4.es.js +5 -0
- package/dist/vue-components40.es.js +6 -0
- package/dist/vue-components41.es.js +27 -0
- package/dist/vue-components42.es.js +5 -0
- package/dist/vue-components43.es.js +48 -0
- package/dist/vue-components44.es.js +70 -0
- package/dist/vue-components45.es.js +9 -0
- package/dist/vue-components46.es.js +10 -0
- package/dist/vue-components47.es.js +8 -0
- package/dist/vue-components48.es.js +173 -0
- package/dist/vue-components49.es.js +14 -0
- package/dist/vue-components5.es.js +24 -0
- package/dist/vue-components50.es.js +11 -0
- package/dist/vue-components51.es.js +4 -0
- package/dist/vue-components52.es.js +7 -0
- package/dist/vue-components53.es.js +29 -0
- package/dist/vue-components54.es.js +221 -0
- package/dist/vue-components55.es.js +85 -0
- package/dist/vue-components56.es.js +74 -0
- package/dist/vue-components57.es.js +819 -0
- package/dist/vue-components58.es.js +4 -0
- package/dist/vue-components59.es.js +109 -0
- package/dist/vue-components6.es.js +13 -0
- package/dist/vue-components60.es.js +54 -0
- package/dist/vue-components61.es.js +520 -0
- package/dist/vue-components62.es.js +440 -0
- package/dist/vue-components63.es.js +10 -0
- package/dist/vue-components64.es.js +13 -0
- package/dist/vue-components65.es.js +19 -0
- package/dist/vue-components66.es.js +29 -0
- package/dist/vue-components67.es.js +22 -0
- package/dist/vue-components68.es.js +30 -0
- package/dist/vue-components69.es.js +29 -0
- package/dist/vue-components7.es.js +13 -0
- package/dist/vue-components70.es.js +73 -0
- package/dist/vue-components71.es.js +6 -0
- package/dist/vue-components72.es.js +18 -0
- package/dist/vue-components73.es.js +5 -0
- package/dist/vue-components74.es.js +70 -0
- package/dist/vue-components75.es.js +140 -0
- package/dist/vue-components76.es.js +4 -0
- package/dist/vue-components77.es.js +21 -0
- package/dist/vue-components78.es.js +55 -0
- package/dist/vue-components79.es.js +9 -0
- package/dist/vue-components8.es.js +35 -0
- package/dist/vue-components80.es.js +16 -0
- package/dist/vue-components81.es.js +39 -0
- package/dist/vue-components82.es.js +49 -0
- package/dist/vue-components83.es.js +128 -0
- package/dist/vue-components84.es.js +65 -0
- package/dist/vue-components85.es.js +63 -0
- package/dist/vue-components86.es.js +25 -0
- package/dist/vue-components87.es.js +5 -0
- package/dist/vue-components88.es.js +80 -0
- package/dist/vue-components89.es.js +95 -0
- package/dist/vue-components9.es.js +47 -0
- package/dist/vue-components90.es.js +73 -0
- package/dist/vue-components91.es.js +12 -0
- package/dist/vue-components92.es.js +56 -0
- package/dist/vue-components93.es.js +5 -0
- package/dist/vue-components94.es.js +44 -0
- package/dist/vue-components95.es.js +5 -0
- package/dist/vue-components96.es.js +84 -0
- package/dist/vue-components98.es.js +8 -0
- package/dist/vue-components99.es.js +9 -0
- package/package.json +29 -29
- package/src/components/CommandButton.vue +55 -7
- package/src/components/OmegaForm/OmegaArray.vue +2 -4
- package/src/components/OmegaForm/OmegaAutoGen.vue +3 -2
- package/src/components/OmegaForm/OmegaErrorsInternal.vue +1 -1
- package/src/components/OmegaForm/OmegaFormInput.vue +1 -1
- package/src/components/OmegaForm/OmegaInput.vue +7 -36
- package/src/components/OmegaForm/OmegaInputVuetify.vue +5 -2
- package/src/components/OmegaForm/OmegaInternalInput.vue +12 -6
- package/src/components/OmegaForm/OmegaTaggedUnion.vue +2 -1
- package/src/components/OmegaForm/OmegaTaggedUnionInternal.vue +1 -1
- package/src/components/OmegaForm/OmegaWrapper.vue +1 -1
- package/src/components/OmegaForm/blockDialog.ts +18 -6
- package/src/components/OmegaForm/createUseFormWithCustomInput.ts +2 -1
- package/src/components/OmegaForm/errors.ts +136 -0
- package/src/components/OmegaForm/getOmegaStore.ts +1 -1
- package/src/components/OmegaForm/hocs.ts +19 -0
- package/src/components/OmegaForm/index.ts +16 -4
- package/src/components/OmegaForm/inputs.ts +22 -0
- package/src/components/OmegaForm/meta/checks.ts +82 -0
- package/src/components/OmegaForm/meta/createMeta.ts +140 -0
- package/src/components/OmegaForm/meta/defaults.ts +134 -0
- package/src/components/OmegaForm/meta/redacted.ts +66 -0
- package/src/components/OmegaForm/meta/types.ts +78 -0
- package/src/components/OmegaForm/meta/walker.ts +248 -0
- package/src/components/OmegaForm/persistency.ts +247 -0
- package/src/components/OmegaForm/submit.ts +131 -0
- package/src/components/OmegaForm/types.ts +753 -0
- package/src/components/OmegaForm/useOmegaForm.ts +59 -893
- package/src/components/OmegaForm/useRegisterField.ts +1 -1
- package/src/components/OmegaForm/validation/localized.ts +203 -0
- package/src/index.ts +0 -1
- package/src/reset.css +39 -38
- package/src/utils/index.ts +11 -8
- package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +0 -159
- package/dist/types/constants/index.d.ts +0 -1
- package/dist/vue-components.es10.js +0 -239
- package/dist/vue-components.es11.js +0 -32
- package/dist/vue-components.es12.js +0 -503
- package/dist/vue-components.es13.js +0 -49
- package/dist/vue-components.es14.js +0 -4
- package/dist/vue-components.es15.js +0 -4
- package/dist/vue-components.es16.js +0 -6
- package/dist/vue-components.es17.js +0 -13
- package/dist/vue-components.es18.js +0 -57
- package/dist/vue-components.es19.js +0 -56
- package/dist/vue-components.es2.js +0 -30
- package/dist/vue-components.es20.js +0 -8
- package/dist/vue-components.es21.js +0 -8
- package/dist/vue-components.es22.js +0 -5
- package/dist/vue-components.es23.js +0 -5
- package/dist/vue-components.es24.js +0 -4
- package/dist/vue-components.es25.js +0 -4
- package/dist/vue-components.es26.js +0 -4
- package/dist/vue-components.es27.js +0 -4
- package/dist/vue-components.es28.js +0 -19
- package/dist/vue-components.es29.js +0 -13
- package/dist/vue-components.es3.js +0 -17
- package/dist/vue-components.es30.js +0 -194
- package/dist/vue-components.es32.js +0 -31
- package/dist/vue-components.es33.js +0 -6
- package/dist/vue-components.es34.js +0 -4
- package/dist/vue-components.es35.js +0 -4
- package/dist/vue-components.es36.js +0 -113
- package/dist/vue-components.es38.js +0 -9
- package/dist/vue-components.es39.js +0 -34
- package/dist/vue-components.es4.js +0 -52
- package/dist/vue-components.es41.js +0 -6
- package/dist/vue-components.es42.js +0 -25
- package/dist/vue-components.es43.js +0 -7
- package/dist/vue-components.es44.js +0 -23
- package/dist/vue-components.es45.js +0 -32
- package/dist/vue-components.es46.js +0 -24
- package/dist/vue-components.es47.js +0 -14
- package/dist/vue-components.es48.js +0 -7
- package/dist/vue-components.es49.js +0 -21
- package/dist/vue-components.es5.js +0 -52
- package/dist/vue-components.es50.js +0 -11
- package/dist/vue-components.es51.js +0 -33
- package/dist/vue-components.es52.js +0 -50
- package/dist/vue-components.es53.js +0 -28
- package/dist/vue-components.es54.js +0 -13
- package/dist/vue-components.es55.js +0 -67
- package/dist/vue-components.es56.js +0 -58
- package/dist/vue-components.es57.js +0 -19
- package/dist/vue-components.es58.js +0 -35
- package/dist/vue-components.es59.js +0 -31
- package/dist/vue-components.es6.js +0 -69
- package/dist/vue-components.es60.js +0 -44
- package/dist/vue-components.es61.js +0 -4
- package/dist/vue-components.es62.js +0 -46
- package/dist/vue-components.es63.js +0 -4
- package/dist/vue-components.es7.js +0 -83
- package/dist/vue-components.es8.js +0 -63
- package/dist/vue-components.es9.js +0 -21
- package/src/components/OmegaForm/OmegaFormStuff.ts +0 -1276
- package/src/constants/index.ts +0 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as S from "effect-app/Schema"
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Checks if an AST node is a S.Redacted Declaration without encoding.
|
|
5
|
+
* These need to be swapped to S.RedactedFromValue for form usage
|
|
6
|
+
* because S.Redacted expects Redacted objects, not plain strings.
|
|
7
|
+
*/
|
|
8
|
+
const isRedactedWithoutEncoding = (ast: S.AST.AST): boolean =>
|
|
9
|
+
S.AST.isDeclaration(ast)
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Effect Schema AST annotations are loosely typed
|
|
11
|
+
&& (ast.annotations as any)?.typeConstructor?._tag === "effect/Redacted"
|
|
12
|
+
&& !ast.encoding
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* Creates a form-compatible schema by replacing S.Redacted(X) with
|
|
16
|
+
* S.RedactedFromValue(X). S.Redacted is a Declaration that expects
|
|
17
|
+
* Redacted<A> on both encoded and type sides, so form inputs (which
|
|
18
|
+
* produce plain strings) fail validation. S.RedactedFromValue accepts
|
|
19
|
+
* plain values on the encoded side and wraps them in Redacted on decode.
|
|
20
|
+
*/
|
|
21
|
+
export const toFormSchema = <From, To>(
|
|
22
|
+
schema: S.Codec<To, From>
|
|
23
|
+
): S.Codec<To, From> => {
|
|
24
|
+
const ast = schema.ast
|
|
25
|
+
const objAst = S.AST.isObjects(ast)
|
|
26
|
+
? ast
|
|
27
|
+
: S.AST.isDeclaration(ast)
|
|
28
|
+
? S.AST.toEncoded(ast)
|
|
29
|
+
: null
|
|
30
|
+
|
|
31
|
+
if (!objAst || !("propertySignatures" in objAst)) return schema
|
|
32
|
+
|
|
33
|
+
let hasRedacted = false
|
|
34
|
+
const props: Record<string, S.Struct.Fields[string]> = {}
|
|
35
|
+
|
|
36
|
+
for (const p of objAst.propertySignatures) {
|
|
37
|
+
if (isRedactedWithoutEncoding(p.type)) {
|
|
38
|
+
hasRedacted = true
|
|
39
|
+
const innerSchema = S.make((p.type as S.AST.Declaration).typeParameters[0])
|
|
40
|
+
props[p.name as string] = S.RedactedFromValue(innerSchema)
|
|
41
|
+
} else if (S.AST.isUnion(p.type)) {
|
|
42
|
+
const types = p.type.types
|
|
43
|
+
const redactedType = types.find(isRedactedWithoutEncoding)
|
|
44
|
+
if (redactedType) {
|
|
45
|
+
hasRedacted = true
|
|
46
|
+
const innerSchema = S.make((redactedType as S.AST.Declaration).typeParameters[0])
|
|
47
|
+
const hasNull = types.some(S.AST.isNull)
|
|
48
|
+
const hasUndefined = types.some(S.AST.isUndefined)
|
|
49
|
+
const base = S.RedactedFromValue(innerSchema)
|
|
50
|
+
props[p.name as string] = hasNull && hasUndefined
|
|
51
|
+
? S.NullishOr(base)
|
|
52
|
+
: hasNull
|
|
53
|
+
? S.NullOr(base)
|
|
54
|
+
: hasUndefined
|
|
55
|
+
? S.UndefinedOr(base)
|
|
56
|
+
: base
|
|
57
|
+
} else {
|
|
58
|
+
props[p.name as string] = S.make(p.type)
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
props[p.name as string] = S.make(p.type)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return hasRedacted ? S.Struct(props) as unknown as S.Codec<To, From> : schema
|
|
66
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import type { DeepKeys } from "@tanstack/vue-form"
|
|
3
|
+
import type * as S from "effect-app/Schema"
|
|
4
|
+
import type { Redacted } from "effect/Redacted"
|
|
5
|
+
|
|
6
|
+
// Recursively replace Redacted<A> with its inner type so DeepKeys treats it as a leaf
|
|
7
|
+
type StripRedacted<T> = T extends Redacted<any> ? string
|
|
8
|
+
: T extends ReadonlyArray<infer U> ? ReadonlyArray<StripRedacted<U>>
|
|
9
|
+
: T extends Record<string, any> ? { [K in keyof T]: StripRedacted<T[K]> }
|
|
10
|
+
: T
|
|
11
|
+
|
|
12
|
+
export type NestedKeyOf<T> = DeepKeys<StripRedacted<T>>
|
|
13
|
+
|
|
14
|
+
// Field metadata type definitions
|
|
15
|
+
export type BaseFieldMeta = {
|
|
16
|
+
required: boolean
|
|
17
|
+
nullableOrUndefined?: false | "undefined" | "null"
|
|
18
|
+
/**
|
|
19
|
+
* True when the schema property is `S.optionalKey` (AST
|
|
20
|
+
* `context.isOptional`) — i.e. the key should be ABSENT from the submitted
|
|
21
|
+
* object when empty, not present with `undefined`. Distinct from
|
|
22
|
+
* `required: false`, which may also mean "empty string is valid" for
|
|
23
|
+
* unconstrained `S.String` fields.
|
|
24
|
+
*/
|
|
25
|
+
isOptionalKey?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type StringFieldMeta = BaseFieldMeta & {
|
|
29
|
+
type: "string"
|
|
30
|
+
maxLength?: number
|
|
31
|
+
minLength?: number
|
|
32
|
+
format?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type NumberFieldMeta = BaseFieldMeta & {
|
|
36
|
+
type: "number"
|
|
37
|
+
minimum?: number
|
|
38
|
+
maximum?: number
|
|
39
|
+
exclusiveMinimum?: number
|
|
40
|
+
exclusiveMaximum?: number
|
|
41
|
+
refinement?: "int"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type SelectFieldMeta = BaseFieldMeta & {
|
|
45
|
+
type: "select"
|
|
46
|
+
members: any[] // TODO: should be non empty array?
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type MultipleFieldMeta = BaseFieldMeta & {
|
|
50
|
+
type: "multiple"
|
|
51
|
+
members: any[] // TODO: should be non empty array?
|
|
52
|
+
rest: readonly S.AST.AST[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type BooleanFieldMeta = BaseFieldMeta & {
|
|
56
|
+
type: "boolean"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type DateFieldMeta = BaseFieldMeta & {
|
|
60
|
+
type: "date"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type UnknownFieldMeta = BaseFieldMeta & {
|
|
64
|
+
type: "unknown"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type FieldMeta =
|
|
68
|
+
| StringFieldMeta
|
|
69
|
+
| NumberFieldMeta
|
|
70
|
+
| SelectFieldMeta
|
|
71
|
+
| MultipleFieldMeta
|
|
72
|
+
| BooleanFieldMeta
|
|
73
|
+
| DateFieldMeta
|
|
74
|
+
| UnknownFieldMeta
|
|
75
|
+
|
|
76
|
+
export type MetaRecord<T = string> = {
|
|
77
|
+
[K in NestedKeyOf<T>]?: FieldMeta
|
|
78
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any -- AST walker interops with Effect Schema generics */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-use-before-define -- mutual recursion between walk and helpers (handleStruct/handleUnion/etc.) */
|
|
3
|
+
import * as S from "effect-app/Schema"
|
|
4
|
+
import { getFieldMetadataFromAst } from "./checks"
|
|
5
|
+
import { isNullableOrUndefined, unwrapDeclaration } from "./createMeta"
|
|
6
|
+
import type { FieldMeta, MetaRecord, NestedKeyOf, SelectFieldMeta } from "./types"
|
|
7
|
+
|
|
8
|
+
const isNullishType = (property: S.AST.AST) => S.AST.isUndefined(property) || S.AST.isNull(property)
|
|
9
|
+
|
|
10
|
+
// TODO: remove after manual _tag deprecation — S.Struct({ _tag: S.Literal("X") }) wraps as Union([Literal("X")])
|
|
11
|
+
const unwrapSingleLiteralUnion = (ast: S.AST.AST): S.AST.AST =>
|
|
12
|
+
S.AST.isUnion(ast) && ast.types.length === 1 && S.AST.isLiteral(ast.types[0])
|
|
13
|
+
? ast.types[0]
|
|
14
|
+
: ast
|
|
15
|
+
|
|
16
|
+
const unwrapNestedUnions = (types: readonly S.AST.AST[]): readonly S.AST.AST[] =>
|
|
17
|
+
types.flatMap((type) => S.AST.isUnion(type) ? unwrapNestedUnions(type.types) : [type])
|
|
18
|
+
|
|
19
|
+
export type WalkerContext<T> = {
|
|
20
|
+
acc: Partial<MetaRecord<T>>
|
|
21
|
+
unionMeta: Record<string, MetaRecord<T>>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type ParentMeta = {
|
|
25
|
+
required: boolean
|
|
26
|
+
nullableOrUndefined: false | "null" | "undefined"
|
|
27
|
+
/** Set when iterating the members of a nullable discriminated union */
|
|
28
|
+
isNullableDiscriminatedUnion?: boolean
|
|
29
|
+
/** Set when this property was declared with S.optionalKey */
|
|
30
|
+
isOptionalKey?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const leafMetaForAst = (
|
|
34
|
+
ast: S.AST.AST,
|
|
35
|
+
parentMeta: ParentMeta
|
|
36
|
+
): FieldMeta => {
|
|
37
|
+
const { nullableOrUndefined, required } = parentMeta
|
|
38
|
+
|
|
39
|
+
if (S.AST.isArrays(ast)) {
|
|
40
|
+
return {
|
|
41
|
+
required,
|
|
42
|
+
nullableOrUndefined,
|
|
43
|
+
type: "multiple",
|
|
44
|
+
members: ast.elements,
|
|
45
|
+
rest: ast.rest
|
|
46
|
+
} as FieldMeta
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (S.AST.isLiteral(ast)) {
|
|
50
|
+
return {
|
|
51
|
+
required,
|
|
52
|
+
nullableOrUndefined,
|
|
53
|
+
type: "select",
|
|
54
|
+
members: [ast.literal]
|
|
55
|
+
} as FieldMeta
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...getFieldMetadataFromAst(ast),
|
|
60
|
+
required,
|
|
61
|
+
nullableOrUndefined
|
|
62
|
+
} as FieldMeta
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const walkStruct = <T>(
|
|
66
|
+
propertySignatures: readonly S.AST.PropertySignature[],
|
|
67
|
+
parent: string,
|
|
68
|
+
parentMeta: ParentMeta,
|
|
69
|
+
ctx: WalkerContext<T>
|
|
70
|
+
): void => {
|
|
71
|
+
for (const p of propertySignatures) {
|
|
72
|
+
const key = parent ? `${parent}.${p.name.toString()}` : p.name.toString()
|
|
73
|
+
const nullableOrUndefined = isNullableOrUndefined(p.type)
|
|
74
|
+
const isOptionalKey = (p.type as any).context?.isOptional === true
|
|
75
|
+
|
|
76
|
+
let isRequired: boolean
|
|
77
|
+
if (parentMeta.isNullableDiscriminatedUnion && p.name.toString() === "_tag") {
|
|
78
|
+
isRequired = false
|
|
79
|
+
} else if (!parentMeta.required) {
|
|
80
|
+
isRequired = false
|
|
81
|
+
} else if (isOptionalKey) {
|
|
82
|
+
isRequired = false
|
|
83
|
+
} else {
|
|
84
|
+
isRequired = !nullableOrUndefined
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
walk(
|
|
88
|
+
p.type,
|
|
89
|
+
key,
|
|
90
|
+
{ required: isRequired, nullableOrUndefined, isOptionalKey },
|
|
91
|
+
ctx
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const classifyAndWalkUnion = <T>(
|
|
97
|
+
unionAst: S.AST.Union,
|
|
98
|
+
key: string,
|
|
99
|
+
parentMeta: ParentMeta,
|
|
100
|
+
ctx: WalkerContext<T>
|
|
101
|
+
): void => {
|
|
102
|
+
const { acc } = ctx
|
|
103
|
+
const unwrappedTypes = unwrapNestedUnions(unionAst.types).map(unwrapDeclaration)
|
|
104
|
+
const nonNullTypes = unwrappedTypes.filter((t) => !isNullishType(t))
|
|
105
|
+
|
|
106
|
+
// Boolean literal shortcut (single-value union wrapping a boolean literal)
|
|
107
|
+
if (nonNullTypes.length === 1 && S.AST.isLiteral(nonNullTypes[0]) && typeof nonNullTypes[0].literal === "boolean") {
|
|
108
|
+
acc[key as NestedKeyOf<T>] = leafMetaForAst(nonNullTypes[0], parentMeta)
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (nonNullTypes.some(S.AST.isObjects)) {
|
|
113
|
+
const isNullableDiscriminatedUnion = !!parentMeta.nullableOrUndefined && nonNullTypes.length > 1
|
|
114
|
+
|
|
115
|
+
// Mixed union: also create a parent leaf entry from the first non-struct member
|
|
116
|
+
if (!parentMeta.nullableOrUndefined && key) {
|
|
117
|
+
const firstNonStruct = nonNullTypes.find((t) => !S.AST.isObjects(t))
|
|
118
|
+
if (firstNonStruct) {
|
|
119
|
+
acc[key as NestedKeyOf<T>] = leafMetaForAst(firstNonStruct, parentMeta)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const discriminatorValues: any[] = []
|
|
124
|
+
const branchParentMeta: ParentMeta = isNullableDiscriminatedUnion
|
|
125
|
+
? { required: true, nullableOrUndefined: false, isNullableDiscriminatedUnion: true }
|
|
126
|
+
: { required: true, nullableOrUndefined: false }
|
|
127
|
+
|
|
128
|
+
for (const memberType of nonNullTypes) {
|
|
129
|
+
if (!S.AST.isObjects(memberType)) continue
|
|
130
|
+
|
|
131
|
+
const tagProp = memberType.propertySignatures.find((p) => p.name.toString() === "_tag")
|
|
132
|
+
const resolvedTagType = tagProp ? unwrapSingleLiteralUnion(tagProp.type) : null
|
|
133
|
+
let tagValue: string | null = null
|
|
134
|
+
|
|
135
|
+
if (resolvedTagType && S.AST.isLiteral(resolvedTagType)) {
|
|
136
|
+
tagValue = resolvedTagType.literal as string
|
|
137
|
+
if (!discriminatorValues.includes(tagValue)) discriminatorValues.push(tagValue)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const branchCtx: WalkerContext<T> = { acc: {}, unionMeta: ctx.unionMeta }
|
|
141
|
+
walkStruct(memberType.propertySignatures, key, branchParentMeta, branchCtx)
|
|
142
|
+
|
|
143
|
+
if (tagValue) {
|
|
144
|
+
const existing = ctx.unionMeta[tagValue]
|
|
145
|
+
if (existing) Object.assign(existing, branchCtx.acc as MetaRecord<T>)
|
|
146
|
+
else ctx.unionMeta[tagValue] = branchCtx.acc as MetaRecord<T>
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const [metaKey, metaValue] of Object.entries(branchCtx.acc)) {
|
|
150
|
+
const existing = acc[metaKey as NestedKeyOf<T>]
|
|
151
|
+
if (existing && existing.type === "select" && (metaValue as any)?.type === "select") {
|
|
152
|
+
existing.members = [
|
|
153
|
+
...existing.members,
|
|
154
|
+
...(metaValue as SelectFieldMeta).members.filter((m: any) => !existing.members.includes(m))
|
|
155
|
+
]
|
|
156
|
+
} else {
|
|
157
|
+
acc[metaKey as NestedKeyOf<T>] = metaValue as FieldMeta
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (discriminatorValues.length > 0) {
|
|
163
|
+
const tagKey = key ? `${key}._tag` : "_tag"
|
|
164
|
+
const existing = acc[tagKey as NestedKeyOf<T>]
|
|
165
|
+
if (existing && existing.type === "select") {
|
|
166
|
+
for (const v of discriminatorValues) {
|
|
167
|
+
if (!existing.members.includes(v)) existing.members.push(v)
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
acc[tagKey as NestedKeyOf<T>] = {
|
|
171
|
+
type: "select",
|
|
172
|
+
members: discriminatorValues,
|
|
173
|
+
required: !isNullableDiscriminatedUnion
|
|
174
|
+
} as FieldMeta
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const ast = nonNullTypes.find(S.AST.isArrays)
|
|
181
|
+
if (ast) {
|
|
182
|
+
walk(ast, key, parentMeta, ctx)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Literal / primitive union (e.g. legacy _tag pattern)
|
|
187
|
+
const resolvedTypes = unwrappedTypes.map(unwrapSingleLiteralUnion)
|
|
188
|
+
if (resolvedTypes.every((_) => isNullishType(_) || S.AST.isLiteral(_))) {
|
|
189
|
+
const { isOptionalKey, nullableOrUndefined, required } = parentMeta
|
|
190
|
+
const leaf: FieldMeta = {
|
|
191
|
+
required,
|
|
192
|
+
nullableOrUndefined,
|
|
193
|
+
type: "select",
|
|
194
|
+
members: resolvedTypes.filter(S.AST.isLiteral).map((t) => t.literal)
|
|
195
|
+
} as FieldMeta
|
|
196
|
+
if (isOptionalKey) leaf.isOptionalKey = true
|
|
197
|
+
acc[key as NestedKeyOf<T>] = leaf
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Fallback: recurse into first non-null type
|
|
202
|
+
const nonNullType = nonNullTypes[0]
|
|
203
|
+
if (nonNullType) walk(nonNullType, key, parentMeta, ctx)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export const walk = <T>(
|
|
207
|
+
ast: S.AST.AST,
|
|
208
|
+
key: string,
|
|
209
|
+
parentMeta: ParentMeta,
|
|
210
|
+
ctx: WalkerContext<T>
|
|
211
|
+
): void => {
|
|
212
|
+
ast = unwrapDeclaration(ast)
|
|
213
|
+
const { acc } = ctx
|
|
214
|
+
|
|
215
|
+
if (S.AST.isObjects(ast)) {
|
|
216
|
+
walkStruct(ast.propertySignatures, key, parentMeta, ctx)
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (S.AST.isUnion(ast)) {
|
|
221
|
+
classifyAndWalkUnion(ast, key, parentMeta, ctx)
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (S.AST.isArrays(ast)) {
|
|
226
|
+
const restElement = ast.rest.length > 0 ? unwrapDeclaration(ast.rest[0]) : null
|
|
227
|
+
if (restElement && S.AST.isObjects(restElement)) {
|
|
228
|
+
// Array-of-struct: skip creating a meta entry for the array itself,
|
|
229
|
+
// recurse into the element struct's properties instead
|
|
230
|
+
walkStruct(restElement.propertySignatures, key, { required: true, nullableOrUndefined: false }, ctx)
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Primitive or tuple array
|
|
235
|
+
acc[key as NestedKeyOf<T>] = leafMetaForAst(ast, parentMeta)
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Leaf primitive / literal / unknown
|
|
240
|
+
const { isOptionalKey, nullableOrUndefined, required } = parentMeta
|
|
241
|
+
const adjusted: ParentMeta = {
|
|
242
|
+
required: required && (!S.AST.isString(ast) || !!getFieldMetadataFromAst(ast).minLength),
|
|
243
|
+
nullableOrUndefined
|
|
244
|
+
}
|
|
245
|
+
const leaf = leafMetaForAst(ast, adjusted)
|
|
246
|
+
if (isOptionalKey) leaf.isOptionalKey = true
|
|
247
|
+
acc[key as NestedKeyOf<T>] = leaf
|
|
248
|
+
}
|
|
@@ -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
|
|
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
|
+
}
|