@frigear-nu/forms 0.0.6 → 0.0.8

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 (185) hide show
  1. package/README.md +16 -1
  2. package/dist/module.d.mts +148 -8
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +65 -35
  5. package/dist/runtime/app/components/FormBuilder.d.vue.ts +26 -0
  6. package/dist/runtime/app/components/FormBuilder.vue +220 -0
  7. package/dist/runtime/app/components/FormBuilder.vue.d.ts +26 -0
  8. package/dist/runtime/app/components/FormBuilderAccess.d.vue.ts +16 -0
  9. package/dist/runtime/app/components/FormBuilderAccess.vue +404 -0
  10. package/dist/runtime/app/components/FormBuilderAccess.vue.d.ts +16 -0
  11. package/dist/runtime/app/components/FormBuilderCaptchaSettings.d.vue.ts +15 -0
  12. package/dist/runtime/app/components/FormBuilderCaptchaSettings.vue +126 -0
  13. package/dist/runtime/app/components/FormBuilderCaptchaSettings.vue.d.ts +15 -0
  14. package/dist/runtime/app/components/FormBuilderCondition.d.vue.ts +16 -0
  15. package/dist/runtime/app/components/FormBuilderCondition.vue +62 -0
  16. package/dist/runtime/app/components/FormBuilderCondition.vue.d.ts +16 -0
  17. package/dist/runtime/app/components/FormBuilderFieldRow.d.vue.ts +27 -0
  18. package/dist/runtime/app/components/FormBuilderFieldRow.vue +87 -0
  19. package/dist/runtime/app/components/FormBuilderFieldRow.vue.d.ts +27 -0
  20. package/dist/runtime/app/components/FormBuilderFieldSettings.d.vue.ts +25 -0
  21. package/dist/runtime/app/components/FormBuilderFieldSettings.vue +170 -0
  22. package/dist/runtime/app/components/FormBuilderFieldSettings.vue.d.ts +25 -0
  23. package/dist/runtime/app/components/FormBuilderInsertRow.d.vue.ts +20 -0
  24. package/dist/runtime/app/components/FormBuilderInsertRow.vue +51 -0
  25. package/dist/runtime/app/components/FormBuilderInsertRow.vue.d.ts +20 -0
  26. package/dist/runtime/app/components/FormBuilderOptions.d.vue.ts +11 -0
  27. package/dist/runtime/app/components/FormBuilderOptions.vue +81 -0
  28. package/dist/runtime/app/components/FormBuilderOptions.vue.d.ts +11 -0
  29. package/dist/runtime/app/components/FormBuilderPhoneSettings.d.vue.ts +11 -0
  30. package/dist/runtime/app/components/FormBuilderPhoneSettings.vue +63 -0
  31. package/dist/runtime/app/components/FormBuilderPhoneSettings.vue.d.ts +11 -0
  32. package/dist/runtime/app/components/FormBuilderSettings.d.vue.ts +11 -0
  33. package/dist/runtime/app/components/FormBuilderSettings.vue +233 -0
  34. package/dist/runtime/app/components/FormBuilderSettings.vue.d.ts +11 -0
  35. package/dist/runtime/app/components/FormBuilderStepHeader.d.vue.ts +27 -0
  36. package/dist/runtime/app/components/FormBuilderStepHeader.vue +71 -0
  37. package/dist/runtime/app/components/FormBuilderStepHeader.vue.d.ts +27 -0
  38. package/dist/runtime/app/components/FormBuilderStepSettings.d.vue.ts +13 -0
  39. package/dist/runtime/app/components/FormBuilderStepSettings.vue +33 -0
  40. package/dist/runtime/app/components/FormBuilderStepSettings.vue.d.ts +13 -0
  41. package/dist/runtime/app/components/FormBuilderText.d.vue.ts +19 -0
  42. package/dist/runtime/app/components/FormBuilderText.vue +127 -0
  43. package/dist/runtime/app/components/FormBuilderText.vue.d.ts +19 -0
  44. package/dist/runtime/app/components/FormCaptcha.d.vue.ts +19 -0
  45. package/dist/runtime/app/components/FormCaptcha.vue +95 -0
  46. package/dist/runtime/app/components/FormCaptcha.vue.d.ts +19 -0
  47. package/dist/runtime/app/components/FormEmbed.d.vue.ts +17 -0
  48. package/dist/runtime/app/components/FormEmbed.vue +113 -0
  49. package/dist/runtime/app/components/FormEmbed.vue.d.ts +17 -0
  50. package/dist/runtime/app/components/FormField.d.vue.ts +4 -0
  51. package/dist/runtime/app/components/FormField.vue +35 -3
  52. package/dist/runtime/app/components/FormField.vue.d.ts +4 -0
  53. package/dist/runtime/app/components/FormPhoneInput.d.vue.ts +20 -0
  54. package/dist/runtime/app/components/FormPhoneInput.vue +56 -0
  55. package/dist/runtime/app/components/FormPhoneInput.vue.d.ts +20 -0
  56. package/dist/runtime/app/components/FormPreview.d.vue.ts +12 -0
  57. package/dist/runtime/app/components/FormPreview.vue +244 -0
  58. package/dist/runtime/app/components/FormPreview.vue.d.ts +12 -0
  59. package/dist/runtime/app/components/FormRenderer.d.vue.ts +6 -0
  60. package/dist/runtime/app/components/FormRenderer.vue +16 -7
  61. package/dist/runtime/app/components/FormRenderer.vue.d.ts +6 -0
  62. package/dist/runtime/app/components/FormSubmissionView.vue +2 -2
  63. package/dist/runtime/app/composables/index.d.ts +4 -1
  64. package/dist/runtime/app/composables/index.js +4 -1
  65. package/dist/runtime/app/composables/useCaptcha.d.ts +22 -0
  66. package/dist/runtime/app/composables/useCaptcha.js +39 -0
  67. package/dist/runtime/app/composables/useCaptchaSecrets.d.ts +7 -0
  68. package/dist/runtime/app/composables/useCaptchaSecrets.js +69 -0
  69. package/dist/runtime/app/composables/useFormBuilderEditor.d.ts +49 -0
  70. package/dist/runtime/app/composables/useFormBuilderEditor.js +210 -0
  71. package/dist/runtime/app/composables/useFormBuilderLabels.d.ts +8 -0
  72. package/dist/runtime/app/composables/useFormBuilderLabels.js +41 -0
  73. package/dist/runtime/app/composables/useForms.d.ts +18 -3
  74. package/dist/runtime/app/composables/useForms.js +38 -5
  75. package/dist/runtime/app/pages/admin-form.vue +112 -69
  76. package/dist/runtime/app/pages/admin-submission.vue +7 -16
  77. package/dist/runtime/app/pages/admin-submissions.vue +41 -46
  78. package/dist/runtime/app/pages/admin.vue +128 -44
  79. package/dist/runtime/app/pages/form.vue +71 -5
  80. package/dist/runtime/i18n/lang/da.json +231 -3
  81. package/dist/runtime/i18n/lang/en.json +231 -3
  82. package/dist/runtime/menus.js +10 -0
  83. package/dist/runtime/server/db/schema.sqlite.d.ts +283 -0
  84. package/dist/runtime/server/db/schema.sqlite.js +33 -1
  85. package/dist/runtime/server/mcp/schemas.d.ts +9 -0
  86. package/dist/runtime/server/mcp/schemas.js +8 -0
  87. package/dist/runtime/server/mcp/tools/admin/create-form.d.ts +2 -0
  88. package/dist/runtime/server/mcp/tools/admin/create-form.js +21 -0
  89. package/dist/runtime/server/mcp/tools/admin/get-form-definition.d.ts +2 -0
  90. package/dist/runtime/server/mcp/tools/admin/get-form-definition.js +14 -0
  91. package/dist/runtime/server/mcp/tools/admin/get-submission.d.ts +2 -0
  92. package/dist/runtime/server/mcp/tools/admin/get-submission.js +26 -0
  93. package/dist/runtime/server/mcp/tools/admin/list-all-forms.d.ts +2 -0
  94. package/dist/runtime/server/mcp/tools/admin/list-all-forms.js +21 -0
  95. package/dist/runtime/server/mcp/tools/admin/list-submissions.d.ts +2 -0
  96. package/dist/runtime/server/mcp/tools/admin/list-submissions.js +22 -0
  97. package/dist/runtime/server/mcp/tools/admin/update-form.d.ts +2 -0
  98. package/dist/runtime/server/mcp/tools/admin/update-form.js +24 -0
  99. package/dist/runtime/server/mcp/tools/get-form.d.ts +2 -0
  100. package/dist/runtime/server/mcp/tools/get-form.js +12 -0
  101. package/dist/runtime/server/mcp/tools/list-forms.d.ts +2 -0
  102. package/dist/runtime/server/mcp/tools/list-forms.js +11 -0
  103. package/dist/runtime/server/mcp/tools/submit-form.d.ts +2 -0
  104. package/dist/runtime/server/mcp/tools/submit-form.js +17 -0
  105. package/dist/runtime/server/plugins/email-delivery.js +16 -11
  106. package/dist/runtime/server/plugins/encryption.d.ts +2 -0
  107. package/dist/runtime/server/plugins/encryption.js +4 -0
  108. package/dist/runtime/server/plugins/staff-scopes.d.ts +2 -0
  109. package/dist/runtime/server/plugins/staff-scopes.js +1 -0
  110. package/dist/runtime/server/routes/admin/form-clone.post.d.ts +2 -0
  111. package/dist/runtime/server/routes/admin/form-clone.post.js +17 -0
  112. package/dist/runtime/server/routes/admin/form-password.delete.d.ts +2 -0
  113. package/dist/runtime/server/routes/admin/form-password.delete.js +8 -0
  114. package/dist/runtime/server/routes/admin/form-password.get.d.ts +2 -0
  115. package/dist/runtime/server/routes/admin/form-password.get.js +8 -0
  116. package/dist/runtime/server/routes/admin/form-password.put.d.ts +2 -0
  117. package/dist/runtime/server/routes/admin/form-password.put.js +14 -0
  118. package/dist/runtime/server/routes/admin/form-secret.delete.d.ts +2 -0
  119. package/dist/runtime/server/routes/admin/form-secret.delete.js +8 -0
  120. package/dist/runtime/server/routes/admin/form-secrets.get.d.ts +2 -0
  121. package/dist/runtime/server/routes/admin/form-secrets.get.js +5 -0
  122. package/dist/runtime/server/routes/admin/form-secrets.put.d.ts +2 -0
  123. package/dist/runtime/server/routes/admin/form-secrets.put.js +12 -0
  124. package/dist/runtime/server/routes/admin/form-token.delete.d.ts +2 -0
  125. package/dist/runtime/server/routes/admin/form-token.delete.js +12 -0
  126. package/dist/runtime/server/routes/admin/form-tokens.get.d.ts +2 -0
  127. package/dist/runtime/server/routes/admin/form-tokens.get.js +8 -0
  128. package/dist/runtime/server/routes/admin/form-tokens.post.d.ts +2 -0
  129. package/dist/runtime/server/routes/admin/form-tokens.post.js +12 -0
  130. package/dist/runtime/server/routes/admin/form.get.js +5 -1
  131. package/dist/runtime/server/routes/admin/form.patch.js +3 -12
  132. package/dist/runtime/server/routes/admin/forms.post.js +1 -6
  133. package/dist/runtime/server/routes/public/access.get.d.ts +2 -0
  134. package/dist/runtime/server/routes/public/access.get.js +3 -0
  135. package/dist/runtime/server/routes/public/form.get.js +3 -1
  136. package/dist/runtime/server/routes/public/submit.post.js +2 -2
  137. package/dist/runtime/server/utils/access.d.ts +27 -0
  138. package/dist/runtime/server/utils/access.js +63 -0
  139. package/dist/runtime/server/utils/admin-schemas.d.ts +219 -15
  140. package/dist/runtime/server/utils/auth.d.ts +7 -7
  141. package/dist/runtime/server/utils/auth.js +4 -4
  142. package/dist/runtime/server/utils/captcha.d.ts +10 -0
  143. package/dist/runtime/server/utils/captcha.js +25 -0
  144. package/dist/runtime/server/utils/crypto.d.ts +18 -0
  145. package/dist/runtime/server/utils/crypto.js +48 -0
  146. package/dist/runtime/server/utils/forms.d.ts +6 -1
  147. package/dist/runtime/server/utils/forms.js +28 -0
  148. package/dist/runtime/server/utils/mcp-access.d.ts +9 -0
  149. package/dist/runtime/server/utils/mcp-access.js +8 -0
  150. package/dist/runtime/server/utils/rate-limit.d.ts +3 -0
  151. package/dist/runtime/server/utils/rate-limit.js +19 -0
  152. package/dist/runtime/server/utils/render.d.ts +2 -11
  153. package/dist/runtime/server/utils/render.js +1 -26
  154. package/dist/runtime/server/utils/secrets.d.ts +28 -0
  155. package/dist/runtime/server/utils/secrets.js +60 -0
  156. package/dist/runtime/server/utils/submit.d.ts +12 -1
  157. package/dist/runtime/server/utils/submit.js +18 -4
  158. package/dist/runtime/utils/builder.d.ts +73 -0
  159. package/dist/runtime/utils/builder.js +220 -0
  160. package/dist/runtime/utils/captcha.d.ts +9 -0
  161. package/dist/runtime/utils/captcha.js +7 -0
  162. package/dist/runtime/utils/conditions.d.ts +3 -1
  163. package/dist/runtime/utils/conditions.js +4 -0
  164. package/dist/runtime/utils/definition.d.ts +116 -6
  165. package/dist/runtime/utils/definition.js +53 -4
  166. package/dist/runtime/utils/embed.d.ts +8 -0
  167. package/dist/runtime/utils/embed.js +7 -0
  168. package/dist/runtime/utils/index.d.ts +16 -3
  169. package/dist/runtime/utils/index.js +66 -3
  170. package/dist/runtime/utils/markdown.d.ts +1 -5
  171. package/dist/runtime/utils/markdown.js +1 -5
  172. package/dist/runtime/utils/phone.d.ts +29 -0
  173. package/dist/runtime/utils/phone.js +294 -0
  174. package/dist/runtime/utils/preview.d.ts +12 -0
  175. package/dist/runtime/utils/preview.js +17 -0
  176. package/dist/runtime/utils/schedule.d.ts +1 -0
  177. package/dist/runtime/utils/schedule.js +1 -0
  178. package/dist/runtime/utils/schema.d.ts +2 -0
  179. package/dist/runtime/utils/schema.js +12 -4
  180. package/dist/runtime/utils/template.d.ts +57 -0
  181. package/dist/runtime/utils/template.js +147 -0
  182. package/dist/runtime/utils/text.d.ts +0 -3
  183. package/dist/runtime/utils/text.js +0 -5
  184. package/dist/types.d.mts +5 -1
  185. package/package.json +13 -5
