@effect-app/vue-components 4.0.0-beta.31 → 4.0.0-beta.310
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 +24 -5
- package/dist/types/components/Dialog.vue.d.ts +3 -1
- package/dist/types/components/FixedNuxtErrorBoundary.vue.d.ts +39 -0
- package/dist/types/components/OmegaForm/InputProps.d.ts +1 -1
- 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/OmegaInputVuetify.vue.d.ts +6 -0
- package/dist/types/components/OmegaForm/OmegaInternalInput.vue.d.ts +6 -9
- package/dist/types/components/OmegaForm/OmegaTaggedUnion.vue.d.ts +2 -2
- package/dist/types/components/OmegaForm/OmegaTaggedUnionInternal.vue.d.ts +3 -3
- package/dist/types/components/OmegaForm/OmegaWrapper.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/createUseFormWithCustomInput.d.ts +4 -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 +14 -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 +21 -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 +312 -0
- package/dist/types/components/OmegaForm/useOmegaForm.d.ts +9 -213
- package/dist/types/components/OmegaForm/validation/localized.d.ts +10 -0
- package/dist/types/components/index.d.ts +1 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/utils/index.d.ts +8 -8
- package/dist/vue-components.es.js +30 -44
- package/dist/vue-components10.es.js +5 -0
- package/dist/vue-components11.es.js +56 -0
- package/dist/vue-components12.es.js +5 -0
- package/dist/vue-components13.es.js +20 -0
- package/dist/vue-components14.es.js +49 -0
- package/dist/vue-components15.es.js +128 -0
- package/dist/vue-components16.es.js +65 -0
- package/dist/vue-components17.es.js +114 -0
- package/dist/vue-components18.es.js +22 -0
- package/dist/vue-components19.es.js +5 -0
- package/dist/vue-components2.es.js +11 -0
- package/dist/vue-components20.es.js +80 -0
- package/dist/vue-components21.es.js +93 -0
- package/dist/vue-components22.es.js +73 -0
- package/dist/vue-components23.es.js +12 -0
- package/dist/vue-components24.es.js +57 -0
- package/dist/vue-components25.es.js +5 -0
- package/dist/vue-components26.es.js +44 -0
- package/dist/vue-components27.es.js +5 -0
- package/dist/vue-components28.es.js +84 -0
- package/dist/vue-components3.es.js +98 -0
- package/dist/vue-components30.es.js +8 -0
- package/dist/vue-components31.es.js +9 -0
- package/dist/vue-components32.es.js +24 -0
- package/dist/vue-components33.es.js +300 -0
- package/dist/vue-components35.es.js +8 -0
- package/dist/vue-components36.es.js +73 -0
- package/dist/vue-components37.es.js +5 -0
- package/dist/vue-components38.es.js +64 -0
- package/dist/vue-components39.es.js +5 -0
- package/dist/vue-components4.es.js +5 -0
- package/dist/vue-components40.es.js +24 -0
- package/dist/vue-components41.es.js +5 -0
- package/dist/vue-components42.es.js +59 -0
- package/dist/vue-components43.es.js +5 -0
- package/dist/vue-components44.es.js +12 -0
- package/dist/vue-components45.es.js +22 -0
- package/dist/vue-components47.es.js +9 -0
- package/dist/vue-components48.es.js +4 -0
- package/dist/vue-components49.es.js +38 -0
- package/dist/vue-components5.es.js +24 -0
- package/dist/vue-components50.es.js +27 -0
- package/dist/vue-components51.es.js +28 -0
- package/dist/vue-components52.es.js +7 -0
- package/dist/vue-components53.es.js +18 -0
- package/dist/vue-components54.es.js +36 -0
- package/dist/vue-components55.es.js +18 -0
- package/dist/vue-components56.es.js +21 -0
- package/dist/vue-components57.es.js +30 -0
- package/dist/vue-components58.es.js +7 -0
- package/dist/vue-components59.es.js +9 -0
- package/dist/vue-components6.es.js +13 -0
- package/dist/vue-components60.es.js +38 -0
- package/dist/vue-components61.es.js +25 -0
- package/dist/vue-components62.es.js +128 -0
- package/dist/vue-components63.es.js +24 -0
- package/dist/vue-components64.es.js +21 -0
- package/dist/vue-components65.es.js +9 -0
- package/dist/vue-components66.es.js +19 -0
- package/dist/vue-components67.es.js +5 -0
- package/dist/vue-components68.es.js +29 -0
- package/dist/vue-components69.es.js +5 -0
- package/dist/vue-components7.es.js +13 -0
- package/dist/vue-components70.es.js +43 -0
- package/dist/vue-components71.es.js +101 -0
- package/dist/vue-components72.es.js +36 -0
- package/dist/vue-components73.es.js +19 -0
- package/dist/vue-components74.es.js +50 -0
- package/dist/vue-components8.es.js +35 -0
- package/dist/vue-components9.es.js +47 -0
- package/package.json +39 -33
- package/src/components/CommandButton.vue +96 -16
- package/src/components/Dialog.vue +0 -1
- package/src/components/FixedNuxtErrorBoundary.vue +112 -0
- package/src/components/OmegaForm/InputProps.ts +1 -1
- package/src/components/OmegaForm/OmegaArray.vue +8 -9
- 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 +26 -40
- package/src/components/OmegaForm/OmegaInputVuetify.vue +58 -8
- package/src/components/OmegaForm/OmegaInternalInput.vue +32 -13
- package/src/components/OmegaForm/OmegaTaggedUnion.vue +10 -3
- package/src/components/OmegaForm/OmegaTaggedUnionInternal.vue +6 -6
- package/src/components/OmegaForm/OmegaWrapper.vue +1 -1
- package/src/components/OmegaForm/blockDialog.ts +18 -6
- package/src/components/OmegaForm/createUseFormWithCustomInput.ts +16 -14
- 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 +17 -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 +262 -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 +801 -0
- package/src/components/OmegaForm/useOmegaForm.ts +103 -896
- package/src/components/OmegaForm/useRegisterField.ts +1 -1
- package/src/components/OmegaForm/validation/localized.ts +203 -0
- package/src/components/index.ts +1 -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
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script
|
|
2
2
|
setup
|
|
3
3
|
lang="ts"
|
|
4
|
-
generic="I = never"
|
|
4
|
+
generic="I = never, RA = unknown, RE = unknown"
|
|
5
5
|
>
|
|
6
|
-
import type { CommandBase } from "@effect-app/vue"
|
|
6
|
+
import type { CommandBase, Progress } from "@effect-app/vue/makeClient"
|
|
7
|
+
import * as Option from "effect-app/Option"
|
|
8
|
+
import type * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
7
9
|
import { computed } from "vue"
|
|
8
10
|
import type { VBtn } from "vuetify/components"
|
|
9
11
|
|
|
@@ -14,38 +16,89 @@ const props = defineProps<
|
|
|
14
16
|
& (
|
|
15
17
|
| {
|
|
16
18
|
input: NoInfer<I>
|
|
17
|
-
|
|
19
|
+
optionalInput?: undefined
|
|
20
|
+
command: CommandBase<I, any, RA, RE>
|
|
18
21
|
empty?: boolean
|
|
19
22
|
}
|
|
20
23
|
| {
|
|
21
|
-
|
|
24
|
+
optionalInput: Option.Option<NoInfer<I>>
|
|
22
25
|
input?: undefined
|
|
26
|
+
command: CommandBase<I, any, RA, RE>
|
|
27
|
+
empty?: boolean
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
command: CommandBase<any, any, RA, RE>
|
|
31
|
+
input?: undefined
|
|
32
|
+
optionalInput?: undefined
|
|
23
33
|
empty?: boolean
|
|
24
34
|
}
|
|
25
35
|
)
|
|
26
36
|
& {
|
|
27
37
|
disabled?: ButtonProps["disabled"]
|
|
28
38
|
title?: string // why isn't it part of VBtnProps??
|
|
39
|
+
mapProgress?: (result: AsyncResult.AsyncResult<RA, RE>) => Progress | undefined
|
|
29
40
|
}
|
|
30
41
|
& ButtonProps
|
|
31
42
|
>()
|
|
32
43
|
|
|
33
|
-
const
|
|
44
|
+
const resolvedInput = computed<{ _tag: "ready"; value: I } | { _tag: "missing" } | { _tag: "void" }>(() => {
|
|
45
|
+
if ("optionalInput" in props && props.optionalInput !== undefined) {
|
|
46
|
+
return Option.isSome(props.optionalInput)
|
|
47
|
+
? { _tag: "ready", value: props.optionalInput.value }
|
|
48
|
+
: { _tag: "missing" }
|
|
49
|
+
}
|
|
50
|
+
if ("input" in props && props.input !== undefined) {
|
|
51
|
+
return { _tag: "ready", value: props.input as I }
|
|
52
|
+
}
|
|
53
|
+
return { _tag: "void" }
|
|
54
|
+
})
|
|
34
55
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
56
|
+
const isDisabled = computed(() =>
|
|
57
|
+
props.command.blocked
|
|
58
|
+
|| props.disabled
|
|
59
|
+
|| resolvedInput.value._tag === "missing"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const resolvedProgress = computed(() => {
|
|
63
|
+
if (props.mapProgress) {
|
|
64
|
+
const result = props.command.result
|
|
65
|
+
return result !== undefined ? props.mapProgress(result) : undefined
|
|
39
66
|
}
|
|
67
|
+
return props.command.progress
|
|
68
|
+
})
|
|
40
69
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
70
|
+
const progressText = computed(() => {
|
|
71
|
+
const p = resolvedProgress.value
|
|
72
|
+
if (p === undefined) return undefined
|
|
73
|
+
return typeof p === "string" ? p : p.text
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const progressPercentage = computed(() => {
|
|
77
|
+
const p = resolvedProgress.value
|
|
78
|
+
return typeof p === "object" && p !== null ? p.percentage : undefined
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const handleClick = () => {
|
|
82
|
+
if (isDisabled.value) return
|
|
83
|
+
const ri = resolvedInput.value
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
const handle = props.command.handle as any
|
|
86
|
+
if (ri._tag === "ready") handle(ri.value)
|
|
87
|
+
else if (ri._tag === "void") handle(undefined)
|
|
88
|
+
// _tag === "missing" can't reach here — guarded by isDisabled
|
|
45
89
|
}
|
|
46
90
|
</script>
|
|
47
91
|
<script lang="ts">
|
|
48
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* CommandButton connects a command to a button and tracks disabled / loading state.
|
|
94
|
+
*
|
|
95
|
+
* Input variants:
|
|
96
|
+
* - `input`: pass a validated value; button enabled when command + disabled allow it.
|
|
97
|
+
* - `optionalInput`: pass an `Option<I>`. `Some` → enabled, click fires with the value.
|
|
98
|
+
* `None` → disabled. Use this when the input is gated by a `computed` instead of
|
|
99
|
+
* a `v-if` on the button itself.
|
|
100
|
+
* - neither: command takes no input.
|
|
101
|
+
*/
|
|
49
102
|
export default {
|
|
50
103
|
name: "CommandButton"
|
|
51
104
|
}
|
|
@@ -55,27 +108,54 @@ export default {
|
|
|
55
108
|
v-if="command.allowed && !empty"
|
|
56
109
|
v-bind="$attrs"
|
|
57
110
|
:loading="command.waiting"
|
|
111
|
+
:disabled="isDisabled"
|
|
58
112
|
:aria-disabled="isDisabled"
|
|
59
113
|
:title="title ?? command.action"
|
|
60
114
|
:class="{ 'v-btn--disabled': isDisabled }"
|
|
61
115
|
@click="handleClick"
|
|
62
116
|
>
|
|
117
|
+
<template
|
|
118
|
+
v-if="progressText !== undefined"
|
|
119
|
+
#loader
|
|
120
|
+
>
|
|
121
|
+
<v-progress-circular
|
|
122
|
+
:indeterminate="progressPercentage === undefined"
|
|
123
|
+
:model-value="progressPercentage"
|
|
124
|
+
size="20"
|
|
125
|
+
width="2"
|
|
126
|
+
/>
|
|
127
|
+
<span class="ml-2">{{ progressText }}</span>
|
|
128
|
+
</template>
|
|
63
129
|
<slot
|
|
64
130
|
:loading="command.waiting"
|
|
65
131
|
:disabled="isDisabled"
|
|
66
132
|
:label="command.label"
|
|
67
133
|
:title="title ?? command.action"
|
|
68
134
|
>
|
|
69
|
-
|
|
135
|
+
{{ command.label }}
|
|
70
136
|
</slot>
|
|
71
137
|
</v-btn>
|
|
72
138
|
<v-btn
|
|
73
139
|
v-else-if="command.allowed"
|
|
74
140
|
v-bind="$attrs"
|
|
75
141
|
:loading="command.waiting"
|
|
142
|
+
:disabled="isDisabled"
|
|
76
143
|
:aria-disabled="isDisabled"
|
|
77
144
|
:title="title ?? command.action"
|
|
78
145
|
:class="{ 'v-btn--disabled': isDisabled }"
|
|
79
146
|
@click="handleClick"
|
|
80
|
-
|
|
147
|
+
>
|
|
148
|
+
<template
|
|
149
|
+
v-if="progressText !== undefined"
|
|
150
|
+
#loader
|
|
151
|
+
>
|
|
152
|
+
<v-progress-circular
|
|
153
|
+
:indeterminate="progressPercentage === undefined"
|
|
154
|
+
:model-value="progressPercentage"
|
|
155
|
+
size="20"
|
|
156
|
+
width="2"
|
|
157
|
+
/>
|
|
158
|
+
<span class="ml-2">{{ progressText }}</span>
|
|
159
|
+
</template>
|
|
160
|
+
</v-btn>
|
|
81
161
|
</template>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { CauseException } from "effect-app/client"
|
|
3
|
+
import * as Cause from "effect/Cause"
|
|
4
|
+
import { onErrorCaptured, shallowRef } from "vue"
|
|
5
|
+
import { useRouter } from "vue-router"
|
|
6
|
+
|
|
7
|
+
// withDefaults, NOT `props.enabled ?? true`: Vue's boolean casting resolves an
|
|
8
|
+
// ABSENT boolean prop to `false`, so the `??` fallback would never fire and an
|
|
9
|
+
// omitted `enabled` (the frontend case) would silently disable the boundary.
|
|
10
|
+
const props = withDefaults(
|
|
11
|
+
defineProps<{
|
|
12
|
+
captureException: (err: Error, hint?: { extra?: Record<string, unknown> }) => void
|
|
13
|
+
toastError: (message: string) => void
|
|
14
|
+
debug: boolean
|
|
15
|
+
/** register the boundary only when true (backend passes import.meta.client; frontend omits → true) */
|
|
16
|
+
enabled?: boolean
|
|
17
|
+
/** defer handling until app ready (backend: onNuxtReady while hydrating; frontend: omit → run now) */
|
|
18
|
+
scheduleAfterReady?: (fn: () => void) => void
|
|
19
|
+
/** extra side-effect after emit, before error.value is set (backend: nuxtApp vue:error hook) */
|
|
20
|
+
onHandled?: (err: Error, instance: unknown, info: string) => void
|
|
21
|
+
}>(),
|
|
22
|
+
{ enabled: true }
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
defineOptions({ name: "NuxtErrorBoundary", inheritAttrs: false })
|
|
26
|
+
|
|
27
|
+
const emit = defineEmits<{ error: [error: Error] }>()
|
|
28
|
+
|
|
29
|
+
defineSlots<{
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
error(props: { error: Error; clearError: () => void }): any
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
default(): any
|
|
34
|
+
}>()
|
|
35
|
+
|
|
36
|
+
const error = shallowRef<Error | null>(null)
|
|
37
|
+
function clearError() {
|
|
38
|
+
error.value = null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// lol in production these are different...
|
|
42
|
+
const supportedErrors = ["https://vuejs.org/error-reference/#runtime-0", 0, "0", "setup function"]
|
|
43
|
+
|
|
44
|
+
function handleError(...args: Parameters<Parameters<typeof onErrorCaptured<Error>>[0]>) {
|
|
45
|
+
const [err, instance, info] = args
|
|
46
|
+
const fiberFailure = err instanceof CauseException ? err : null
|
|
47
|
+
// PRO: log that we hit the error boundary
|
|
48
|
+
console.warn(
|
|
49
|
+
"NuxtErrorBoundary caught error in " + info + ": " + (fiberFailure ? "CauseException" : "classic error"),
|
|
50
|
+
fiberFailure ? Cause.pretty(fiberFailure.originalCause) : err,
|
|
51
|
+
instance
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
// PRO: we don't handle native event handlers the same way we handle setup errors,
|
|
55
|
+
// because these errors should only get reported, not take over the page.
|
|
56
|
+
if (!supportedErrors.includes(info)) {
|
|
57
|
+
props.captureException(err, { extra: { info, instance } })
|
|
58
|
+
if (props.debug) {
|
|
59
|
+
props.toastError("An unexpected error has occurred: " + err.toString())
|
|
60
|
+
}
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// PRO: don't render interruptions..
|
|
65
|
+
// e.g when we run useSuspenseQuery, and we navigate away before a query is finished, we get a CancelledError
|
|
66
|
+
// if we however render it here instead of the default slot, we will show the cancellation error of the previous page, instead of rendering the new page
|
|
67
|
+
//
|
|
68
|
+
// NOTE the engine-side contract that makes this blanket silence safe: `useSuspenseQuery(New)`
|
|
69
|
+
// converts an interrupt that settles while the component is STILL MOUNTED (a query-side interrupt,
|
|
70
|
+
// e.g. a server RPC interrupt Exit — the thing that used to blank the page) into a typed
|
|
71
|
+
// `SuspenseInterruptedError` (@effect-app/vue makeClient), whose cause is NOT interrupt-only and
|
|
72
|
+
// therefore falls through to the error slot below. What remains interrupt-only here is genuinely
|
|
73
|
+
// "navigated away / unmounted" — silencing it is correct and avoids rendering the previous page's
|
|
74
|
+
// cancellation over the new page.
|
|
75
|
+
if (fiberFailure && Cause.hasInterruptsOnly(fiberFailure.originalCause)) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
emit("error", err)
|
|
80
|
+
props.onHandled?.(err, instance, info)
|
|
81
|
+
error.value = err
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (props.enabled) {
|
|
85
|
+
onErrorCaptured((err, instance, info) => {
|
|
86
|
+
if (props.scheduleAfterReady) props.scheduleAfterReady(() => handleError(err, instance, info))
|
|
87
|
+
else handleError(err, instance, info)
|
|
88
|
+
return false
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// PRO: fix error not clearing after route change: https://github.com/nuxt/nuxt/issues/15781#issuecomment-2320928163
|
|
93
|
+
const router = useRouter()
|
|
94
|
+
router.afterEach(() => {
|
|
95
|
+
clearError()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
defineExpose({ error, clearError })
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
<template>
|
|
102
|
+
<slot
|
|
103
|
+
v-if="error"
|
|
104
|
+
v-bind="{ error, clearError }"
|
|
105
|
+
name="error"
|
|
106
|
+
/>
|
|
107
|
+
|
|
108
|
+
<slot
|
|
109
|
+
v-else
|
|
110
|
+
name="default"
|
|
111
|
+
/>
|
|
112
|
+
</template>
|
|
@@ -43,7 +43,7 @@ export type InputProps<From extends Record<PropertyKey, any>, TName extends Deep
|
|
|
43
43
|
inputClass: string | undefined | null
|
|
44
44
|
}
|
|
45
45
|
field: OmegaFieldInternalApi<From, TName>
|
|
46
|
-
/**
|
|
46
|
+
/** reactive field state, sourced from `field.state` (the Field slot's own `state` prop is a stale snapshot since @tanstack/vue-form 1.32) */
|
|
47
47
|
state: OmegaFieldInternalApi<From, TName>["state"]
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -3,24 +3,23 @@
|
|
|
3
3
|
:is="form.Field"
|
|
4
4
|
:name="name"
|
|
5
5
|
>
|
|
6
|
-
|
|
6
|
+
<!-- `field.state` is reactive; the Field slot's `state` prop is a stale snapshot since @tanstack/vue-form 1.32 -->
|
|
7
|
+
<template #default="{ field }">
|
|
7
8
|
<slot
|
|
8
9
|
name="pre-array"
|
|
9
|
-
v-bind="{ field, state }"
|
|
10
|
+
v-bind="{ field, state: field.state }"
|
|
10
11
|
/>
|
|
11
12
|
<component
|
|
12
13
|
:is="form.Field"
|
|
13
14
|
v-for="(_, i) of items"
|
|
14
15
|
:key="`${name}[${Number(i)}]`"
|
|
15
|
-
:name="
|
|
16
|
-
`${name}[${Number(i)}]` as DeepKeys<From>
|
|
17
|
-
"
|
|
16
|
+
:name="`${name}[${Number(i)}]` as DeepKeys<From>"
|
|
18
17
|
>
|
|
19
|
-
<template #default="{ field: subField
|
|
18
|
+
<template #default="{ field: subField }">
|
|
20
19
|
<slot
|
|
21
20
|
v-bind="{
|
|
22
21
|
subField,
|
|
23
|
-
subState,
|
|
22
|
+
subState: subField.state,
|
|
24
23
|
index: Number(i),
|
|
25
24
|
field
|
|
26
25
|
}"
|
|
@@ -29,7 +28,7 @@
|
|
|
29
28
|
</component>
|
|
30
29
|
<slot
|
|
31
30
|
name="post-array"
|
|
32
|
-
v-bind="{ field, state }"
|
|
31
|
+
v-bind="{ field, state: field.state }"
|
|
33
32
|
/>
|
|
34
33
|
<!-- TODO: legacy slot, remove this slot -->
|
|
35
34
|
<slot
|
|
@@ -46,7 +45,7 @@
|
|
|
46
45
|
>
|
|
47
46
|
import { type DeepKeys } from "@tanstack/vue-form"
|
|
48
47
|
import { computed, onMounted, provide } from "vue"
|
|
49
|
-
import { type OmegaArrayProps } from "./
|
|
48
|
+
import { type OmegaArrayProps } from "./types"
|
|
50
49
|
|
|
51
50
|
const props = defineProps<OmegaArrayProps<From, To, Name>>()
|
|
52
51
|
|
|
@@ -20,9 +20,10 @@
|
|
|
20
20
|
Name extends DeepKeys<From>"
|
|
21
21
|
>
|
|
22
22
|
import { type DeepKeys } from "@tanstack/vue-form"
|
|
23
|
-
import
|
|
23
|
+
import * as Order from "effect/Order"
|
|
24
24
|
import { computed } from "vue"
|
|
25
|
-
import { type FieldMeta
|
|
25
|
+
import { type FieldMeta } from "./meta/types"
|
|
26
|
+
import { type FieldPath, type OmegaAutoGenMeta, type OmegaInputProps } from "./types"
|
|
26
27
|
|
|
27
28
|
type NewMeta = OmegaAutoGenMeta<From, To, Name>
|
|
28
29
|
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
import type { StandardSchemaV1Issue } from "@tanstack/vue-form"
|
|
103
103
|
import { computed, getCurrentInstance } from "vue"
|
|
104
104
|
import { useIntl } from "../../utils"
|
|
105
|
-
import { type OmegaError } from "./
|
|
105
|
+
import { type OmegaError } from "./types"
|
|
106
106
|
|
|
107
107
|
const instance = getCurrentInstance()
|
|
108
108
|
const vuetified = instance?.appContext.components["VAlert"]
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
import { type DeepKeys } from "@tanstack/vue-form"
|
|
18
18
|
import { inject } from "vue"
|
|
19
19
|
import type { MergedInputProps } from "./InputProps"
|
|
20
|
-
import type { BaseProps, DefaultTypeProps, OmegaInputProps } from "./OmegaFormStuff"
|
|
21
20
|
import OmegaInput from "./OmegaInput.vue"
|
|
21
|
+
import type { BaseProps, DefaultTypeProps, OmegaInputProps } from "./types"
|
|
22
22
|
import { OmegaFormKey } from "./useOmegaForm"
|
|
23
23
|
|
|
24
24
|
const form = inject(OmegaFormKey) as unknown as OmegaInputProps<
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<component
|
|
3
3
|
:is="form.Field"
|
|
4
|
-
:key="fieldKey"
|
|
5
4
|
:name="name"
|
|
6
|
-
:validators="
|
|
7
|
-
onChange: schema,
|
|
8
|
-
...validators
|
|
9
|
-
}"
|
|
5
|
+
:validators="validators"
|
|
10
6
|
>
|
|
11
|
-
|
|
7
|
+
<!--
|
|
8
|
+
Read `state` from `field.state`, not the Field slot's `state` prop.
|
|
9
|
+
Since @tanstack/vue-form 1.32 the slot `state` is a one-time snapshot
|
|
10
|
+
taken when the field mounts and never updates, whereas `field.state`
|
|
11
|
+
is a reactive getter backed by the field store.
|
|
12
|
+
-->
|
|
13
|
+
<template #default="{ field }">
|
|
12
14
|
<OmegaInternalInput
|
|
13
15
|
v-if="meta"
|
|
14
|
-
v-bind="{ ...$attrs,
|
|
15
|
-
:field="field"
|
|
16
|
-
:state="state"
|
|
16
|
+
v-bind="{ ...$attrs, ...internalInputProps, inputClass: computedClass }"
|
|
17
|
+
:field="field as any"
|
|
18
|
+
:state="field.state"
|
|
17
19
|
:register="form.registerField"
|
|
18
20
|
:label="label ?? errori18n(propsName)"
|
|
19
21
|
:meta="meta"
|
|
@@ -40,14 +42,24 @@
|
|
|
40
42
|
lang="ts"
|
|
41
43
|
generic="From extends Record<PropertyKey, any>, To extends Record<PropertyKey, any>, Name extends DeepKeys<From>"
|
|
42
44
|
>
|
|
45
|
+
/* eslint-disable @typescript-eslint/no-explicit-any -- TanStack Form Field generic interop and slot prop typing */
|
|
43
46
|
import { type DeepKeys } from "@tanstack/vue-form"
|
|
44
47
|
import { computed, inject, type Ref, useAttrs } from "vue"
|
|
45
|
-
import {
|
|
46
|
-
import { type FieldMeta
|
|
48
|
+
import { useErrorLabel } from "./errors"
|
|
49
|
+
import { type FieldMeta } from "./meta/types"
|
|
47
50
|
import OmegaInternalInput from "./OmegaInternalInput.vue"
|
|
48
|
-
import {
|
|
51
|
+
import { type OmegaInputPropsBase } from "./types"
|
|
49
52
|
|
|
50
|
-
const props = defineProps<OmegaInputPropsBase<From, To, Name>>()
|
|
53
|
+
const props = withDefaults(defineProps<OmegaInputPropsBase<From, To, Name>>(), {
|
|
54
|
+
required: undefined
|
|
55
|
+
})
|
|
56
|
+
const internalInputProps = computed(() => ({
|
|
57
|
+
label: props.label,
|
|
58
|
+
required: props.required,
|
|
59
|
+
type: props.type,
|
|
60
|
+
options: props.options,
|
|
61
|
+
inputs: props.form.inputs
|
|
62
|
+
}))
|
|
51
63
|
|
|
52
64
|
// downgrade to *as* DeepKeys<From> to avoid useless and possible infinite recursion in TS
|
|
53
65
|
const propsName = computed(() => props.name as DeepKeys<From>)
|
|
@@ -57,13 +69,10 @@ defineSlots<{
|
|
|
57
69
|
default?: (props: any) => any
|
|
58
70
|
}>()
|
|
59
71
|
|
|
60
|
-
defineOptions({
|
|
61
|
-
inheritAttrs: false
|
|
62
|
-
})
|
|
72
|
+
defineOptions({ inheritAttrs: false })
|
|
63
73
|
|
|
64
74
|
const attrs = useAttrs()
|
|
65
75
|
|
|
66
|
-
// Compute the class to use based on inputClass prop
|
|
67
76
|
const computedClass = computed(() => {
|
|
68
77
|
if (props.inputClass === null) return undefined
|
|
69
78
|
if (props.inputClass !== undefined) return props.inputClass
|
|
@@ -82,28 +91,5 @@ const meta = computed(() => {
|
|
|
82
91
|
return props.form.meta[propsName.value]
|
|
83
92
|
})
|
|
84
93
|
|
|
85
|
-
// Key to force Field re-mount when meta type changes (for TaggedUnion support)
|
|
86
|
-
const fieldKey = computed(() => {
|
|
87
|
-
const m = meta.value
|
|
88
|
-
if (!m) return propsName.value
|
|
89
|
-
// Include type and key constraints in the key so Field re-mounts when validation rules change
|
|
90
|
-
// Cast to any since not all FieldMeta variants have these properties
|
|
91
|
-
const fm = m as any
|
|
92
|
-
return `${propsName.value}-${fm.type}-${fm.minLength ?? ""}-${fm.maxLength ?? ""}-${fm.minimum ?? ""}-${
|
|
93
|
-
fm.maximum ?? ""
|
|
94
|
-
}`
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
// Call useIntl during setup to avoid issues when computed re-evaluates
|
|
98
|
-
const { trans } = useIntl()
|
|
99
|
-
|
|
100
|
-
const schema = computed(() => {
|
|
101
|
-
if (!meta.value) {
|
|
102
|
-
console.log(props.name, Object.keys(props.form.meta), props.form.meta)
|
|
103
|
-
throw new Error("Meta is undefined")
|
|
104
|
-
}
|
|
105
|
-
return generateInputStandardSchemaFromFieldMeta(meta.value, trans)
|
|
106
|
-
})
|
|
107
|
-
|
|
108
94
|
const errori18n = useErrorLabel(props.form)
|
|
109
95
|
</script>
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
class="omega-input"
|
|
4
|
-
@focusout="
|
|
4
|
+
@focusout="(e) => {
|
|
5
|
+
$emit('blur', e)
|
|
6
|
+
field.handleBlur()
|
|
7
|
+
}"
|
|
5
8
|
@focusin="$emit('focus', $event)"
|
|
6
9
|
>
|
|
7
10
|
<component
|
|
@@ -31,7 +34,12 @@
|
|
|
31
34
|
v-if="inputProps.type === 'email'
|
|
32
35
|
|| inputProps.type === 'string'
|
|
33
36
|
|| inputProps.type === 'password'
|
|
34
|
-
|| inputProps.type === 'date'
|
|
37
|
+
|| inputProps.type === 'date'
|
|
38
|
+
|| inputProps.type === 'tel'
|
|
39
|
+
|| inputProps.type === 'url'
|
|
40
|
+
|| inputProps.type === 'color'
|
|
41
|
+
|| inputProps.type === 'time'
|
|
42
|
+
|| inputProps.type === 'search'"
|
|
35
43
|
:id="inputProps.id"
|
|
36
44
|
:required="inputProps.required"
|
|
37
45
|
:min-length="inputProps.minLength"
|
|
@@ -43,7 +51,7 @@
|
|
|
43
51
|
:error="inputProps.error"
|
|
44
52
|
v-bind="$attrs"
|
|
45
53
|
:model-value="state.value"
|
|
46
|
-
@update:model-value="field.handleChange"
|
|
54
|
+
@update:model-value="(v: any) => field.handleChange(v)"
|
|
47
55
|
>
|
|
48
56
|
<template
|
|
49
57
|
v-if="$slots.label"
|
|
@@ -67,7 +75,7 @@
|
|
|
67
75
|
:error="inputProps.error"
|
|
68
76
|
v-bind="$attrs"
|
|
69
77
|
:model-value="state.value"
|
|
70
|
-
@update:model-value="field.handleChange"
|
|
78
|
+
@update:model-value="(v: any) => field.handleChange(v)"
|
|
71
79
|
>
|
|
72
80
|
<template
|
|
73
81
|
v-if="$slots.label"
|
|
@@ -120,7 +128,7 @@
|
|
|
120
128
|
:error="inputProps.error"
|
|
121
129
|
v-bind="$attrs"
|
|
122
130
|
:model-value="state.value"
|
|
123
|
-
@update:model-value="field.handleChange"
|
|
131
|
+
@update:model-value="(v: any) => field.handleChange(v)"
|
|
124
132
|
>
|
|
125
133
|
<template
|
|
126
134
|
v-if="$slots.label"
|
|
@@ -133,7 +141,7 @@
|
|
|
133
141
|
</template>
|
|
134
142
|
<v-radio
|
|
135
143
|
v-for="option in inputProps.options"
|
|
136
|
-
:key="option.value"
|
|
144
|
+
:key="option.value as PropertyKey"
|
|
137
145
|
:label="option.title"
|
|
138
146
|
:value="option.value"
|
|
139
147
|
/>
|
|
@@ -195,6 +203,33 @@
|
|
|
195
203
|
/>
|
|
196
204
|
</template>
|
|
197
205
|
</v-autocomplete>
|
|
206
|
+
|
|
207
|
+
<!-- Fallback for any type not handled by a dedicated branch above (e.g. an
|
|
208
|
+
unrecognized schema type resolved to "unknown", or a custom `type` with
|
|
209
|
+
no renderer). Renders an editable text input instead of nothing. -->
|
|
210
|
+
<v-text-field
|
|
211
|
+
v-if="isUnhandledType"
|
|
212
|
+
:id="inputProps.id"
|
|
213
|
+
:required="inputProps.required"
|
|
214
|
+
:type="getInputType(inputProps.type)"
|
|
215
|
+
:name="field.name"
|
|
216
|
+
:label="inputProps.label"
|
|
217
|
+
:error-messages="inputProps.errorMessages"
|
|
218
|
+
:error="inputProps.error"
|
|
219
|
+
v-bind="$attrs"
|
|
220
|
+
:model-value="state.value"
|
|
221
|
+
@update:model-value="(v: any) => field.handleChange(v)"
|
|
222
|
+
>
|
|
223
|
+
<template
|
|
224
|
+
v-if="$slots.label"
|
|
225
|
+
#label
|
|
226
|
+
>
|
|
227
|
+
<slot
|
|
228
|
+
name="label"
|
|
229
|
+
v-bind="{ required: inputProps.required, id: inputProps.id, label: inputProps.label }"
|
|
230
|
+
/>
|
|
231
|
+
</template>
|
|
232
|
+
</v-text-field>
|
|
198
233
|
</div>
|
|
199
234
|
</template>
|
|
200
235
|
|
|
@@ -204,10 +239,12 @@
|
|
|
204
239
|
generic="From extends Record<PropertyKey, any>, Name extends DeepKeys<From>"
|
|
205
240
|
>
|
|
206
241
|
import { type DeepKeys } from "@tanstack/vue-form"
|
|
207
|
-
import {
|
|
242
|
+
import { computed, watchEffect } from "vue"
|
|
208
243
|
import type { VuetifyInputProps } from "./InputProps"
|
|
244
|
+
import { getInputType } from "./inputs"
|
|
245
|
+
import { typeOverrides } from "./types"
|
|
209
246
|
|
|
210
|
-
defineProps<VuetifyInputProps<From, Name>>()
|
|
247
|
+
const props = defineProps<VuetifyInputProps<From, Name>>()
|
|
211
248
|
|
|
212
249
|
defineEmits<{
|
|
213
250
|
(e: "focus", event: Event): void
|
|
@@ -217,6 +254,19 @@ defineEmits<{
|
|
|
217
254
|
defineOptions({
|
|
218
255
|
inheritAttrs: false
|
|
219
256
|
})
|
|
257
|
+
|
|
258
|
+
// True when no dedicated branch handles `inputProps.type` (it's outside the
|
|
259
|
+
// built-in `typeOverrides`): the fallback text input renders and we warn.
|
|
260
|
+
const isUnhandledType = computed(() => !(typeOverrides as readonly string[]).includes(props.inputProps.type))
|
|
261
|
+
|
|
262
|
+
watchEffect(() => {
|
|
263
|
+
if (isUnhandledType.value) {
|
|
264
|
+
// eslint-disable-next-line no-console
|
|
265
|
+
console.warn(
|
|
266
|
+
`[OmegaForm] Unhandled input type "${props.inputProps.type}", falling back to a text input.`
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
})
|
|
220
270
|
</script>
|
|
221
271
|
|
|
222
272
|
<style>
|