@groupteknology/vuno 0.6.0 → 0.6.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.
Files changed (56) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +45 -0
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +6 -0
  5. package/dist/runtime/app/assets/css/main.css +1 -1
  6. package/dist/runtime/app/components/App/App.d.vue.ts +33 -0
  7. package/dist/runtime/app/components/App/App.vue +127 -0
  8. package/dist/runtime/app/components/App/App.vue.d.ts +33 -0
  9. package/dist/runtime/app/components/App/AppContext.d.vue.ts +3 -0
  10. package/dist/runtime/app/components/App/AppContext.vue +7 -0
  11. package/dist/runtime/app/components/App/AppContext.vue.d.ts +3 -0
  12. package/dist/runtime/app/components/App/AppModal.d.vue.ts +38 -0
  13. package/dist/runtime/app/components/App/AppModal.vue +103 -0
  14. package/dist/runtime/app/components/App/AppModal.vue.d.ts +38 -0
  15. package/dist/runtime/app/components/App/AppOverlay.d.vue.ts +7 -0
  16. package/dist/runtime/app/components/App/AppOverlay.vue +33 -0
  17. package/dist/runtime/app/components/App/AppOverlay.vue.d.ts +7 -0
  18. package/dist/runtime/app/components/App/AppToast.d.vue.ts +11 -0
  19. package/dist/runtime/app/components/App/AppToast.vue +53 -0
  20. package/dist/runtime/app/components/App/AppToast.vue.d.ts +11 -0
  21. package/dist/runtime/app/components/App/AppToaster.d.vue.ts +11 -0
  22. package/dist/runtime/app/components/App/AppToaster.vue +49 -0
  23. package/dist/runtime/app/components/App/AppToaster.vue.d.ts +11 -0
  24. package/dist/runtime/app/components/Confirm/Confirm.vue +20 -13
  25. package/dist/runtime/app/components/Field/FieldArray.vue +68 -68
  26. package/dist/runtime/app/components/Field/FieldColor.vue +36 -36
  27. package/dist/runtime/app/components/Form/Form.vue +61 -49
  28. package/dist/runtime/app/components/Form/FormModal.vue +23 -15
  29. package/dist/runtime/app/components/Form/FormSection.vue +27 -27
  30. package/dist/runtime/app/components/Page/Page.vue +46 -46
  31. package/dist/runtime/app/components/State/EmptyState.vue +20 -20
  32. package/dist/runtime/app/components/State/FailureState.vue +21 -21
  33. package/dist/runtime/app/components/Table/Table.vue +18 -114
  34. package/dist/runtime/app/composables/useVunoConfirm.d.ts +15 -0
  35. package/dist/runtime/app/composables/useVunoConfirm.js +5 -0
  36. package/dist/runtime/app/composables/useVunoContext.d.ts +13 -0
  37. package/dist/runtime/app/composables/useVunoContext.js +9 -0
  38. package/dist/runtime/app/composables/useVunoModal.d.ts +21 -0
  39. package/dist/runtime/app/composables/useVunoModal.js +26 -0
  40. package/dist/runtime/app/composables/useVunoOverlay.d.ts +22 -0
  41. package/dist/runtime/app/composables/useVunoOverlay.js +10 -0
  42. package/dist/runtime/app/composables/useVunoTableQuery.js +3 -3
  43. package/dist/runtime/app/composables/useVunoTheme.js +2 -1
  44. package/dist/runtime/app/composables/useVunoToast.d.ts +28 -0
  45. package/dist/runtime/app/composables/useVunoToast.js +13 -0
  46. package/dist/runtime/app/internal/useVunoTable.d.ts +65 -0
  47. package/dist/runtime/app/internal/useVunoTable.js +161 -0
  48. package/dist/runtime/app/theme/index.js +64 -11
  49. package/dist/runtime/app/types/app.d.ts +83 -0
  50. package/dist/runtime/app/types/app.js +0 -0
  51. package/dist/runtime/app/types/confirm.d.ts +8 -0
  52. package/dist/runtime/app/types/index.d.ts +1 -0
  53. package/dist/runtime/app/types/theme.d.ts +6 -0
  54. package/dist/runtime/app/utils/pagination.d.ts +12 -0
  55. package/dist/runtime/app/utils/pagination.js +6 -0
  56. package/package.json +6 -3