@@ -0,0 +1,51 @@
1
+ <script setup>
2
+ defineProps({
3
+ items: { type: Array, required: false, default: void 0 },
4
+ dot: { type: Boolean, required: false },
5
+ visible: { type: Boolean, required: false },
6
+ label: { type: String, required: false, default: void 0 },
7
+ ariaLabel: { type: String, required: false, default: void 0 },
8
+ line: { type: Boolean, required: false }
9
+ });
10
+ const emit = defineEmits(["click", "dragover"]);
11
+ </script>
12
+
13
+ <template>
14
+ <div class="insert-row">
15
+ <UDropdownMenu
16
+ v-if="items"
17
+ :items="items"
18
+ :content="{ align: 'center' }"
19
+ >
20
+ <UButton
21
+ class="insert-button"
22
+ :class="dot ? 'insert-dot' : visible ? 'insert-button-visible' : ''"
23
+ color="neutral"
24
+ variant="soft"
25
+ size="xs"
26
+ icon="i-lucide-plus"
27
+ :aria-label="ariaLabel"
28
+ :label="label"
29
+ />
30
+ </UDropdownMenu>
31
+ <UButton
32
+ v-else
33
+ class="insert-button"
34
+ color="neutral"
35
+ variant="soft"
36
+ size="xs"
37
+ icon="i-lucide-plus"
38
+ :label="label"
39
+ @click="emit('click')"
40
+ @dragover="emit('dragover', $event)"
41
+ />
42
+ <div
43
+ v-if="line"
44
+ class="pointer-events-none absolute inset-x-0 top-1/2 h-1 -translate-y-1/2 rounded bg-primary"
45
+ />
46
+ </div>
47
+ </template>
48
+
49
+ <style scoped>
50
+ .insert-row{align-items:center;display:flex;height:1rem;justify-content:center;position:relative;width:100%}.insert-row:before{border-top:1px dashed var(--ui-border-accented);content:"";inset-inline:0;opacity:0;position:absolute;top:50%;transition:opacity .15s}.insert-button{opacity:.35;position:relative;transition:opacity .15s}.insert-dot{align-items:center;border-radius:9999px;display:inline-flex;flex:none;gap:0;height:1rem;justify-content:center;line-height:0;min-width:0;padding:0;transition:opacity .15s,transform .15s;width:1rem}.insert-dot :deep(.iconify){display:block;flex:none;height:.75rem;margin:0;width:.75rem}@media (hover:none),(max-width:640px),(pointer:coarse){.insert-row{height:2rem}.insert-dot{height:1.75rem;opacity:1;width:1.75rem}.insert-dot :deep(.iconify){height:1rem;width:1rem}.insert-dot:focus-visible,.insert-dot:hover{transform:scale(1.15)}}.insert-dot:focus-visible,.insert-dot:hover{transform:scale(1.6)}.insert-row:focus-within:before,.insert-row:hover:before{opacity:1}.insert-button-visible,.insert-row:focus-within .insert-button,.insert-row:hover .insert-button{opacity:1}@media (hover:none){.insert-button,.insert-row:before{opacity:1}}
51
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { DropdownMenuItem } from '@nuxt/ui';
2
+ type __VLS_Props = {
3
+ items?: DropdownMenuItem[][];
4
+ /** A small round dot instead of a regular button. */
5
+ dot?: boolean;
6
+ /** Keeps the button fully visible instead of only on hover. */
7
+ visible?: boolean;
8
+ label?: string;
9
+ ariaLabel?: string;
10
+ line?: boolean;
11
+ };
12
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ click: () => any;
14
+ dragover: (event: DragEvent) => any;
15
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
+ onClick?: (() => any) | undefined;
17
+ onDragover?: ((event: DragEvent) => any) | undefined;
18
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import type { FieldOption } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ options?: FieldOption[];
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ "update:options": (options: FieldOption[] | undefined) => any;
7
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
8
+ "onUpdate:options"?: ((options: FieldOption[] | undefined) => any) | undefined;
9
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
@@ -0,0 +1,81 @@
1
+ <script setup>
2
+ import { arrayMove } from "../../utils";
3
+ const props = defineProps({
4
+ options: { type: Array, required: false }
5
+ });
6
+ const emit = defineEmits(["update:options"]);
7
+ const t = useModuleT("forms");
8
+ function setOption(index, patch) {
9
+ const options = [...props.options ?? []];
10
+ options[index] = { ...options[index], ...patch };
11
+ emit("update:options", options);
12
+ }
13
+ function addOption() {
14
+ const options = props.options ?? [];
15
+ emit("update:options", [...options, { value: `option_${options.length + 1}`, label: `Option ${options.length + 1}` }]);
16
+ }
17
+ function moveOption(index, delta) {
18
+ emit("update:options", arrayMove(props.options ?? [], index, index + delta));
19
+ }
20
+ </script>
21
+
22
+ <template>
23
+ <div class="space-y-2">
24
+ <h4 class="text-sm font-medium">
25
+ {{ t("builder.options", "Options") }}
26
+ </h4>
27
+ <div
28
+ v-for="(option, optionIndex) in options"
29
+ :key="optionIndex"
30
+ class="space-y-1 rounded-md border border-default p-2"
31
+ >
32
+ <FormBuilderText
33
+ :model-value="option.label"
34
+ :placeholder="t('builder.label', 'Label')"
35
+ @update:model-value="setOption(optionIndex, { label: $event ?? '' })"
36
+ />
37
+ <UInput
38
+ :model-value="option.value"
39
+ :placeholder="t('builder.value', 'Value')"
40
+ class="w-full font-mono"
41
+ @update:model-value="setOption(optionIndex, { value: String($event) })"
42
+ />
43
+ <div class="flex justify-end">
44
+ <UButton
45
+ color="neutral"
46
+ variant="ghost"
47
+ size="xs"
48
+ icon="i-lucide-chevron-up"
49
+ :disabled="optionIndex === 0"
50
+ :aria-label="t('builder.moveUp', 'Move up')"
51
+ @click="moveOption(optionIndex, -1)"
52
+ />
53
+ <UButton
54
+ color="neutral"
55
+ variant="ghost"
56
+ size="xs"
57
+ icon="i-lucide-chevron-down"
58
+ :disabled="optionIndex === (options?.length ?? 0) - 1"
59
+ :aria-label="t('builder.moveDown', 'Move down')"
60
+ @click="moveOption(optionIndex, 1)"
61
+ />
62
+ <UButton
63
+ color="error"
64
+ variant="ghost"
65
+ size="xs"
66
+ icon="i-lucide-x"
67
+ :aria-label="t('builder.delete', 'Delete')"
68
+ @click="emit('update:options', options?.filter((_, i) => i !== optionIndex))"
69
+ />
70
+ </div>
71
+ </div>
72
+ <UButton
73
+ color="neutral"
74
+ variant="outline"
75
+ size="xs"
76
+ icon="i-lucide-plus"
77
+ :label="t('builder.addOption', 'Add option')"
78
+ @click="addOption"
79
+ />
80
+ </div>
81
+ </template>
@@ -0,0 +1,11 @@
1
+ import type { FieldOption } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ options?: FieldOption[];
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ "update:options": (options: FieldOption[] | undefined) => any;
7
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
8
+ "onUpdate:options"?: ((options: FieldOption[] | undefined) => any) | undefined;
9
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import type { FormField } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ field: FormField;
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ patch: (patch: Partial<FormField>) => any;
7
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
8
+ onPatch?: ((patch: Partial<FormField>) => any) | undefined;
9
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
@@ -0,0 +1,63 @@
1
+ <script setup>
2
+ import { allPhoneCountries, countryName, defaultPhoneCountry, phoneCountries } from "../../utils";
3
+ import { useFormsLocale } from "../composables/useForms";
4
+ const props = defineProps({
5
+ field: { type: Object, required: true }
6
+ });
7
+ const emit = defineEmits(["patch"]);
8
+ const t = useModuleT("forms");
9
+ const locale = useFormsLocale();
10
+ const modeItems = computed(() => [
11
+ { value: "simple", label: t("builder.phoneSimple", "Single input") },
12
+ { value: "country", label: t("builder.phoneCountry", "Country code + number") }
13
+ ]);
14
+ const countryItems = computed(() => allPhoneCountries.map(({ code, dial, flag }) => ({
15
+ value: code,
16
+ label: `${flag} ${countryName(code, locale.value)} (+${dial})`
17
+ })));
18
+ const offeredCountries = computed(() => new Set(phoneCountries(props.field.phone).map((country) => country.code)));
19
+ function patchPhone(patch) {
20
+ const next = Object.fromEntries(Object.entries({ ...props.field.phone, ...patch }).filter(([, value]) => value !== void 0));
21
+ const countries = next.countries;
22
+ if (countries && next.defaultCountry && !countries.includes(next.defaultCountry)) delete next.defaultCountry;
23
+ emit("patch", { phone: Object.keys(next).length ? next : void 0 });
24
+ }
25
+ function setMode(mode) {
26
+ emit("patch", { phone: mode === "country" ? { ...props.field.phone, mode } : void 0 });
27
+ }
28
+ </script>
29
+
30
+ <template>
31
+ <UFormField :label="t('builder.phoneMode', 'Phone input')">
32
+ <USelect
33
+ :model-value="field.phone?.mode ?? 'simple'"
34
+ :items="modeItems"
35
+ class="w-full"
36
+ @update:model-value="setMode($event)"
37
+ />
38
+ </UFormField>
39
+ <template v-if="field.phone?.mode === 'country'">
40
+ <UFormField
41
+ :label="t('builder.phoneCountries', 'Allowed countries')"
42
+ :description="t('builder.phoneCountriesHint', 'Leave empty to allow all countries.')"
43
+ >
44
+ <USelectMenu
45
+ :model-value="field.phone?.countries ?? []"
46
+ :items="countryItems"
47
+ value-key="value"
48
+ multiple
49
+ class="w-full"
50
+ @update:model-value="patchPhone({ countries: $event.length ? $event : void 0 })"
51
+ />
52
+ </UFormField>
53
+ <UFormField :label="t('builder.phoneDefaultCountry', 'Default country')">
54
+ <USelectMenu
55
+ :model-value="defaultPhoneCountry(field.phone)"
56
+ :items="countryItems.filter((item) => offeredCountries.has(item.value))"
57
+ value-key="value"
58
+ class="w-full"
59
+ @update:model-value="patchPhone({ defaultCountry: $event })"
60
+ />
61
+ </UFormField>
62
+ </template>
63
+ </template>
@@ -0,0 +1,11 @@
1
+ import type { FormField } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ field: FormField;
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ patch: (patch: Partial<FormField>) => any;
7
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
8
+ onPatch?: ((patch: Partial<FormField>) => any) | undefined;
9
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import type { FormDefinition } from '../../../types.js';
2
+ type __VLS_ModelProps = {
3
+ modelValue: FormDefinition;
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ "update:modelValue": (value: FormDefinition) => any;
7
+ }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
8
+ "onUpdate:modelValue"?: ((value: FormDefinition) => any) | undefined;
9
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
@@ -0,0 +1,233 @@
1
+ <script setup>
2
+ import {
3
+ availableVariables,
4
+ composeEmail,
5
+ emailFieldKeys,
6
+ patchEmailDelivery,
7
+ renderAnswers,
8
+ routableFields,
9
+ sampleAnswers,
10
+ setEmailRecipients,
11
+ setRoute,
12
+ setRouteField,
13
+ setStorage,
14
+ storageWarnings,
15
+ translate,
16
+ unknownBodyVariables
17
+ } from "../../utils";
18
+ import { useFormsLocale } from "../composables/useForms";
19
+ const definition = defineModel({ type: Object, ...{ required: true } });
20
+ const t = useModuleT("forms");
21
+ const locale = useFormsLocale();
22
+ const email = computed(() => definition.value.delivery?.email);
23
+ const warnings = computed(() => storageWarnings(definition.value));
24
+ const replyToFields = computed(() => emailFieldKeys(definition.value));
25
+ const routable = computed(() => routableFields(definition.value));
26
+ const routeField = computed(() => routable.value.find((field) => field.key === email.value?.routes?.field));
27
+ const unknownVariables = computed(() => unknownBodyVariables(definition.value));
28
+ const variables = computed(() => availableVariables(definition.value));
29
+ const answersExample = "{{answers}}";
30
+ const variableLabel = (name) => `{{${name}}}`;
31
+ const bodyEditor = ref();
32
+ let lastTextarea;
33
+ function rememberTextarea(event) {
34
+ if (event.target instanceof HTMLTextAreaElement) lastTextarea = event.target;
35
+ }
36
+ function insertVariable(name) {
37
+ const textarea = lastTextarea?.isConnected ? lastTextarea : bodyEditor.value?.querySelector("textarea");
38
+ if (!textarea) return;
39
+ const text = variableLabel(name);
40
+ const start = textarea.selectionStart ?? textarea.value.length;
41
+ const end = textarea.selectionEnd ?? start;
42
+ textarea.value = textarea.value.slice(0, start) + text + textarea.value.slice(end);
43
+ textarea.dispatchEvent(new Event("input", { bubbles: true }));
44
+ textarea.focus();
45
+ textarea.setSelectionRange(start + text.length, start + text.length);
46
+ }
47
+ const preview = computed(() => {
48
+ if (!email.value) return "";
49
+ const answers = sampleAnswers(definition.value);
50
+ return composeEmail(email.value, {
51
+ form: { name: "Form name", slug: "form-slug", version: 1 },
52
+ submission: { id: "sub_example", createdAt: (/* @__PURE__ */ new Date()).toISOString(), locale: locale.value },
53
+ answers: renderAnswers(definition.value, answers, locale.value),
54
+ files: [],
55
+ replyTo: "name@example.com"
56
+ }).text;
57
+ });
58
+ const NONE = "__none__";
59
+ const replyToItems = computed(() => [
60
+ { value: NONE, label: t("delivery.replyToNone", "None") },
61
+ ...replyToFields.value.map((value) => ({ value, label: value }))
62
+ ]);
63
+ const routeFieldItems = computed(() => [
64
+ { value: NONE, label: t("delivery.routingNone", "No routing") },
65
+ ...routable.value.map((field) => ({ value: field.key, label: translate(field.label, locale.value) || field.key }))
66
+ ]);
67
+ </script>
68
+
69
+ <template>
70
+ <div class="max-w-2xl space-y-8">
71
+ <section class="space-y-3">
72
+ <h2 class="font-semibold">
73
+ {{ t("delivery.storeTitle", "Store submissions") }}
74
+ </h2>
75
+ <USwitch
76
+ :model-value="definition.storage === 'db'"
77
+ :label="t('delivery.storeTitle', 'Store submissions')"
78
+ :description="t('delivery.storeHint', 'Keep submissions in the database so they can be reviewed under Submissions.')"
79
+ @update:model-value="definition = setStorage(definition, $event ? 'db' : 'none')"
80
+ />
81
+ <UAlert
82
+ v-if="warnings.files"
83
+ color="warning"
84
+ variant="subtle"
85
+ :title="t('delivery.filesWarning', 'This form has file upload fields, which need stored submissions. Remove them or keep storing.')"
86
+ />
87
+ <UAlert
88
+ v-if="warnings.lost"
89
+ color="warning"
90
+ variant="subtle"
91
+ :title="t('delivery.lostWarning', 'Submissions are neither stored nor emailed. Add recipients or keep storing, or answers are lost unless something handles forms:submitted.')"
92
+ />
93
+ </section>
94
+
95
+ <section class="space-y-4">
96
+ <h2 class="font-semibold">
97
+ {{ t("delivery.emailTitle", "Email notification") }}
98
+ </h2>
99
+ <UFormField
100
+ :label="t('delivery.recipients', 'Recipients')"
101
+ :description="t('delivery.recipientsHint', 'Press Enter after each address. Leave empty to turn email off.')"
102
+ >
103
+ <UInputTags
104
+ :model-value="email?.to ?? []"
105
+ type="email"
106
+ class="w-full"
107
+ @update:model-value="definition = setEmailRecipients(definition, $event)"
108
+ />
109
+ </UFormField>
110
+
111
+ <p
112
+ v-if="!email"
113
+ class="text-sm text-muted"
114
+ >
115
+ {{ t("delivery.needRecipients", "Add recipients first to set the subject, reply-to and routing.") }}
116
+ </p>
117
+
118
+ <template v-else>
119
+ <UFormField
120
+ :label="t('delivery.subject', 'Subject')"
121
+ :description="t('delivery.subjectHint', 'Variables from the list below work here too.')"
122
+ >
123
+ <FormBuilderText
124
+ :model-value="email.subject"
125
+ @update:model-value="definition = patchEmailDelivery(definition, { subject: $event })"
126
+ />
127
+ </UFormField>
128
+
129
+ <UFormField
130
+ :label="t('delivery.body', 'Email body')"
131
+ :description="t('delivery.bodyHint', 'Optional. Leave empty to email the list of all answers. Variables are replaced with the submitted values.')"
132
+ >
133
+ <div
134
+ ref="bodyEditor"
135
+ class="space-y-2"
136
+ @focusin="rememberTextarea"
137
+ >
138
+ <FormBuilderText
139
+ :model-value="email.body"
140
+ multiline
141
+ :preview="false"
142
+ @update:model-value="definition = patchEmailDelivery(definition, { body: $event })"
143
+ />
144
+ <div class="space-y-1">
145
+ <p class="text-xs text-muted">
146
+ {{ t("delivery.bodyVariables", "Click a variable to insert it at the cursor. The full list of answers is") }}
147
+ <code>{{ answersExample }}</code>.
148
+ </p>
149
+ <div class="flex max-h-28 flex-wrap gap-1 overflow-y-auto">
150
+ <UButton
151
+ v-for="name in variables"
152
+ :key="name"
153
+ type="button"
154
+ color="neutral"
155
+ variant="subtle"
156
+ size="xs"
157
+ class="font-mono"
158
+ :label="variableLabel(name)"
159
+ @click="insertVariable(name)"
160
+ />
161
+ </div>
162
+ </div>
163
+ <UAlert
164
+ v-if="unknownVariables.length"
165
+ color="warning"
166
+ variant="subtle"
167
+ :title="t('delivery.bodyUnknown', 'Unknown variables are left empty: {names}', { names: unknownVariables.map(variableLabel).join(', ') })"
168
+ />
169
+ <div
170
+ v-if="email.body"
171
+ class="space-y-1"
172
+ >
173
+ <p class="text-xs font-medium text-muted">
174
+ {{ t("delivery.bodyPreview", "Preview with sample answers") }}
175
+ </p>
176
+ <pre class="max-h-48 overflow-auto whitespace-pre-wrap rounded-md border border-default bg-elevated/50 p-2 text-xs">{{ preview }}</pre>
177
+ <UButton
178
+ type="button"
179
+ color="neutral"
180
+ variant="link"
181
+ size="xs"
182
+ icon="i-lucide-rotate-ccw"
183
+ :label="t('delivery.bodyReset', 'Reset to the default body')"
184
+ @click="definition = patchEmailDelivery(definition, { body: void 0 })"
185
+ />
186
+ </div>
187
+ </div>
188
+ </UFormField>
189
+
190
+ <UFormField
191
+ :label="t('delivery.replyTo', 'Reply to')"
192
+ :description="t('delivery.replyToHint', 'The answer of this email field becomes the Reply-To.')"
193
+ >
194
+ <USelect
195
+ :model-value="email.replyToField ?? NONE"
196
+ :items="replyToItems"
197
+ :disabled="!replyToFields.length"
198
+ class="w-full"
199
+ @update:model-value="definition = patchEmailDelivery(definition, { replyToField: $event === NONE ? void 0 : String($event) })"
200
+ />
201
+ </UFormField>
202
+
203
+ <UFormField
204
+ v-if="routable.length"
205
+ :label="t('delivery.routing', 'Route by answer')"
206
+ :description="t('delivery.routingHint', 'Send to other recipients depending on the answer. Others get the default recipients.')"
207
+ >
208
+ <USelect
209
+ :model-value="email.routes?.field ?? NONE"
210
+ :items="routeFieldItems"
211
+ class="w-full"
212
+ @update:model-value="definition = setRouteField(definition, $event === NONE ? void 0 : String($event))"
213
+ />
214
+ </UFormField>
215
+
216
+ <template v-if="routeField">
217
+ <UFormField
218
+ v-for="option in routeField.options"
219
+ :key="option.value"
220
+ :label="t('delivery.routeTo', 'Recipients for \u201C{value}\u201D', { value: translate(option.label, locale) || option.value })"
221
+ >
222
+ <UInputTags
223
+ :model-value="email.routes?.to[option.value] ?? []"
224
+ type="email"
225
+ class="w-full"
226
+ @update:model-value="definition = setRoute(definition, option.value, $event)"
227
+ />
228
+ </UFormField>
229
+ </template>
230
+ </template>
231
+ </section>
232
+ </div>
233
+ </template>
@@ -0,0 +1,11 @@
1
+ import type { FormDefinition } from '../../../types.js';
2
+ type __VLS_ModelProps = {
3
+ modelValue: FormDefinition;
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ "update:modelValue": (value: FormDefinition) => any;
7
+ }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
8
+ "onUpdate:modelValue"?: ((value: FormDefinition) => any) | undefined;
9
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
@@ -0,0 +1,27 @@
1
+ import type { FormStep } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ step: FormStep;
4
+ /** Zero-based position; shown one-based. */
5
+ index: number;
6
+ isFirst: boolean;
7
+ isLast: boolean;
8
+ /** The only step cannot be deleted. */
9
+ isOnly: boolean;
10
+ };
11
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
12
+ select: () => any;
13
+ dragstart: (event: DragEvent) => any;
14
+ dragend: () => any;
15
+ up: () => any;
16
+ down: () => any;
17
+ remove: () => any;
18
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
19
+ onSelect?: (() => any) | undefined;
20
+ onDragstart?: ((event: DragEvent) => any) | undefined;
21
+ onDragend?: (() => any) | undefined;
22
+ onUp?: (() => any) | undefined;
23
+ onDown?: (() => any) | undefined;
24
+ onRemove?: (() => any) | undefined;
25
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
26
+ declare const _default: typeof __VLS_export;
27
+ export default _default;
@@ -0,0 +1,71 @@
1
+ <script setup>
2
+ import { translate } from "../../utils";
3
+ import { useFormsLocale } from "../composables/useForms";
4
+ defineProps({
5
+ step: { type: Object, required: true },
6
+ index: { type: Number, required: true },
7
+ isFirst: { type: Boolean, required: true },
8
+ isLast: { type: Boolean, required: true },
9
+ isOnly: { type: Boolean, required: true }
10
+ });
11
+ const emit = defineEmits(["select", "dragstart", "dragend", "up", "down", "remove"]);
12
+ const t = useModuleT("forms");
13
+ const locale = useFormsLocale();
14
+ </script>
15
+
16
+ <template>
17
+ <header
18
+ class="flex cursor-pointer items-center gap-2 border-b border-default px-3 py-2"
19
+ @click="emit('select')"
20
+ >
21
+ <span
22
+ draggable="true"
23
+ class="cursor-grab text-muted active:cursor-grabbing"
24
+ :title="t('builder.dragStep', 'Drag to reorder steps')"
25
+ @dragstart.stop="emit('dragstart', $event)"
26
+ @dragend="emit('dragend')"
27
+ >
28
+ <UIcon
29
+ name="i-lucide-grip-vertical"
30
+ class="size-4"
31
+ />
32
+ </span>
33
+ <span class="flex-1 truncate text-sm font-semibold">
34
+ {{ index + 1 }}. {{ translate(step.title, locale) || step.id }}
35
+ </span>
36
+ <UBadge
37
+ v-if="step.showIf"
38
+ color="neutral"
39
+ variant="subtle"
40
+ size="sm"
41
+ :label="t('builder.conditionalBadge', 'Conditional')"
42
+ />
43
+ <UButton
44
+ color="neutral"
45
+ variant="ghost"
46
+ size="xs"
47
+ icon="i-lucide-chevron-up"
48
+ :disabled="isFirst"
49
+ :aria-label="t('builder.moveUp', 'Move up')"
50
+ @click.stop="emit('up')"
51
+ />
52
+ <UButton
53
+ color="neutral"
54
+ variant="ghost"
55
+ size="xs"
56
+ icon="i-lucide-chevron-down"
57
+ :disabled="isLast"
58
+ :aria-label="t('builder.moveDown', 'Move down')"
59
+ @click.stop="emit('down')"
60
+ />
61
+ <UButton
62
+ color="error"
63
+ variant="ghost"
64
+ size="xs"
65
+ icon="i-lucide-trash-2"
66
+ :disabled="isOnly"
67
+ :aria-label="t('builder.deleteStep', 'Delete step')"
68
+ @click.stop="emit('remove')"
69
+ />
70
+ </header>
71
+ </template>
@@ -0,0 +1,27 @@
1
+ import type { FormStep } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ step: FormStep;
4
+ /** Zero-based position; shown one-based. */
5
+ index: number;
6
+ isFirst: boolean;
7
+ isLast: boolean;
8
+ /** The only step cannot be deleted. */
9
+ isOnly: boolean;
10
+ };
11
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
12
+ select: () => any;
13
+ dragstart: (event: DragEvent) => any;
14
+ dragend: () => any;
15
+ up: () => any;
16
+ down: () => any;
17
+ remove: () => any;
18
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
19
+ onSelect?: (() => any) | undefined;
20
+ onDragstart?: ((event: DragEvent) => any) | undefined;
21
+ onDragend?: (() => any) | undefined;
22
+ onUp?: (() => any) | undefined;
23
+ onDown?: (() => any) | undefined;
24
+ onRemove?: (() => any) | undefined;
25
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
26
+ declare const _default: typeof __VLS_export;
27
+ export default _default;