@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,13 @@
1
+ import type { FormStep } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ step: FormStep;
4
+ /** Keys of the fields the step condition may look at. */
5
+ conditionFields: string[];
6
+ };
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
8
+ patch: (patch: Partial<FormStep>) => any;
9
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
10
+ onPatch?: ((patch: Partial<FormStep>) => any) | undefined;
11
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
@@ -0,0 +1,33 @@
1
+ <script setup>
2
+ defineProps({
3
+ step: { type: Object, required: true },
4
+ conditionFields: { type: Array, required: true }
5
+ });
6
+ const emit = defineEmits(["patch"]);
7
+ const t = useModuleT("forms");
8
+ </script>
9
+
10
+ <template>
11
+ <aside class="space-y-4 border-b border-default bg-elevated/40 p-3">
12
+ <h3 class="font-semibold">
13
+ {{ t("builder.step", "Step") }}
14
+ </h3>
15
+ <UFormField :label="t('builder.stepTitle', 'Title')">
16
+ <FormBuilderText
17
+ :model-value="step.title"
18
+ @update:model-value="emit('patch', { title: $event })"
19
+ />
20
+ </UFormField>
21
+ <UFormField :label="t('builder.description', 'Help text')">
22
+ <FormBuilderText
23
+ :model-value="step.description"
24
+ @update:model-value="emit('patch', { description: $event })"
25
+ />
26
+ </UFormField>
27
+ <FormBuilderCondition
28
+ :model-value="step.showIf"
29
+ :fields="conditionFields"
30
+ @update:model-value="emit('patch', { showIf: $event })"
31
+ />
32
+ </aside>
33
+ </template>
@@ -0,0 +1,13 @@
1
+ import type { FormStep } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ step: FormStep;
4
+ /** Keys of the fields the step condition may look at. */
5
+ conditionFields: string[];
6
+ };
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
8
+ patch: (patch: Partial<FormStep>) => any;
9
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
10
+ onPatch?: ((patch: Partial<FormStep>) => any) | undefined;
11
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
@@ -0,0 +1,19 @@
1
+ import type { Translated } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ placeholder?: string;
4
+ multiline?: boolean;
5
+ /** Write / Preview toggle for multiline text (markdown). */
6
+ preview?: boolean;
7
+ };
8
+ type __VLS_ModelProps = {
9
+ /** Either one string for every language, or a map of locale → text. */
10
+ modelValue?: Translated | undefined;
11
+ };
12
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
13
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
14
+ "update:modelValue": (value: Translated | undefined) => any;
15
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
16
+ "onUpdate:modelValue"?: ((value: Translated | undefined) => any) | undefined;
17
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -0,0 +1,127 @@
1
+ <script setup>
2
+ import { collapseTranslated, expandTranslated, readTranslated } from "../../utils";
3
+ import { useFormsLocale, useFormsLocales } from "../composables/useForms";
4
+ const props = defineProps({
5
+ placeholder: { type: String, required: false },
6
+ multiline: { type: Boolean, required: false },
7
+ preview: { type: Boolean, required: false, default: true }
8
+ });
9
+ const model = defineModel({ type: null });
10
+ const t = useModuleT("forms");
11
+ const current = useFormsLocale();
12
+ const available = useFormsLocales();
13
+ const translated = computed(() => !!model.value && typeof model.value === "object");
14
+ const added = ref([]);
15
+ const newLocale = ref("");
16
+ const locales = computed(() => [.../* @__PURE__ */ new Set([...available.value, ...Object.keys(translated.value ? model.value : {}), ...added.value])]);
17
+ function addLocale() {
18
+ const code = newLocale.value.trim();
19
+ if (/^[a-z]{2,3}(?:[-_][a-z0-9]{2,8})*$/i.test(code) && !locales.value.includes(code)) added.value.push(code);
20
+ newLocale.value = "";
21
+ }
22
+ function toggle() {
23
+ model.value = translated.value ? collapseTranslated(model.value, current.value) : expandTranslated(model.value, current.value) ?? {};
24
+ }
25
+ function setPlain(text) {
26
+ model.value = text || void 0;
27
+ }
28
+ function setLocale(locale, text) {
29
+ model.value = Object.fromEntries(Object.entries({ ...model.value, [locale]: text }).filter(([, entry]) => entry));
30
+ }
31
+ </script>
32
+
33
+ <template>
34
+ <div class="space-y-1">
35
+ <div
36
+ v-if="!translated"
37
+ class="flex items-start gap-1"
38
+ >
39
+ <MarkdownTextarea
40
+ v-if="multiline"
41
+ :model-value="readTranslated(model, current)"
42
+ :placeholder="placeholder"
43
+ :rows="4"
44
+ :preview="props.preview"
45
+ class="min-w-0 flex-1"
46
+ @update:model-value="setPlain(String($event))"
47
+ />
48
+ <UInput
49
+ v-else
50
+ :model-value="readTranslated(model, current)"
51
+ :placeholder="placeholder"
52
+ class="min-w-0 flex-1"
53
+ @update:model-value="setPlain(String($event))"
54
+ />
55
+ <UButton
56
+ color="neutral"
57
+ variant="outline"
58
+ icon="i-lucide-languages"
59
+ :aria-label="t('builder.translate', 'Translate')"
60
+ :title="t('builder.translate', 'Translate')"
61
+ @click="toggle"
62
+ />
63
+ </div>
64
+
65
+ <div
66
+ v-else
67
+ class="space-y-1 rounded-md border border-default p-1.5"
68
+ >
69
+ <div
70
+ v-for="locale in locales"
71
+ :key="locale"
72
+ class="flex items-start gap-1.5"
73
+ >
74
+ <UBadge
75
+ :color="locale === current ? 'primary' : 'neutral'"
76
+ variant="subtle"
77
+ size="sm"
78
+ class="mt-1.5 w-8 justify-center uppercase"
79
+ :label="locale"
80
+ />
81
+ <MarkdownTextarea
82
+ v-if="multiline"
83
+ :model-value="readTranslated(model, locale)"
84
+ :placeholder="placeholder"
85
+ :rows="3"
86
+ :preview="props.preview"
87
+ class="min-w-0 flex-1"
88
+ @update:model-value="setLocale(locale, String($event))"
89
+ />
90
+ <UInput
91
+ v-else
92
+ :model-value="readTranslated(model, locale)"
93
+ :placeholder="placeholder"
94
+ class="min-w-0 flex-1"
95
+ @update:model-value="setLocale(locale, String($event))"
96
+ />
97
+ </div>
98
+ <form
99
+ class="flex gap-1"
100
+ @submit.prevent="addLocale"
101
+ >
102
+ <UInput
103
+ v-model="newLocale"
104
+ size="xs"
105
+ :placeholder="t('builder.addLanguage', 'Add language, e.g. da')"
106
+ class="min-w-0 flex-1"
107
+ />
108
+ <UButton
109
+ type="submit"
110
+ color="neutral"
111
+ variant="outline"
112
+ size="xs"
113
+ icon="i-lucide-plus"
114
+ :aria-label="t('builder.addLanguage', 'Add language, e.g. da')"
115
+ />
116
+ </form>
117
+ <UButton
118
+ color="neutral"
119
+ variant="link"
120
+ size="xs"
121
+ icon="i-lucide-x"
122
+ :label="t('builder.singleLanguage', 'Same text in every language')"
123
+ @click="toggle"
124
+ />
125
+ </div>
126
+ </div>
127
+ </template>
@@ -0,0 +1,19 @@
1
+ import type { Translated } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ placeholder?: string;
4
+ multiline?: boolean;
5
+ /** Write / Preview toggle for multiline text (markdown). */
6
+ preview?: boolean;
7
+ };
8
+ type __VLS_ModelProps = {
9
+ /** Either one string for every language, or a map of locale → text. */
10
+ modelValue?: Translated | undefined;
11
+ };
12
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
13
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
14
+ "update:modelValue": (value: Translated | undefined) => any;
15
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
16
+ "onUpdate:modelValue"?: ((value: Translated | undefined) => any) | undefined;
17
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -0,0 +1,19 @@
1
+ import type { FormField } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ field: FormField;
4
+ /** Admin preview: shows a stub and supplies a dummy token instead of loading the provider. */
5
+ preview?: boolean;
6
+ /** Bump to reset the widget (and clear the token), e.g. after a failed submit. */
7
+ resetKey?: number;
8
+ };
9
+ type __VLS_ModelProps = {
10
+ modelValue?: string | undefined;
11
+ };
12
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
13
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
14
+ "update:modelValue": (value: string | undefined) => any;
15
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
16
+ "onUpdate:modelValue"?: ((value: string | undefined) => any) | undefined;
17
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -0,0 +1,95 @@
1
+ <script setup>
2
+ import { resolveCaptcha } from "../../utils";
3
+ import { captchaDefaultProvider, captchaSiteKey, loadCaptchaScript } from "../composables/useCaptcha";
4
+ const props = defineProps({
5
+ field: { type: Object, required: true },
6
+ preview: { type: Boolean, required: false },
7
+ resetKey: { type: Number, required: false }
8
+ });
9
+ const model = defineModel({ type: null });
10
+ const t = useModuleT("forms");
11
+ const config = computed(() => resolveCaptcha(props.field, captchaDefaultProvider()));
12
+ const siteKey = computed(() => captchaSiteKey(props.field));
13
+ const providerName = computed(() => t(`captcha.providers.${config.value.provider}`, config.value.provider));
14
+ const container = useTemplateRef("container");
15
+ const failed = ref(false);
16
+ let api;
17
+ let widgetId;
18
+ async function renderWidget() {
19
+ if (props.preview || !container.value || !siteKey.value) return;
20
+ failed.value = false;
21
+ try {
22
+ api = await loadCaptchaScript(config.value.provider);
23
+ } catch {
24
+ failed.value = true;
25
+ return;
26
+ }
27
+ if (!container.value) return;
28
+ const theme = config.value.theme && (config.value.theme !== "auto" || config.value.provider === "turnstile") ? config.value.theme : void 0;
29
+ widgetId = api.render(container.value, {
30
+ "sitekey": siteKey.value,
31
+ theme,
32
+ "size": config.value.size,
33
+ "callback": (token) => {
34
+ model.value = token;
35
+ },
36
+ "expired-callback": () => {
37
+ model.value = void 0;
38
+ },
39
+ "error-callback": () => {
40
+ model.value = void 0;
41
+ }
42
+ });
43
+ }
44
+ function resetWidget() {
45
+ model.value = props.preview ? "preview" : void 0;
46
+ if (!props.preview && api && widgetId !== void 0) api.reset(widgetId);
47
+ }
48
+ model.value = props.preview ? "preview" : void 0;
49
+ onMounted(renderWidget);
50
+ onBeforeUnmount(() => {
51
+ if (api && widgetId !== void 0) api.remove?.(widgetId);
52
+ });
53
+ watch(() => props.resetKey, resetWidget);
54
+ watch([() => config.value.provider, siteKey], () => {
55
+ if (api && widgetId !== void 0) api.remove?.(widgetId);
56
+ widgetId = void 0;
57
+ if (container.value) container.value.innerHTML = "";
58
+ model.value = void 0;
59
+ renderWidget();
60
+ });
61
+ </script>
62
+
63
+ <template>
64
+ <div
65
+ v-if="preview"
66
+ data-testid="captcha-stub"
67
+ class="flex items-center gap-3 rounded-md border border-dashed border-default bg-elevated/50 px-3 py-4 text-sm text-muted"
68
+ >
69
+ <UIcon
70
+ name="i-lucide-shield-check"
71
+ class="size-5 shrink-0"
72
+ />
73
+ <span>{{ t("captcha.preview", "Captcha: {provider} \u2014 shown to visitors", { provider: providerName }) }}</span>
74
+ </div>
75
+ <UAlert
76
+ v-else-if="!siteKey"
77
+ color="error"
78
+ variant="subtle"
79
+ icon="i-lucide-shield-alert"
80
+ :title="t('captcha.missingKey', 'The {provider} captcha has no site key configured.', { provider: providerName })"
81
+ />
82
+ <UAlert
83
+ v-else-if="failed"
84
+ color="error"
85
+ variant="subtle"
86
+ icon="i-lucide-shield-alert"
87
+ :title="t('captcha.loadFailed', 'The captcha could not be loaded. Reload the page to try again.')"
88
+ />
89
+ <div
90
+ v-else
91
+ ref="container"
92
+ data-testid="captcha-widget"
93
+ class="min-h-[65px]"
94
+ />
95
+ </template>
@@ -0,0 +1,19 @@
1
+ import type { FormField } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ field: FormField;
4
+ /** Admin preview: shows a stub and supplies a dummy token instead of loading the provider. */
5
+ preview?: boolean;
6
+ /** Bump to reset the widget (and clear the token), e.g. after a failed submit. */
7
+ resetKey?: number;
8
+ };
9
+ type __VLS_ModelProps = {
10
+ modelValue?: string | undefined;
11
+ };
12
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
13
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
14
+ "update:modelValue": (value: string | undefined) => any;
15
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
16
+ "onUpdate:modelValue"?: ((value: string | undefined) => any) | undefined;
17
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Embeds a published form in a page or in Markdown (MDC): `::form-embed{slug="contact"}`.
3
+ * Honors the form's access settings: sign-in prompt, password gate, nothing for API-only/token forms.
4
+ */
5
+ type __VLS_Props = {
6
+ /** Slug of a published form. */
7
+ slug: string;
8
+ /** Show the form's name as a heading above it. */
9
+ title?: boolean;
10
+ /** Overrides the locale of translated form content (defaults to the app's locale). */
11
+ locale?: string;
12
+ /** Extra classes for the wrapper. */
13
+ class?: string;
14
+ };
15
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
16
+ declare const _default: typeof __VLS_export;
17
+ export default _default;
@@ -0,0 +1,113 @@
1
+ <script setup>
2
+ import { embedMode } from "../../utils";
3
+ const props = defineProps({
4
+ slug: { type: String, required: true },
5
+ title: { type: Boolean, required: false, default: false },
6
+ locale: { type: String, required: false, default: void 0 },
7
+ class: { type: String, required: false, default: void 0 }
8
+ });
9
+ const route = useRoute();
10
+ const t = useModuleT("forms");
11
+ const { data: access, status } = useFormAccess(() => props.slug);
12
+ const mode = computed(() => embedMode(access.value));
13
+ const loading = computed(() => !access.value && (status.value === "pending" || status.value === "idle"));
14
+ if (import.meta.dev) {
15
+ watch(mode, (value) => {
16
+ if (value === "hidden") console.warn(`[forms] <FormEmbed slug="${props.slug}"> renders nothing: API-only and token forms have no public UI.`);
17
+ }, { immediate: true });
18
+ }
19
+ const loginPath = computed(() => useAuthPages().login);
20
+ const password = ref("");
21
+ const unlocked = ref("");
22
+ const checking = ref(false);
23
+ const wrong = ref(false);
24
+ async function unlock() {
25
+ checking.value = true;
26
+ wrong.value = false;
27
+ try {
28
+ await $fetch(`/api/forms/${props.slug}`, { headers: { [FORM_PASSWORD_HEADER]: password.value } });
29
+ unlocked.value = password.value;
30
+ } catch {
31
+ wrong.value = true;
32
+ } finally {
33
+ checking.value = false;
34
+ }
35
+ }
36
+ </script>
37
+
38
+ <template>
39
+ <div
40
+ v-if="mode !== 'hidden'"
41
+ :class="['not-prose', props.class]"
42
+ data-form-embed
43
+ >
44
+ <div
45
+ v-if="loading"
46
+ class="space-y-4"
47
+ aria-busy="true"
48
+ >
49
+ <USkeleton class="h-10 w-full" />
50
+ <USkeleton class="h-10 w-full" />
51
+ <USkeleton class="h-24 w-full" />
52
+ <USkeleton class="h-10 w-32" />
53
+ </div>
54
+
55
+ <UAlert
56
+ v-else-if="mode === 'not-found'"
57
+ color="error"
58
+ variant="subtle"
59
+ :title="t('error.notFound', 'This form could not be found.')"
60
+ />
61
+
62
+ <template v-else>
63
+ <h2
64
+ v-if="props.title && access"
65
+ class="mb-4 text-2xl font-bold"
66
+ >
67
+ {{ access.name }}
68
+ </h2>
69
+
70
+ <UAlert
71
+ v-if="mode === 'sign-in'"
72
+ color="neutral"
73
+ variant="subtle"
74
+ icon="i-lucide-lock"
75
+ :title="t('embed.signInTitle', 'Sign in to use this form')"
76
+ :actions="[{ label: t('embed.signIn', 'Sign in'), to: { path: loginPath, query: { redirect: route.fullPath } } }]"
77
+ />
78
+
79
+ <UCard v-else-if="mode === 'password' && !unlocked">
80
+ <form
81
+ class="space-y-4"
82
+ @submit.prevent="unlock"
83
+ >
84
+ <UFormField
85
+ :label="t('access.passwordLabel', 'Password')"
86
+ :description="t('access.passwordHint', 'This form is protected. Enter the password you were given.')"
87
+ :error="wrong ? t('access.passwordWrong', 'Wrong password.') : void 0"
88
+ >
89
+ <UInput
90
+ v-model="password"
91
+ type="password"
92
+ autocomplete="off"
93
+ class="w-full"
94
+ />
95
+ </UFormField>
96
+ <UButton
97
+ type="submit"
98
+ :loading="checking"
99
+ :disabled="!password"
100
+ :label="t('access.unlock', 'Continue')"
101
+ />
102
+ </form>
103
+ </UCard>
104
+
105
+ <FormRenderer
106
+ v-else
107
+ :slug="props.slug"
108
+ :password="unlocked || void 0"
109
+ :locale="props.locale"
110
+ />
111
+ </template>
112
+ </div>
113
+ </template>
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Embeds a published form in a page or in Markdown (MDC): `::form-embed{slug="contact"}`.
3
+ * Honors the form's access settings: sign-in prompt, password gate, nothing for API-only/token forms.
4
+ */
5
+ type __VLS_Props = {
6
+ /** Slug of a published form. */
7
+ slug: string;
8
+ /** Show the form's name as a heading above it. */
9
+ title?: boolean;
10
+ /** Overrides the locale of translated form content (defaults to the app's locale). */
11
+ locale?: string;
12
+ /** Extra classes for the wrapper. */
13
+ class?: string;
14
+ };
15
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
16
+ declare const _default: typeof __VLS_export;
17
+ export default _default;
@@ -2,6 +2,10 @@ import type { FormField } from '../../../types.js';
2
2
  type __VLS_Props = {
3
3
  field: FormField;
4
4
  locale: string;
5
+ /** Admin preview: captcha fields render a stub instead of the provider widget. */
6
+ preview?: boolean;
7
+ /** Bumped by the form after a failed submit so captcha widgets reset. */
8
+ captchaResetKey?: number;
5
9
  };