@@ -0,0 +1,11 @@
1
+ import type { VunoNotification } from '#vuno/types/app';
2
+ type __VLS_Props = {
3
+ notifications: VunoNotification[];
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ dismiss: (id: number) => any;
7
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
8
+ onDismiss?: ((id: number) => any) | undefined;
9
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
@@ -0,0 +1,49 @@
1
+ <script setup>
2
+ import { useVunoTheme } from "#vuno/composables/useVunoTheme";
3
+ import AppToast from "./AppToast.vue";
4
+ const props = defineProps({
5
+ notifications: { type: Array, required: true }
6
+ });
7
+ const emit = defineEmits(["dismiss"]);
8
+ const ui = useVunoTheme("appToaster");
9
+ </script>
10
+
11
+ <template>
12
+ <!--
13
+ La pila se porta a `body` para que no la recorte ningún `overflow` ni la
14
+ reordene ningún contexto de apilamiento de la pantalla que la abrió.
15
+
16
+ `pointer-events-none` en el contenedor y `auto` en cada aviso: si no, una
17
+ franja invisible a lo alto de la pantalla se come los clics de lo que hay
18
+ detrás aunque no haya ninguna notificación.
19
+ -->
20
+ <Teleport to="body">
21
+ <div
22
+ v-if="props.notifications.length > 0"
23
+ :class="ui.root"
24
+ >
25
+ <!--
26
+ `TransitionGroup` y no `Transition`: hay varias a la vez y al
27
+ quitar una de en medio las de abajo tienen que subir. `move`
28
+ anima justo eso, que si no da un salto.
29
+ -->
30
+ <TransitionGroup
31
+ :class="ui.list"
32
+ :enterActiveClass="ui.enterActive"
33
+ :enterFromClass="ui.enterFrom"
34
+ :leaveActiveClass="ui.leaveActive"
35
+ :leaveToClass="ui.leaveTo"
36
+ :moveClass="ui.move"
37
+ tag="div"
38
+ >
39
+ <AppToast
40
+ v-for="notification in props.notifications"
41
+ :key="notification.id"
42
+ :class="ui.item"
43
+ :notification="notification"
44
+ @dismiss="emit('dismiss', notification.id)"
45
+ />
46
+ </TransitionGroup>
47
+ </div>
48
+ </Teleport>
49
+ </template>
@@ -0,0 +1,11 @@
1
+ import type { VunoNotification } from '#vuno/types/app';
2
+ type __VLS_Props = {
3
+ notifications: VunoNotification[];
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ dismiss: (id: number) => any;
7
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
8
+ onDismiss?: ((id: number) => any) | undefined;
9
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
@@ -1,7 +1,9 @@
1
1
  <script setup>
2
2
  import { computed } from "vue";
3
+ import AppModal from "#vuno/components/App/AppModal.vue";
3
4
  import { useVunoIcons, useVunoMessages, useVunoTheme } from "#vuno/composables/useVunoTheme";
4
5
  const props = defineProps({
6
+ alert: { type: Boolean, required: false },
5
7
  cancelLabel: { type: String, required: false },
6
8
  color: { type: String, required: false },
7
9
  confirmLabel: { type: String, required: false },
@@ -30,27 +32,32 @@ function onConfirm() {
30
32
  </script>
31
33
 
32
34
  <template>
33
- <UModal
35
+ <!--
36
+ Sobre `AppModal`, no sobre el de Nuxt UI, y no por gusto: un `<dialog>`
37
+ nativo vive en la capa superior del navegador, que queda por encima de
38
+ cualquier z-index. Mezclando los dos sistemas, un confirm abierto desde
39
+ dentro de un `AppModal` se pintaba detrás de él —estaba en el DOM y no se
40
+ veía—. Nativos los dos, el navegador los apila por orden de apertura.
41
+ -->
42
+ <AppModal
34
43
  v-model:open="open"
35
44
  :dismissible="!props.loading"
36
- scrollable
37
45
  :title="props.title ?? messages.confirmTitle"
38
46
  :ui="modalUi"
39
47
  >
40
- <template #body>
41
- <div :class="ui.body">
42
- <UIcon
43
- :class="[ui.icon, `text-${color}`]"
44
- :name="props.icon ?? icons.confirm"
45
- />
46
- <div :class="ui.description">
47
- <slot>{{ props.description ?? messages.confirmDescription }}</slot>
48
- </div>
48
+ <div :class="ui.body">
49
+ <UIcon
50
+ :class="[ui.icon, `text-${color}`]"
51
+ :name="props.icon ?? icons.confirm"
52
+ />
53
+ <div :class="ui.description">
54
+ <slot>{{ props.description ?? messages.confirmDescription }}</slot>
49
55
  </div>
50
- </template>
56
+ </div>
51
57
 
52
58
  <template #footer>
53
59
  <UButton
60
+ v-if="!props.alert"
54
61
  color="neutral"
55
62
  :disabled="props.loading"
56
63
  :label="props.cancelLabel ?? messages.cancel"
@@ -64,5 +71,5 @@ function onConfirm() {
64
71
  @click="onConfirm"
65
72
  />
66
73
  </template>
67
- </UModal>
74
+ </AppModal>
68
75
  </template>
@@ -57,72 +57,72 @@ function move(index, offset) {
57
57
  </script>
58
58
 
59
59
  <template>
60
- <div :class="ui.root">
61
- <div
62
- v-for="(item, index) in items"
63
- :key="index"
64
- :class="ui.item"
65
- >
66
- <div :class="ui.itemHeader">
67
- <span :class="ui.itemLabel">{{ index + 1 }}</span>
68
-
69
- <div :class="ui.itemActions">
70
- <UButton
71
- v-if="props.sortable && index > 0"
72
- :aria-label="messages.fileMoveUp"
73
- color="neutral"
74
- :icon="icons.fileMoveUp"
75
- size="xs"
76
- variant="ghost"
77
- @click="move(index, -1)"
78
- />
79
- <UButton
80
- v-if="props.sortable && index < items.length - 1"
81
- :aria-label="messages.fileMoveDown"
82
- color="neutral"
83
- :icon="icons.fileMoveDown"
84
- size="xs"
85
- variant="ghost"
86
- @click="move(index, 1)"
87
- />
88
- <UButton
89
- :aria-label="messages.removeItem"
90
- color="error"
91
- :disabled="!canRemove"
92
- :icon="icons.removeItem"
93
- size="xs"
94
- variant="ghost"
95
- @click="remove(index)"
96
- />
97
- </div>
98
- </div>
99
-
100
- <div :class="ui.itemFields">
101
- <div
102
- v-for="field in props.fields"
103
- :key="`${index}-${field.name}`"
104
- :class="getColumnSpanClass(field.columns)"
105
- >
106
- <FormField :field="fieldAt(field, index)" />
107
- </div>
108
- </div>
109
- </div>
110
-
111
- <div
112
- v-if="items.length === 0"
113
- :class="ui.empty"
114
- >
115
- {{ messages.emptyArray }}
116
- </div>
117
-
118
- <UButton
119
- class="self-start"
120
- color="neutral"
121
- :disabled="!canAdd"
122
- :icon="icons.addItem"
123
- :label="props.addLabel ?? messages.addItem"
124
- variant="outline"
125
- @click="add"
126
- />
127
- </div>
60
+ <div :class="ui.root">
61
+ <div
62
+ v-for="(item, index) in items"
63
+ :key="index"
64
+ :class="ui.item"
65
+ >
66
+ <div :class="ui.itemHeader">
67
+ <span :class="ui.itemLabel">{{ index + 1 }}</span>
68
+
69
+ <div :class="ui.itemActions">
70
+ <UButton
71
+ v-if="props.sortable && index > 0"
72
+ :aria-label="messages.fileMoveUp"
73
+ color="neutral"
74
+ :icon="icons.fileMoveUp"
75
+ size="xs"
76
+ variant="ghost"
77
+ @click="move(index, -1)"
78
+ />
79
+ <UButton
80
+ v-if="props.sortable && index < items.length - 1"
81
+ :aria-label="messages.fileMoveDown"
82
+ color="neutral"
83
+ :icon="icons.fileMoveDown"
84
+ size="xs"
85
+ variant="ghost"
86
+ @click="move(index, 1)"
87
+ />
88
+ <UButton
89
+ :aria-label="messages.removeItem"
90
+ color="error"
91
+ :disabled="!canRemove"
92
+ :icon="icons.removeItem"
93
+ size="xs"
94
+ variant="ghost"
95
+ @click="remove(index)"
96
+ />
97
+ </div>
98
+ </div>
99
+
100
+ <div :class="ui.itemFields">
101
+ <div
102
+ v-for="field in props.fields"
103
+ :key="`${index}-${field.name}`"
104
+ :class="getColumnSpanClass(field.columns)"
105
+ >
106
+ <FormField :field="fieldAt(field, index)" />
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ <div
112
+ v-if="items.length === 0"
113
+ :class="ui.empty"
114
+ >
115
+ {{ messages.emptyArray }}
116
+ </div>
117
+
118
+ <UButton
119
+ class="self-start"
120
+ color="neutral"
121
+ :disabled="!canAdd"
122
+ :icon="icons.addItem"
123
+ :label="props.addLabel ?? messages.addItem"
124
+ variant="outline"
125
+ @click="add"
126
+ />
127
+ </div>
128
128
  </template>
@@ -18,40 +18,40 @@ const ui = useVunoTheme("fieldColor", () => props.ui);
18
18
  </script>
19
19
 
20
20
  <template>
21
- <div :class="ui.root">
22
- <!-- El selector nativo y el texto comparten valor: se puede pegar un
23
- código de la guía de marca sin pelearse con la rueda de color. -->
24
- <UPopover>
25
- <UButton
26
- :aria-label="props.label"
27
- color="neutral"
28
- :disabled="props.disabled"
29
- :size="props.size"
30
- :ui="{ leadingIcon: 'rounded-full' }"
31
- variant="outline"
32
- >
33
- <span
34
- :class="ui.swatch"
35
- :style="{ backgroundColor: value || 'transparent' }"
36
- />
37
- </UButton>
38
-
39
- <template #content>
40
- <UColorPicker
41
- v-model="value"
42
- class="p-2"
43
- />
44
- </template>
45
- </UPopover>
46
-
47
- <UInput
48
- v-model="value"
49
- class="font-mono"
50
- :disabled="props.disabled"
51
- :name="props.name"
52
- :placeholder="props.placeholder ?? '#000000'"
53
- :size="props.size"
54
- :ui="{ root: 'w-full' }"
55
- />
56
- </div>
21
+ <div :class="ui.root">
22
+ <!-- El selector nativo y el texto comparten valor: se puede pegar un
23
+ código de la guía de marca sin pelearse con la rueda de color. -->
24
+ <UPopover>
25
+ <UButton
26
+ :aria-label="props.label"
27
+ color="neutral"
28
+ :disabled="props.disabled"
29
+ :size="props.size"
30
+ :ui="{ leadingIcon: 'rounded-full' }"
31
+ variant="outline"
32
+ >
33
+ <span
34
+ :class="ui.swatch"
35
+ :style="{ backgroundColor: value || 'transparent' }"
36
+ />
37
+ </UButton>
38
+
39
+ <template #content>
40
+ <UColorPicker
41
+ v-model="value"
42
+ class="p-2"
43
+ />
44
+ </template>
45
+ </UPopover>
46
+
47
+ <UInput
48
+ v-model="value"
49
+ class="font-mono"
50
+ :disabled="props.disabled"
51
+ :name="props.name"
52
+ :placeholder="props.placeholder ?? '#000000'"
53
+ :size="props.size"
54
+ :ui="{ root: 'w-full' }"
55
+ />
56
+ </div>
57
57
  </template>
@@ -86,53 +86,65 @@ defineExpose({ dirty, errors: errorNames, isDirty, reset, resetField, submit: ()
86
86
  </script>
87
87
 
88
88
  <template>
89
- <UForm
90
- :id="formId"
91
- ref="form"
92
- :schema="props.schema"
93
- :state="state"
94
- :ui="{ base: ui.base }"
95
- @submit="onSubmit"
96
- >
97
- <UTabs
98
- v-if="isTabs"
99
- :defaultValue="tabItems[0]?.value"
100
- :items="tabItems"
101
- :size="size"
102
- :ui="{ root: ui.tabs, content: ui.tabsContent }"
103
- :unmountOnHide="false"
104
- variant="link"
105
- >
106
- <template #content="{ item }">
107
- <FormSection
108
- plain
109
- :section="item.section"
110
- />
111
- </template>
112
- </UTabs>
113
-
114
- <div
115
- v-else
116
- :class="ui.sections"
117
- >
118
- <div
119
- v-for="(section, index) in props.definition.sections"
120
- :key="`${section.id}-${index}`"
121
- :class="getColumnSpanClass(section.columns)"
122
- >
123
- <FormSection :section="section" />
124
- </div>
125
- </div>
126
-
127
- <div
128
- v-if="slots.footer"
129
- :class="ui.footer"
130
- >
131
- <slot
132
- :dirty="dirty"
133
- name="footer"
134
- :reset="reset"
135
- />
136
- </div>
137
- </UForm>
89
+ <!--
90
+ `novalidate` apaga la validación nativa del navegador, no el `required`.
91
+ Sin él conviven dos validaciones: la del navegador corta el envío antes
92
+ de que el esquema llegue a ejecutarse, y avisa con un globo suyo, en el
93
+ idioma del navegador y sin pasar por el tema. Los mensajes del esquema
94
+ no llegaban a verse en los campos obligatorios.
95
+
96
+ El atributo `required` se queda en los inputs, que es lo que anuncia el
97
+ campo como obligatorio a un lector de pantalla y lo que permite
98
+ estilarlo; lo único que se apaga es la interfaz nativa.
99
+ -->
100
+ <UForm
101
+ :id="formId"
102
+ ref="form"
103
+ novalidate
104
+ :schema="props.schema"
105
+ :state="state"
106
+ :ui="{ base: ui.base }"
107
+ @submit="onSubmit"
108
+ >
109
+ <UTabs
110
+ v-if="isTabs"
111
+ :defaultValue="tabItems[0]?.value"
112
+ :items="tabItems"
113
+ :size="size"
114
+ :ui="{ root: ui.tabs, content: ui.tabsContent }"
115
+ :unmountOnHide="false"
116
+ variant="link"
117
+ >
118
+ <template #content="{ item }">
119
+ <FormSection
120
+ plain
121
+ :section="item.section"
122
+ />
123
+ </template>
124
+ </UTabs>
125
+
126
+ <div
127
+ v-else
128
+ :class="ui.sections"
129
+ >
130
+ <div
131
+ v-for="(section, index) in props.definition.sections"
132
+ :key="`${section.id}-${index}`"
133
+ :class="getColumnSpanClass(section.columns)"
134
+ >
135
+ <FormSection :section="section" />
136
+ </div>
137
+ </div>
138
+
139
+ <div
140
+ v-if="slots.footer"
141
+ :class="ui.footer"
142
+ >
143
+ <slot
144
+ :dirty="dirty"
145
+ name="footer"
146
+ :reset="reset"
147
+ />
148
+ </div>
149
+ </UForm>
138
150
  </template>
@@ -1,6 +1,7 @@
1
1
  <script setup>
2
2
  import { computed, useId, useTemplateRef } from "vue";
3
3
  import { useVunoMessages, useVunoTheme } from "#vuno/composables/useVunoTheme";
4
+ import AppModal from "#vuno/components/App/AppModal.vue";
4
5
  import Form from "./Form.vue";
5
6
  const props = defineProps({
6
7
  closeOnSubmit: { type: Boolean, required: false },
@@ -18,7 +19,7 @@ const state = defineModel("state", { type: null, ...{ required: true } });
18
19
  defineSlots();
19
20
  const messages = useVunoMessages();
20
21
  const ui = useVunoTheme("formModal", () => props.ui);
21
- const modalUi = computed(() => ({ body: ui.value.body, content: ui.value.content, footer: ui.value.footer }));
22
+ const modalUi = computed(() => ({ body: ui.value.body, content: ui.value.content, footer: ui.value.footer, root: ui.value.root }));
22
23
  const formId = useId();
23
24
  const form = useTemplateRef("form");
24
25
  function close() {
@@ -33,27 +34,34 @@ function onSubmit(event) {
33
34
  </script>
34
35
 
35
36
  <template>
36
- <UModal
37
+ <!--
38
+ Sobre `AppModal`: un `<dialog>` nativo, no el de Nuxt UI. El navegador lo
39
+ pone en su capa superior, que atrapa el foco, lo devuelve al cerrar y
40
+ apila los diálogos por orden de apertura.
41
+
42
+ Importa que sean todos del mismo sistema: la capa superior queda por
43
+ encima de cualquier z-index, así que un formulario posicionado con
44
+ z-index abierto sobre un diálogo nativo se pintaría detrás de él, en el
45
+ DOM y sin verse.
46
+ -->
47
+ <AppModal
37
48
  v-model:open="open"
38
49
  :description="props.description"
39
50
  :dismissible="!props.loading"
40
- scrollable
41
51
  :title="props.title"
42
52
  :ui="modalUi"
43
53
  >
44
54
  <slot name="trigger" />
45
55
 
46
- <template #body>
47
- <Form
48
- :id="formId"
49
- ref="form"
50
- v-model:state="state"
51
- :definition="props.definition"
52
- :schema="props.schema"
53
- :ui="props.formUi"
54
- @submit="onSubmit"
55
- />
56
- </template>
56
+ <Form
57
+ :id="formId"
58
+ ref="form"
59
+ v-model:state="state"
60
+ :definition="props.definition"
61
+ :schema="props.schema"
62
+ :ui="props.formUi"
63
+ @submit="onSubmit"
64
+ />
57
65
 
58
66
  <template #footer>
59
67
  <slot
@@ -79,5 +87,5 @@ function onSubmit(event) {
79
87
  />
80
88
  </slot>
81
89
  </template>
82
- </UModal>
90
+ </AppModal>
83
91
  </template>
@@ -13,31 +13,31 @@ const showHeader = computed(() => props.plain ? Boolean(props.section.descriptio
13
13
  </script>
14
14
 
15
15
  <template>
16
- <div :class="root">
17
- <div
18
- v-if="showHeader"
19
- :class="ui.header"
20
- >
21
- <p
22
- v-if="props.section.title && !props.plain"
23
- :class="ui.title"
24
- >
25
- {{ props.section.title }}
26
- </p>
27
- <p
28
- v-if="props.section.description"
29
- :class="ui.description"
30
- >
31
- {{ props.section.description }}
32
- </p>
33
- </div>
34
-
35
- <div
36
- v-for="(field, index) in props.section.fields"
37
- :key="`${field.name}-${index}`"
38
- :class="getColumnSpanClass(field.columns)"
39
- >
40
- <FormField :field="field" />
41
- </div>
42
- </div>
16
+ <div :class="root">
17
+ <div
18
+ v-if="showHeader"
19
+ :class="ui.header"
20
+ >
21
+ <p
22
+ v-if="props.section.title && !props.plain"
23
+ :class="ui.title"
24
+ >
25
+ {{ props.section.title }}
26
+ </p>
27
+ <p
28
+ v-if="props.section.description"
29
+ :class="ui.description"
30
+ >
31
+ {{ props.section.description }}
32
+ </p>
33
+ </div>
34
+
35
+ <div
36
+ v-for="(field, index) in props.section.fields"
37
+ :key="`${field.name}-${index}`"
38
+ :class="getColumnSpanClass(field.columns)"
39
+ >
40
+ <FormField :field="field" />
41
+ </div>
42
+ </div>
43
43
  </template>