@effect-app/vue-components 0.3.3 → 0.4.2
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/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +13 -0
- package/dist/types/components/OmegaForm/OmegaInput.vue.d.ts +2 -14
- package/dist/types/components/OmegaForm/OmegaWrapper.vue.d.ts +7 -3
- package/dist/types/components/OmegaForm/index.d.ts +2 -42
- package/dist/types/components/OmegaForm/useOmegaForm.d.ts +6 -2
- package/dist/vue-components.es11.js +43 -33
- package/dist/vue-components.es15.js +1 -1
- package/dist/vue-components.es16.js +2 -93
- package/dist/vue-components.es17.js +96 -11
- package/dist/vue-components.es18.js +11 -2
- package/dist/vue-components.es19.js +1 -1
- package/dist/vue-components.es5.js +88 -61
- package/dist/vue-components.es7.js +2 -2
- package/dist/vue-components.es8.js +1 -1
- package/package.json +3 -1
- package/src/components/OmegaForm/OmegaFormStuff.ts +11 -0
- package/src/components/OmegaForm/OmegaInput.vue +2 -15
- package/src/components/OmegaForm/OmegaInternalInput.vue +1 -1
- package/src/components/OmegaForm/OmegaWrapper.vue +61 -23
- package/src/components/OmegaForm/index.ts +3 -3
- package/src/components/OmegaForm/useOmegaForm.ts +50 -4
- package/src/stories/OmegaForm/ComplexForm.vue +39 -33
- package/src/stories/OmegaForm/EmailForm.vue +1 -1
- package/src/stories/OmegaForm/Meta.vue +1 -1
- package/src/stories/OmegaForm/OneHundredWaysToWriteAForm.vue +188 -0
- package/src/stories/OmegaForm/PersistencyForm.vue +6 -6
- package/src/stories/OmegaForm/SimpleForm.vue +1 -1
- package/src/stories/OmegaForm/SimpleFormVuetifyDefault.vue +1 -1
- package/src/stories/OmegaForm/SumExample.vue +3 -5
- package/src/stories/OmegaForm/form.Input.vue +86 -0
- package/src/stories/OmegaForm.stories.ts +16 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="container">
|
|
3
|
+
<h1>One hundred ways to write a form</h1>
|
|
4
|
+
<p>
|
|
5
|
+
OmegaForm is a powerful and flexible form library that wraps
|
|
6
|
+
<a href="https://tanstack.com/form/latest" target="_blank">TanStack Form</a>
|
|
7
|
+
and uses
|
|
8
|
+
<a href="https://effect.website/docs/schema/introduction/" target="_blank">
|
|
9
|
+
Effect Schema
|
|
10
|
+
</a>
|
|
11
|
+
as the schema definition. All of this allows you to create forms in a
|
|
12
|
+
declarative way.
|
|
13
|
+
We also use
|
|
14
|
+
<a href="https://vuetifyjs.com/" target="_blank">Vuetify</a> as peer dependency
|
|
15
|
+
for the UI components, but you can use any other UI library you want or custom inputs.
|
|
16
|
+
Here are some examples of how to write forms using
|
|
17
|
+
different approaches
|
|
18
|
+
</p>
|
|
19
|
+
<p>for our example, we will use the following dependencies</p>
|
|
20
|
+
<pre v-highlightjs><code class="javascript">{{ `import { S } from "effect-app"
|
|
21
|
+
import { OmegaForm, OmegaInput, useOmegaForm } from "@effect-app/vue-components"` }}</code></pre>
|
|
22
|
+
|
|
23
|
+
<h2>Simplest way</h2>
|
|
24
|
+
<p>Now, let's write a form using the following schema:</p>
|
|
25
|
+
<pre v-highlightjs><code class="typescript">{{ `const schema = S.Struct({
|
|
26
|
+
name: S.String,
|
|
27
|
+
age: S.Number,
|
|
28
|
+
})` }}</code></pre>
|
|
29
|
+
|
|
30
|
+
<p>Now, let's write in the template the form using the following schema:</p>
|
|
31
|
+
<pre
|
|
32
|
+
v-highlightjs
|
|
33
|
+
><code class="vue">{{ `<OmegaForm :schema="schema" :on-submit="console.log">
|
|
34
|
+
<template #internalForm="{ form }">
|
|
35
|
+
<OmegaInput label="name" :form="form" name="name" />
|
|
36
|
+
<OmegaInput label="age" :form="form" name="age" />
|
|
37
|
+
</template>
|
|
38
|
+
</OmegaForm>` }}</code></pre>
|
|
39
|
+
|
|
40
|
+
<OmegaForm :schema="schema" :on-submit="console.log">
|
|
41
|
+
<template #internalForm="{ form }">
|
|
42
|
+
<OmegaInput label="name" :form="form" name="name" />
|
|
43
|
+
<OmegaInput label="age" :form="form" name="age" />
|
|
44
|
+
</template>
|
|
45
|
+
</OmegaForm>
|
|
46
|
+
|
|
47
|
+
<p>
|
|
48
|
+
<code>OmegaInput</code>
|
|
49
|
+
is a component that will render a form input based on the schema. It's
|
|
50
|
+
alread embedded inside the
|
|
51
|
+
<code>OmegaForm</code>
|
|
52
|
+
component, so you don't need to import it separately or pass form as a prop:
|
|
53
|
+
</p>
|
|
54
|
+
<pre v-highlightjs><code class="vue">{{ `<OmegaForm :schema="schema">
|
|
55
|
+
<template #internalForm="{ form }">
|
|
56
|
+
<component :is="form.Input" label="name" name="name" />
|
|
57
|
+
<component :is="form.Input" label="age" name="age" />
|
|
58
|
+
</template>
|
|
59
|
+
</OmegaForm>` }}</code></pre>
|
|
60
|
+
<p>you can also register to the values via the `subscribe` prop</p>
|
|
61
|
+
<pre
|
|
62
|
+
v-highlightjs
|
|
63
|
+
><code class="vue">{{ `<OmegaForm :schema="schema" :subscribe="['values']">
|
|
64
|
+
<template #internalForm="{ form, subscribedValues: { values } }">
|
|
65
|
+
<component :is="form.Input" label="name" name="name" />
|
|
66
|
+
<component :is="form.Input" label="age" name="age" />
|
|
67
|
+
<pre>\{\{ values \}\}</pre>
|
|
68
|
+
</template>
|
|
69
|
+
</OmegaForm>` }}</code></pre>
|
|
70
|
+
|
|
71
|
+
<OmegaForm :schema="schema" :subscribe="['values']">
|
|
72
|
+
<template #internalForm="{ form, subscribedValues: { values } }">
|
|
73
|
+
<component :is="form.Input" label="name" name="name" />
|
|
74
|
+
<component :is="form.Input" label="age" name="age" />
|
|
75
|
+
<pre>{{ values }}</pre>
|
|
76
|
+
</template>
|
|
77
|
+
</OmegaForm>
|
|
78
|
+
|
|
79
|
+
<h2>Using the useOmegaForm hook</h2>
|
|
80
|
+
<p>
|
|
81
|
+
The useOmegaForm hook is a hook that returns the form instance and the
|
|
82
|
+
values. It's a good way to create a form in a functional way.
|
|
83
|
+
</p>
|
|
84
|
+
<pre
|
|
85
|
+
v-highlightjs
|
|
86
|
+
><code class="typescript">{{ `const form = useOmegaForm(schema)` }}</code></pre>
|
|
87
|
+
<p>
|
|
88
|
+
Now, you can use the form instance to create the form in the template.
|
|
89
|
+
</p>
|
|
90
|
+
<pre v-highlightjs><code class="vue">{{ `<OmegaForm :form="form">
|
|
91
|
+
<form.Input" label="name" name="name" />
|
|
92
|
+
<form.Input" label="age" name="age" />
|
|
93
|
+
</OmegaForm>` }}</code></pre>
|
|
94
|
+
|
|
95
|
+
<p>you can still register to the values via the `subscribe` prop</p>
|
|
96
|
+
<pre
|
|
97
|
+
v-highlightjs
|
|
98
|
+
><code class="vue">{{ `<OmegaForm :form="form" :subscribe="['values']">
|
|
99
|
+
<template #externalForm="{ subscribedValues: { values } }">
|
|
100
|
+
<form.Input" label="name" name="name" />
|
|
101
|
+
<form.Input" label="age" name="age" />
|
|
102
|
+
<pre>\{\{ values \}\}</pre>
|
|
103
|
+
</template>
|
|
104
|
+
</OmegaForm>` }}</code></pre>
|
|
105
|
+
<p>
|
|
106
|
+
<strong>Note:</strong> the template name is <code>externalForm</code>
|
|
107
|
+
because the form is not inside the component, it's outside. And you don't
|
|
108
|
+
have access to the form instance inside the template variables anymore.
|
|
109
|
+
</p>
|
|
110
|
+
|
|
111
|
+
<h3>Using custom inputs</h3>
|
|
112
|
+
<p>
|
|
113
|
+
You can use custom inputs by passing an OmegaInput a child component.
|
|
114
|
+
</p>
|
|
115
|
+
<pre v-highlightjs><code class="vue">{{ `<OmegaForm :form="form">
|
|
116
|
+
<form.Input label="name" name="name">
|
|
117
|
+
<template #default="{ field, label }">
|
|
118
|
+
<label :for="name">\{\{ label \}\}</label>
|
|
119
|
+
<input
|
|
120
|
+
:id="name"
|
|
121
|
+
v-model="field.state.value"
|
|
122
|
+
:name="name"
|
|
123
|
+
style="border: 1px solid red"
|
|
124
|
+
@change="(e) => field.handleChange(e.target.value)"
|
|
125
|
+
/>
|
|
126
|
+
</template>
|
|
127
|
+
</form.Input>
|
|
128
|
+
</OmegaForm>` }}</code></pre>
|
|
129
|
+
<OmegaForm :form="form">
|
|
130
|
+
<form.Input label="name" name="name">
|
|
131
|
+
<template #default="{ field, label, name }">
|
|
132
|
+
<label :for="name">{{ label }}</label>
|
|
133
|
+
<input
|
|
134
|
+
:id="name"
|
|
135
|
+
v-model="field.state.value"
|
|
136
|
+
:name="name"
|
|
137
|
+
style="border: 1px solid red"
|
|
138
|
+
@change="(e: any) => field.handleChange(e.target.value)"
|
|
139
|
+
/>
|
|
140
|
+
</template>
|
|
141
|
+
</form.Input>
|
|
142
|
+
</OmegaForm>
|
|
143
|
+
<h3>Known issues</h3>
|
|
144
|
+
<p>
|
|
145
|
+
You can't write something like this:
|
|
146
|
+
</p>
|
|
147
|
+
<pre v-highlightjs><code class="vue">{{ `<OmegaForm :schema="schema">
|
|
148
|
+
<template #internalForm="{ form }">
|
|
149
|
+
<form.Input label="name" name="name" />
|
|
150
|
+
<form.Input label="age" name="age" />
|
|
151
|
+
</template>
|
|
152
|
+
</OmegaForm>` }}</code></pre>
|
|
153
|
+
<p>
|
|
154
|
+
because When Vue's template compiler encounters <code>form.Input</code>, it try to resolve or analyze the form object and its Input property earlier in the rendering pipeline. Since form.Input is provided by the parent OmegaForm through the slot, this direct usage inside the slot might create a tighter, more immediate dependency loop that the compiler/renderer detects or falls into, leading to the stack overflow.
|
|
155
|
+
</p>
|
|
156
|
+
</div>
|
|
157
|
+
</template>
|
|
158
|
+
|
|
159
|
+
<script setup lang="ts">
|
|
160
|
+
import { S } from "effect-app"
|
|
161
|
+
import { OmegaForm, OmegaInput, useOmegaForm } from "../../components/OmegaForm"
|
|
162
|
+
|
|
163
|
+
const schema = S.Struct({
|
|
164
|
+
name: S.String,
|
|
165
|
+
age: S.Number,
|
|
166
|
+
})
|
|
167
|
+
const form = useOmegaForm(schema)
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
<style scoped>
|
|
171
|
+
p,
|
|
172
|
+
pre {
|
|
173
|
+
margin-bottom: 1rem;
|
|
174
|
+
}
|
|
175
|
+
form {
|
|
176
|
+
margin-bottom: 2rem;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.container {
|
|
180
|
+
max-width: 1200px;
|
|
181
|
+
margin: 0 auto;
|
|
182
|
+
padding: 0 1rem;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
h1, h2, h3 {
|
|
186
|
+
text-wrap: balance;
|
|
187
|
+
}
|
|
188
|
+
</style>
|
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
:subscribe="['errors', 'values']"
|
|
5
5
|
show-errors-on="onChange"
|
|
6
6
|
>
|
|
7
|
-
<template #
|
|
7
|
+
<template #externalForm="{ subscribedValues: { errors, values: vvv } }">
|
|
8
8
|
<div>Errors: {{ errors }}</div>
|
|
9
|
-
<div>Values: {{
|
|
10
|
-
<OmegaInput label="first" :form="
|
|
9
|
+
<div>Values: {{ vvv }}</div>
|
|
10
|
+
<OmegaInput label="first" :form="addForm" name="first" />
|
|
11
11
|
<div>+</div>
|
|
12
|
-
<OmegaInput label="second" :form="
|
|
12
|
+
<OmegaInput label="second" :form="addForm" name="second" />
|
|
13
13
|
<br />
|
|
14
14
|
<hr />
|
|
15
15
|
<br />
|
|
16
|
-
<OmegaInput label="third.fourth" :form="
|
|
17
|
-
<OmegaInput label="third.fifth" :form="
|
|
16
|
+
<OmegaInput label="third.fourth" :form="addForm" name="third.fourth" />
|
|
17
|
+
<OmegaInput label="third.fifth" :form="addForm" name="third.fifth" />
|
|
18
18
|
</template>
|
|
19
19
|
</OmegaForm>
|
|
20
20
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<OmegaForm :schema="schema" :on-submit="onSubmit" :subscribe="['values']">
|
|
3
|
-
<template #
|
|
3
|
+
<template #internalForm="{ form, subscribedValues: { values } }">
|
|
4
4
|
<div>values: {{ values }}</div>
|
|
5
5
|
<OmegaInput label="asder2" name="asder2" :form="form">
|
|
6
6
|
<template #default="inputProps">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<OmegaForm :schema="schema" :subscribe="['values']">
|
|
3
|
-
<template #
|
|
3
|
+
<template #internalForm="{ form, subscribedValues: { values } }">
|
|
4
4
|
<OmegaInput label="aString" :form="form" name="aString" />
|
|
5
5
|
<pre>{{ values }}</pre>
|
|
6
6
|
</template>
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<OmegaForm :form="addForm">
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<OmegaInput label="second" :form="form" name="second" />
|
|
7
|
-
</template>
|
|
3
|
+
<OmegaInput label="first" :form="addForm" name="first" />
|
|
4
|
+
<div>+</div>
|
|
5
|
+
<OmegaInput label="second" :form="addForm" name="second" />
|
|
8
6
|
</OmegaForm>
|
|
9
7
|
|
|
10
8
|
<!-- Technically you can do this only with a subscribe but only inside OmegaForm Context -->
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<OmegaForm
|
|
3
|
+
:schema="S.Struct({ myString: S.String })"
|
|
4
|
+
:on-submit="console.log"
|
|
5
|
+
>
|
|
6
|
+
<template #internalForm="{ form }">
|
|
7
|
+
<component :is="form.Input" label="myString" name="myString" />
|
|
8
|
+
</template>
|
|
9
|
+
</OmegaForm>
|
|
10
|
+
<OmegaForm :form="exampleForm">
|
|
11
|
+
<exampleForm.Input label="aString" name="aString" />
|
|
12
|
+
<exampleForm.Input label="aStringMin2" name="aStringMin2" />
|
|
13
|
+
<exampleForm.Input label="aStringMin2Max4" name="aStringMin2Max4" />
|
|
14
|
+
<exampleForm.Input
|
|
15
|
+
label="aStringMin2Max3Nullable"
|
|
16
|
+
name="aStringMin2Max3Nullable"
|
|
17
|
+
/>
|
|
18
|
+
<exampleForm.Input label="aNumber" name="aNumber" />
|
|
19
|
+
<exampleForm.Input label="aNumberMin2" name="aNumberMin2" />
|
|
20
|
+
<exampleForm.Input label="aNumberMin2Max" name="aNumberMin2Max" />
|
|
21
|
+
<exampleForm.Input
|
|
22
|
+
label="aNumberMin2Max4Nullable"
|
|
23
|
+
name="aNumberMin2Max4Nullable"
|
|
24
|
+
/>
|
|
25
|
+
<exampleForm.Input
|
|
26
|
+
label="aSelect"
|
|
27
|
+
name="aSelect"
|
|
28
|
+
:options="[
|
|
29
|
+
{ title: 'a', value: 'a' },
|
|
30
|
+
{ title: 'b', value: 'b' },
|
|
31
|
+
{ title: 'c', value: 'c' },
|
|
32
|
+
]"
|
|
33
|
+
/>
|
|
34
|
+
<button>Submit</button>
|
|
35
|
+
<button type="reset" @click.prevent="exampleForm.clear()">Clear</button>
|
|
36
|
+
<button type="button" @click="exampleForm.reset()">Reset</button>
|
|
37
|
+
</OmegaForm>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import { S } from "effect-app"
|
|
42
|
+
import { OmegaForm, useOmegaForm } from "../../components/OmegaForm"
|
|
43
|
+
|
|
44
|
+
const schema = S.Struct({
|
|
45
|
+
aString: S.String,
|
|
46
|
+
aStringMin2: S.String.pipe(S.minLength(2)),
|
|
47
|
+
aStringMin2Max4: S.String.pipe(S.minLength(2)).pipe(S.maxLength(4)),
|
|
48
|
+
aStringMin2Max3Nullable: S.UndefinedOr(
|
|
49
|
+
S.String.pipe(S.minLength(2)).pipe(S.maxLength(3)),
|
|
50
|
+
),
|
|
51
|
+
aNumber: S.Number,
|
|
52
|
+
aNumberMin2: S.Number.pipe(S.greaterThan(2)),
|
|
53
|
+
aNumberMin2Max: S.Number.pipe(S.greaterThan(2)).pipe(S.lessThan(4)),
|
|
54
|
+
aNumberMin2Max4Nullable: S.NullOr(S.Number.pipe(S.between(2, 4))),
|
|
55
|
+
aSelect: S.Union(S.Literal("a"), S.Literal("b"), S.Literal("c")),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const exampleForm = useOmegaForm(
|
|
59
|
+
schema,
|
|
60
|
+
{
|
|
61
|
+
onSubmit: ({
|
|
62
|
+
value,
|
|
63
|
+
}: {
|
|
64
|
+
value: {
|
|
65
|
+
aString: string
|
|
66
|
+
aStringMin2: string
|
|
67
|
+
aStringMin2Max4: string
|
|
68
|
+
aStringMin2Max3Nullable?: string
|
|
69
|
+
aNumber: number
|
|
70
|
+
aNumberMin2: number
|
|
71
|
+
aNumberMin2Max: number
|
|
72
|
+
aNumberMin2Max4Nullable: number | null
|
|
73
|
+
aSelect: "a" | "b" | "c"
|
|
74
|
+
}
|
|
75
|
+
}) => {
|
|
76
|
+
console.log(value)
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
persistency: {
|
|
81
|
+
policies: ["local"],
|
|
82
|
+
overrideDefaultValues: true,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
</script>
|
|
@@ -10,6 +10,8 @@ import SimpleFormVuetifyDefault from "./OmegaForm/SimpleFormVuetifyDefault.vue"
|
|
|
10
10
|
import SumExample from "./OmegaForm/SumExample.vue"
|
|
11
11
|
import PersistencyForm from "./OmegaForm/PersistencyForm.vue"
|
|
12
12
|
import MetaForm from "./OmegaForm/Meta.vue"
|
|
13
|
+
import FormInput from "./OmegaForm/form.Input.vue"
|
|
14
|
+
import OneHundredWaysToWriteAForm from "./OmegaForm/OneHundredWaysToWriteAForm.vue"
|
|
13
15
|
|
|
14
16
|
const mockIntl = {
|
|
15
17
|
locale: ref("en"),
|
|
@@ -20,7 +22,6 @@ const mockIntl = {
|
|
|
20
22
|
const meta: Meta<typeof OmegaForm> = {
|
|
21
23
|
title: "Components/OmegaForm",
|
|
22
24
|
component: OmegaForm,
|
|
23
|
-
tags: ["autodocs"],
|
|
24
25
|
argTypes: {
|
|
25
26
|
schema: { control: "object" },
|
|
26
27
|
onSubmit: { action: "submitted" },
|
|
@@ -41,6 +42,13 @@ const meta: Meta<typeof OmegaForm> = {
|
|
|
41
42
|
export default meta
|
|
42
43
|
type Story = StoryObj<typeof meta>
|
|
43
44
|
|
|
45
|
+
export const AnHundredWayToWriteAFormStory: Story = {
|
|
46
|
+
render: () => ({
|
|
47
|
+
components: { OneHundredWaysToWriteAForm },
|
|
48
|
+
template: "<OneHundredWaysToWriteAForm />",
|
|
49
|
+
}),
|
|
50
|
+
}
|
|
51
|
+
|
|
44
52
|
export const SimpleFormStory: Story = {
|
|
45
53
|
render: () => ({
|
|
46
54
|
components: { SimpleForm },
|
|
@@ -89,3 +97,10 @@ export const MetaStory: Story = {
|
|
|
89
97
|
template: "<MetaForm />",
|
|
90
98
|
}),
|
|
91
99
|
}
|
|
100
|
+
|
|
101
|
+
export const FormInputStory: Story = {
|
|
102
|
+
render: () => ({
|
|
103
|
+
components: { FormInput },
|
|
104
|
+
template: "<FormInput />",
|
|
105
|
+
}),
|
|
106
|
+
}
|