6
10
  type __VLS_ModelProps = {
7
11
  modelValue?: unknown;
@@ -1,10 +1,14 @@
1
1
  <script setup>
2
- import { renderMarkdown, translate } from "../../utils";
2
+ import { isCountryPhone, renderMarkdown, translate } from "../../utils";
3
3
  const props = defineProps({
4
4
  field: { type: Object, required: true },
5
- locale: { type: String, required: true }
5
+ locale: { type: String, required: true },
6
+ preview: { type: Boolean, required: false },
7
+ captchaResetKey: { type: Number, required: false }
6
8
  });
7
9
  const model = defineModel({ type: null });
10
+ const t = useModuleT("forms");
11
+ const defaultAutocomplete = { email: "email", tel: "tel" };
8
12
  const text = (value) => translate(value, props.locale) || void 0;
9
13
  const items = computed(() => (props.field.options ?? []).map((option) => ({
10
14
  value: option.value,
@@ -23,6 +27,25 @@ const isCheckbox = computed(() => props.field.type === "checkbox");
23
27
  />
24
28
  <!-- eslint-enable vue/no-v-html -->
25
29
 
30
+ <UFormField
31
+ v-else-if="field.type === 'captcha'"
32
+ :name="field.key"
33
+ :label="text(field.label)"
34
+ :description="text(field.description)"
35
+ >
36
+ <ClientOnly>
37
+ <FormCaptcha
38
+ v-model="model"
39
+ :field="field"
40
+ :preview="preview"
41
+ :reset-key="captchaResetKey"
42
+ />
43
+ <template #fallback>
44
+ <div class="h-[65px] rounded-md bg-elevated/50" />
45
+ </template>
46
+ </ClientOnly>
47
+ </UFormField>
48
+
26
49
  <UFormField
27
50
  v-else
28
51
  :name="field.key"
@@ -68,11 +91,20 @@ const isCheckbox = computed(() => props.field.type === "checkbox");
68
91
  :accept="field.validation?.accept?.join(',')"
69
92
  class="w-full"
70
93
  />
94
+ <FormPhoneInput
95
+ v-else-if="isCountryPhone(field)"
96
+ v-model="model"
97
+ :config="field.phone"
98
+ :locale="locale"
99
+ :placeholder="text(field.placeholder)"
100
+ :country-label="t('field.phoneCountry', 'Country')"
101
+ />
71
102
  <UInput
72
103
  v-else
73
104
  v-model="model"
74
105
  :type="field.type === 'date' ? 'date' : field.type === 'tel' ? 'tel' : field.type === 'email' ? 'email' : 'text'"
75
- :autocomplete="field.autocomplete"
106
+ :inputmode="field.type === 'tel' ? 'tel' : void 0"
107
+ :autocomplete="field.autocomplete ?? defaultAutocomplete[field.type]"
76
108
  :placeholder="text(field.placeholder)"
77
109
  class="w-full"
78
110
  />
@@ -2,6 +2,10 @@ import type { FormField } from '../../../types.js';
2
2
  type __VLS_Props = {
3
3
  field: FormField;
4
4
  locale: string;
5
+ /** Admin preview: captcha fields render a stub instead of the provider widget. */
6
+ preview?: boolean;
7
+ /** Bumped by the form after a failed submit so captcha widgets reset. */
8
+ captchaResetKey?: number;
5
9
  };
6
10
  type __VLS_ModelProps = {
7
11
  modelValue?: unknown;
@@ -0,0 +1,20 @@
1
+ import type { PhoneConfig } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ config?: PhoneConfig;
4
+ locale: string;
5
+ placeholder?: string;
6
+ /** Name of the country selector for assistive technology. */
7
+ countryLabel: string;
8
+ };
9
+ type __VLS_ModelProps = {
10
+ /** The single merged value, e.g. `+4512345678`. */
11
+ modelValue?: string | undefined;
12
+ };
13
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
14
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
+ "update:modelValue": (value: string | undefined) => any;
16
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
17
+ "onUpdate:modelValue"?: ((value: string | undefined) => any) | undefined;
18
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;