@teamnovu/nuxt-statamic-formbuilder 0.1.0

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 (40) hide show
  1. package/README.md +13 -0
  2. package/dist/module.d.mts +14 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +83 -0
  5. package/dist/runtime/components/FormBuilderFields.d.vue.ts +34 -0
  6. package/dist/runtime/components/FormBuilderFields.vue +86 -0
  7. package/dist/runtime/components/FormBuilderFields.vue.d.ts +34 -0
  8. package/dist/runtime/components/StatamicFormBuilder.d.vue.ts +26 -0
  9. package/dist/runtime/components/StatamicFormBuilder.vue +54 -0
  10. package/dist/runtime/components/StatamicFormBuilder.vue.d.ts +26 -0
  11. package/dist/runtime/composables/useFieldConditions.d.ts +16 -0
  12. package/dist/runtime/composables/useFieldConditions.js +230 -0
  13. package/dist/runtime/composables/useFormBuilder.d.ts +11 -0
  14. package/dist/runtime/composables/useFormBuilder.js +68 -0
  15. package/dist/runtime/composables/useFormFieldRegistry.d.ts +8 -0
  16. package/dist/runtime/composables/useFormFieldRegistry.js +14 -0
  17. package/dist/runtime/composables/useFormValidation.d.ts +11 -0
  18. package/dist/runtime/composables/useFormValidation.js +166 -0
  19. package/dist/runtime/composables/useTranslatableInput.d.ts +8 -0
  20. package/dist/runtime/composables/useTranslatableInput.js +14 -0
  21. package/dist/runtime/fieldConfigs.d.ts +91 -0
  22. package/dist/runtime/fieldConfigs.js +0 -0
  23. package/dist/runtime/injectionKeys.d.ts +6 -0
  24. package/dist/runtime/injectionKeys.js +1 -0
  25. package/dist/runtime/locales/de.json +29 -0
  26. package/dist/runtime/locales/en.json +29 -0
  27. package/dist/runtime/locales/fr.json +29 -0
  28. package/dist/runtime/styles/form-builder.css +1 -0
  29. package/dist/runtime/types.d.ts +126 -0
  30. package/dist/runtime/types.js +22 -0
  31. package/dist/runtime/utils/graphqlFragment.d.ts +5 -0
  32. package/dist/runtime/utils/graphqlFragment.js +37 -0
  33. package/dist/runtime/utils/removePTags.d.ts +1 -0
  34. package/dist/runtime/utils/removePTags.js +4 -0
  35. package/dist/runtime/utils/submitStatamicForm.d.ts +7 -0
  36. package/dist/runtime/utils/submitStatamicForm.js +64 -0
  37. package/dist/runtime/utils/toFormBuilderConfiguration.d.ts +7 -0
  38. package/dist/runtime/utils/toFormBuilderConfiguration.js +37 -0
  39. package/dist/types.d.mts +5 -0
  40. package/package.json +65 -0
@@ -0,0 +1,68 @@
1
+ import { useRuntimeConfig } from "#imports";
2
+ import { useI18n } from "vue-i18n";
3
+ import { computed, ref, toValue } from "vue";
4
+ import { isDisplayOrSpacerField, isMultiValueField } from "../types.js";
5
+ import { submitStatamicForm } from "../utils/submitStatamicForm.js";
6
+ function flattenFields(form) {
7
+ if ("fields" in form) {
8
+ return form.fields;
9
+ }
10
+ return form.sections.flatMap((section) => section.fields);
11
+ }
12
+ function setInitialState(fields) {
13
+ return toValue(fields).reduce((acc, field) => {
14
+ if (isDisplayOrSpacerField(field)) {
15
+ return acc;
16
+ }
17
+ if (field.type === "input_file_upload") {
18
+ acc[field.handle] = isMultiValueField(field) ? [] : null;
19
+ } else if (isMultiValueField(field)) {
20
+ acc[field.handle] = [];
21
+ } else {
22
+ acc[field.handle] = void 0;
23
+ }
24
+ return acc;
25
+ }, {});
26
+ }
27
+ export function useFormBuilder(formBuilderConfiguration, serverUrlOrOptions) {
28
+ const options = typeof serverUrlOrOptions === "string" ? { serverUrl: serverUrlOrOptions } : serverUrlOrOptions ?? {};
29
+ const runtimeConfig = useRuntimeConfig();
30
+ const { locale } = useI18n();
31
+ const serverUrl = computed(
32
+ () => options.serverUrl || runtimeConfig.public.formBuilder?.serverUrl || runtimeConfig.public.BACKEND_URL || ""
33
+ );
34
+ const submitter = options.submitter ?? submitStatamicForm;
35
+ const requestState = ref("not_submitted");
36
+ const state = ref({});
37
+ const fields = computed(() => flattenFields(formBuilderConfiguration));
38
+ state.value = setInitialState(fields);
39
+ function reset() {
40
+ state.value = setInitialState(fields);
41
+ requestState.value = "not_submitted";
42
+ }
43
+ async function submit(_event, stateRef = state) {
44
+ requestState.value = "loading";
45
+ try {
46
+ const result = await submitter({
47
+ form: formBuilderConfiguration,
48
+ state: stateRef.value,
49
+ serverUrl: serverUrl.value,
50
+ locale: locale.value
51
+ });
52
+ requestState.value = "success";
53
+ return result;
54
+ } catch (error) {
55
+ requestState.value = "error";
56
+ return error?.body ?? error;
57
+ }
58
+ }
59
+ return {
60
+ requestState,
61
+ submit,
62
+ reset,
63
+ state,
64
+ fields,
65
+ serverUrl
66
+ };
67
+ }
68
+ export default useFormBuilder;
@@ -0,0 +1,8 @@
1
+ import type { Component } from 'vue';
2
+ export declare function registerFormField(type: string, component: Component): void;
3
+ export declare function resolveFormField(type: string): Component | undefined;
4
+ export declare function useFormFieldRegistry(): {
5
+ registerFormField: typeof registerFormField;
6
+ resolveFormField: typeof resolveFormField;
7
+ registeredTypes: () => string[];
8
+ };
@@ -0,0 +1,14 @@
1
+ const fieldRegistry = /* @__PURE__ */ new Map();
2
+ export function registerFormField(type, component) {
3
+ fieldRegistry.set(type, component);
4
+ }
5
+ export function resolveFormField(type) {
6
+ return fieldRegistry.get(type);
7
+ }
8
+ export function useFormFieldRegistry() {
9
+ return {
10
+ registerFormField,
11
+ resolveFormField,
12
+ registeredTypes: () => [...fieldRegistry.keys()]
13
+ };
14
+ }
@@ -0,0 +1,11 @@
1
+ import * as v from 'valibot';
2
+ import type { MaybeRefOrGetter, Ref } from 'vue';
3
+ import type { FormBuilderConfiguration, FormBuilderField, InputRules } from '../types.js';
4
+ export declare function useFormValidation(fieldsInput: MaybeRefOrGetter<readonly FormBuilderField[]>, formRules: FormBuilderConfiguration['rules'], _state?: Ref<Record<string, unknown>>): {
5
+ schema: import("vue").ComputedRef<v.ObjectSchema<Record<string, v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>>, undefined>>;
6
+ validationErrors: Ref<Record<string, string>, Record<string, string>>;
7
+ parseRules: (rulesInput?: string | string[]) => InputRules;
8
+ getRulesForField: (field: FormBuilderField) => InputRules;
9
+ buildFieldSchema: (field: FormBuilderField) => v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>;
10
+ };
11
+ export default useFormValidation;
@@ -0,0 +1,166 @@
1
+ import * as v from "valibot";
2
+ import { useI18n } from "vue-i18n";
3
+ import { computed, ref, toValue } from "vue";
4
+ import { isDisplayOrSpacerField } from "../types.js";
5
+ export function useFormValidation(fieldsInput, formRules, _state) {
6
+ const { t } = useI18n();
7
+ const fields = computed(() => toValue(fieldsInput) ?? []);
8
+ const validationErrors = ref({});
9
+ const parseRules = (rulesInput) => {
10
+ if (!rulesInput) {
11
+ return {};
12
+ }
13
+ const source = Array.isArray(rulesInput) ? rulesInput.join("|") : rulesInput;
14
+ if (typeof source !== "string") {
15
+ return {};
16
+ }
17
+ return source.split("|").reduce((parsedRules, rule) => {
18
+ const [name, value] = rule.split(":");
19
+ if (name === "required") {
20
+ return { ...parsedRules, required: true };
21
+ }
22
+ if (name === "min" && value) {
23
+ return { ...parsedRules, min: Number(value) };
24
+ }
25
+ if (name === "max" && value) {
26
+ return { ...parsedRules, max: Number(value) };
27
+ }
28
+ if (name === "maxlength" && value) {
29
+ return { ...parsedRules, maxlength: Number(value) };
30
+ }
31
+ if (name === "pattern" && value) {
32
+ return { ...parsedRules, pattern: value };
33
+ }
34
+ if (name === "type" && value) {
35
+ return { ...parsedRules, type: value };
36
+ }
37
+ return parsedRules;
38
+ }, {});
39
+ };
40
+ const getRulesForField = (field) => parseRules(formRules?.[field.handle]);
41
+ const normalizeFieldType = (field, rules) => {
42
+ const explicit = rules.type?.toLowerCase();
43
+ if (explicit === "array" || explicit === "boolean" || explicit === "number" || explicit === "file" || explicit === "string") {
44
+ return explicit;
45
+ }
46
+ if (field.type === "input_slider" || field.type === "input_number" || explicit?.includes("numeric")) {
47
+ return "number";
48
+ }
49
+ if (field.type === "input_checkboxes" || field.type === "input_radio_buttons" || field.type === "input_select" && field.config.multiple === true) {
50
+ return "array";
51
+ }
52
+ if (field.type === "input_switch" || explicit === "boolean") {
53
+ return "boolean";
54
+ }
55
+ if (field.type === "input_file_upload") {
56
+ return "file";
57
+ }
58
+ return "string";
59
+ };
60
+ const buildFieldSchema = (field) => {
61
+ const rules = getRulesForField(field);
62
+ const normalizedType = normalizeFieldType(field, rules);
63
+ const isRequired = rules.required === true;
64
+ const buildStringSchema = () => {
65
+ let stringSchema2 = v.union([
66
+ v.pipe(v.string()),
67
+ v.pipe(v.null(), v.transform(() => "")),
68
+ v.pipe(v.undefined(), v.transform(() => ""))
69
+ ]);
70
+ if (isRequired) {
71
+ stringSchema2 = v.pipe(stringSchema2, v.minLength(1, t("form.user_feedback.field_validation.required")));
72
+ }
73
+ const pattern = rules.pattern;
74
+ if (pattern) {
75
+ stringSchema2 = v.pipe(stringSchema2, v.regex(new RegExp(pattern), t("form.user_feedback.field_validation.invalid_format")));
76
+ }
77
+ if (rules.maxlength) {
78
+ stringSchema2 = v.pipe(stringSchema2, v.maxLength(rules.maxlength, t("form.user_feedback.field_validation.maxlength_characters", { n: rules.maxlength })));
79
+ }
80
+ if (rules.min) {
81
+ stringSchema2 = v.pipe(stringSchema2, v.minLength(rules.min, t("form.user_feedback.field_validation.minlength_characters", { n: rules.min })));
82
+ }
83
+ if (field.type === "input_email") {
84
+ stringSchema2 = v.pipe(stringSchema2, v.email(t("form.user_feedback.field_validation.email")));
85
+ }
86
+ if (field.type === "input_phone") {
87
+ stringSchema2 = v.pipe(stringSchema2, v.regex(/^\(?\+?\d{1,4}\)?\s*[\d\s]+$/, t("form.user_feedback.field_validation.phone")));
88
+ }
89
+ return stringSchema2;
90
+ };
91
+ const stringSchema = () => isRequired ? buildStringSchema() : v.optional(buildStringSchema());
92
+ const numberSchema = () => {
93
+ const coerceToNumber = v.union([
94
+ v.pipe(v.number()),
95
+ v.pipe(v.string(), v.transform((value) => Number(value))),
96
+ v.pipe(v.null(), v.transform(() => 0)),
97
+ v.pipe(v.undefined(), v.transform(() => 0))
98
+ ]);
99
+ let base = coerceToNumber;
100
+ if (typeof rules.min === "number") {
101
+ base = v.pipe(base, v.minValue(rules.min, t("form.user_feedback.field_validation.min_number", { n: rules.min })));
102
+ }
103
+ if (typeof rules.max === "number") {
104
+ base = v.pipe(base, v.maxValue(rules.max, t("form.user_feedback.field_validation.max_number", { n: rules.max })));
105
+ }
106
+ return isRequired ? base : v.optional(base);
107
+ };
108
+ const arraySchema = () => {
109
+ const itemSchema = v.union([v.string(), v.number()]);
110
+ let base = v.array(itemSchema);
111
+ if (isRequired) {
112
+ base = v.pipe(base, v.minLength(1, t("form.user_feedback.field_validation.required")));
113
+ }
114
+ if (typeof rules.min === "number") {
115
+ base = v.pipe(base, v.minLength(rules.min, t("form.user_feedback.field_validation.min_items", { n: rules.min })));
116
+ }
117
+ if (typeof rules.max === "number") {
118
+ base = v.pipe(base, v.maxLength(rules.max, t("form.user_feedback.field_validation.max_items", { n: rules.max })));
119
+ }
120
+ return isRequired ? base : v.optional(base);
121
+ };
122
+ if (normalizedType === "number") {
123
+ return numberSchema();
124
+ }
125
+ if (normalizedType === "array") {
126
+ return arraySchema();
127
+ }
128
+ if (normalizedType === "boolean") {
129
+ const base = v.boolean();
130
+ return isRequired ? base : v.optional(base);
131
+ }
132
+ if (normalizedType === "file" && field.type === "input_file_upload") {
133
+ const maxFilesizeKb = field.config.max_filesize;
134
+ const maxFiles = field.config.max_files;
135
+ const singleFile = maxFilesizeKb ? v.pipe(v.instance(File), v.check((f) => f.size <= maxFilesizeKb * 1024, t("form.user_feedback.field_validation.max_filesize", { n: maxFilesizeKb }))) : v.instance(File);
136
+ let multipleFiles = v.array(v.instance(File));
137
+ if (maxFiles) {
138
+ multipleFiles = v.pipe(multipleFiles, v.check((files) => files.length <= maxFiles, t("form.user_feedback.field_validation.max_files", { n: maxFiles })));
139
+ }
140
+ if (maxFilesizeKb) {
141
+ multipleFiles = v.pipe(multipleFiles, v.check((files) => files.every((f) => f.size <= maxFilesizeKb * 1024), t("form.user_feedback.field_validation.max_filesize", { n: maxFilesizeKb })));
142
+ }
143
+ const base = v.union([singleFile, multipleFiles]);
144
+ return isRequired ? base : v.nullish(base);
145
+ }
146
+ return stringSchema();
147
+ };
148
+ const schema = computed(() => {
149
+ const schemaEntries = {};
150
+ fields.value.forEach((field) => {
151
+ if (isDisplayOrSpacerField(field)) {
152
+ return;
153
+ }
154
+ schemaEntries[field.handle] = buildFieldSchema(field);
155
+ });
156
+ return v.object(schemaEntries);
157
+ });
158
+ return {
159
+ schema,
160
+ validationErrors,
161
+ parseRules,
162
+ getRulesForField,
163
+ buildFieldSchema
164
+ };
165
+ }
166
+ export default useFormValidation;
@@ -0,0 +1,8 @@
1
+ import type { TranslatableInput } from '../types.js';
2
+ export type ResolveTranslatableInput = (input: TranslatableInput | string | undefined | null) => string | undefined;
3
+ /**
4
+ * Call once at the top of `<script setup>`. Returns a resolver safe to use
5
+ * in templates and computeds (useI18n must not run during render).
6
+ */
7
+ export declare function useTranslatableInput(): ResolveTranslatableInput;
8
+ export default useTranslatableInput;
@@ -0,0 +1,14 @@
1
+ import { useI18n } from "vue-i18n";
2
+ export function useTranslatableInput() {
3
+ const { locale } = useI18n();
4
+ return (input) => {
5
+ if (typeof input === "string") {
6
+ return input;
7
+ }
8
+ if (Array.isArray(input)) {
9
+ return input.find((element) => element.handle === locale.value)?.value ?? input.find((element) => element.handle === "de")?.value ?? void 0;
10
+ }
11
+ return void 0;
12
+ };
13
+ }
14
+ export default useTranslatableInput;
@@ -0,0 +1,91 @@
1
+ export type ValidationTrigger = 'blur' | 'submit';
2
+ export type TranslatableInput = {
3
+ handle: string;
4
+ value: string | null;
5
+ }[];
6
+ /** Shared CMS field config keys present on most input fieldtypes. */
7
+ export type BaseFieldConfig = {
8
+ label?: string | TranslatableInput;
9
+ hint?: string | TranslatableInput;
10
+ help?: string | TranslatableInput;
11
+ inline?: boolean;
12
+ validateOn?: ValidationTrigger[];
13
+ /** When true, UI layers may float the label over the control. */
14
+ floating_label?: boolean;
15
+ };
16
+ export type FormFieldOption = {
17
+ id?: string;
18
+ key: string;
19
+ text: TranslatableInput;
20
+ };
21
+ export type InputTextFieldConfig = BaseFieldConfig & {
22
+ placeholder?: string | TranslatableInput;
23
+ character_limit?: number;
24
+ };
25
+ export type InputTextareaFieldConfig = BaseFieldConfig & {
26
+ placeholder?: string | TranslatableInput;
27
+ character_limit?: number;
28
+ };
29
+ export type InputEmailFieldConfig = BaseFieldConfig & {
30
+ placeholder?: string | TranslatableInput;
31
+ };
32
+ export type InputPhoneFieldConfig = BaseFieldConfig & {
33
+ placeholder?: string | TranslatableInput;
34
+ country_codes?: Record<string, string>;
35
+ show_country_code_selector?: boolean;
36
+ show_search?: boolean;
37
+ default_country_code?: string;
38
+ };
39
+ export type InputNumberFieldConfig = BaseFieldConfig & {
40
+ min?: number;
41
+ max?: number;
42
+ step?: number;
43
+ placeholder?: string | TranslatableInput;
44
+ default?: number;
45
+ };
46
+ export type InputSelectFieldConfig = BaseFieldConfig & {
47
+ placeholder?: string | TranslatableInput;
48
+ options?: FormFieldOption[];
49
+ show_search?: boolean;
50
+ multiple?: boolean;
51
+ };
52
+ export type InputDateFieldConfig = BaseFieldConfig & {
53
+ earliest_date?: string;
54
+ latest_date?: string;
55
+ };
56
+ export type InputSliderFieldConfig = BaseFieldConfig & {
57
+ min?: number;
58
+ max?: number;
59
+ step?: number;
60
+ default?: number;
61
+ };
62
+ export type InputCheckboxesFieldConfig = BaseFieldConfig & {
63
+ options?: FormFieldOption[];
64
+ orientation?: 'horizontal' | 'vertical';
65
+ variant?: 'card' | 'list' | 'table';
66
+ indicator?: 'start' | 'end' | 'hidden';
67
+ };
68
+ export type InputRadioButtonsFieldConfig = BaseFieldConfig & {
69
+ options?: FormFieldOption[];
70
+ orientation?: 'horizontal' | 'vertical';
71
+ variant?: 'card' | 'list' | 'table';
72
+ indicator?: 'start' | 'end' | 'hidden';
73
+ };
74
+ export type InputSwitchFieldConfig = BaseFieldConfig & {
75
+ default?: boolean;
76
+ label_deactivated?: string | TranslatableInput;
77
+ label_activated?: string | TranslatableInput;
78
+ };
79
+ export type InputFileUploadFieldConfig = BaseFieldConfig & {
80
+ description?: string | TranslatableInput;
81
+ icon_label?: string | TranslatableInput;
82
+ /** Max file size in KB */
83
+ max_filesize?: number;
84
+ max_files?: number;
85
+ /** MIME types or file extensions keyed by label */
86
+ allowed_mimes?: Record<string, string>;
87
+ };
88
+ export type DisplayTextFieldConfig = {
89
+ title?: TranslatableInput;
90
+ text?: TranslatableInput;
91
+ };
File without changes
@@ -0,0 +1,6 @@
1
+ import type { InjectionKey, Ref } from 'vue';
2
+ /**
3
+ * Shared form field state provided by FormBuilder / StatamicFormBuilder hosts.
4
+ * Custom UI layers should provide and inject via this key instead of a string.
5
+ */
6
+ export declare const formBuilderStateKey: InjectionKey<Ref<Record<string, unknown>>>;
@@ -0,0 +1 @@
1
+ export const formBuilderStateKey = Symbol("formBuilderState");
@@ -0,0 +1,29 @@
1
+ {
2
+ "form": {
3
+ "user_feedback": {
4
+ "error": {
5
+ "title": "Fehler"
6
+ },
7
+ "field_invalid": "Keine Felder sind ausgefüllt. | Das Feld {fields} ist ungültig. | Die {count} Felder {fields} sind ungültig.",
8
+ "field_validation": {
9
+ "email": "Ungültige E-Mail-Adresse",
10
+ "invalid_format": "Ungültiges Format",
11
+ "max_items": "Maximal {n} Elemente",
12
+ "max_number": "Darf maximal {n} sein",
13
+ "max_value": "Darf maximal {n} sein",
14
+ "maxlength_characters": "Darf maximal {n} Zeichen lang sein",
15
+ "min_items": "Mindestens {n} Elemente",
16
+ "min_number": "Darf minimal {n} sein",
17
+ "min_value": "Darf minimal {n} sein",
18
+ "minlength_characters": "Muss mindestens {n} Zeichen lang sein",
19
+ "required": "Dieses Feld ist erforderlich",
20
+ "phone": "Ungültige Telefonnummer",
21
+ "max_filesize": "Die Dateigröße darf {n} KB nicht überschreiten",
22
+ "max_files": "Es können maximal {n} Datei hochgeladen werden | Es können maximal {n} Dateien hochgeladen werden"
23
+ },
24
+ "success": {
25
+ "title": "Erfolgreich"
26
+ }
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "form": {
3
+ "user_feedback": {
4
+ "error": {
5
+ "title": "Error"
6
+ },
7
+ "field_invalid": "No fields are filled. | The field {fields} is invalid. | The {count} fields {fields} are invalid.",
8
+ "field_validation": {
9
+ "email": "Invalid email address",
10
+ "invalid_format": "Invalid format",
11
+ "max_items": "Must have at most {n} items",
12
+ "max_number": "Must be at most {n}",
13
+ "max_value": "Must be less than or equal to {n}",
14
+ "maxlength_characters": "Must be at most {n} characters",
15
+ "min_items": "Must have at least {n} items",
16
+ "min_number": "Must be at least {n}",
17
+ "min_value": "Must be greater than or equal to {n}",
18
+ "minlength_characters": "Must be at least {n} characters",
19
+ "required": "This field is required",
20
+ "phone": "Invalid phone number",
21
+ "max_filesize": "File size must not exceed {n} KB",
22
+ "max_files": "At most {n} file can be uploaded | At most {n} files can be uploaded"
23
+ },
24
+ "success": {
25
+ "title": "Successful"
26
+ }
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "form": {
3
+ "user_feedback": {
4
+ "error": {
5
+ "title": "Erreur"
6
+ },
7
+ "field_invalid": "Aucun champ n'est rempli. | Le champ {fields} est invalide. | Les {count} champs {fields} sont invalides.",
8
+ "field_validation": {
9
+ "email": "Adresse e-mail invalide",
10
+ "invalid_format": "Format invalide",
11
+ "max_items": "Maximum {n} éléments",
12
+ "max_number": "Doit être au maximum {n}",
13
+ "max_value": "Doit être au maximum {n}",
14
+ "maxlength_characters": "Doit contenir au maximum {n} caractères",
15
+ "min_items": "Minimum {n} éléments",
16
+ "min_number": "Doit être au minimum {n}",
17
+ "min_value": "Doit être au minimum {n}",
18
+ "minlength_characters": "Doit contenir au minimum {n} caractères",
19
+ "required": "Ce champ est obligatoire",
20
+ "phone": "Numéro de téléphone invalide",
21
+ "max_filesize": "La taille du fichier ne doit pas dépasser {n} Ko",
22
+ "max_files": "Au maximum {n} fichier peut être téléchargé | Au maximum {n} fichiers peuvent être téléchargés"
23
+ },
24
+ "success": {
25
+ "title": "Succès"
26
+ }
27
+ }
28
+ }
29
+ }
@@ -0,0 +1 @@
1
+ @source "../**/*.{vue,js,ts}";
@@ -0,0 +1,126 @@
1
+ export type { ValidationTrigger, TranslatableInput, BaseFieldConfig, FormFieldOption, InputTextFieldConfig, InputTextareaFieldConfig, InputEmailFieldConfig, InputPhoneFieldConfig, InputNumberFieldConfig, InputSelectFieldConfig, InputDateFieldConfig, InputSliderFieldConfig, InputCheckboxesFieldConfig, InputRadioButtonsFieldConfig, InputSwitchFieldConfig, InputFileUploadFieldConfig, DisplayTextFieldConfig, } from './fieldConfigs.js';
2
+ import type { BaseFieldConfig, DisplayTextFieldConfig, InputCheckboxesFieldConfig, InputDateFieldConfig, InputEmailFieldConfig, InputFileUploadFieldConfig, InputNumberFieldConfig, InputPhoneFieldConfig, InputRadioButtonsFieldConfig, InputSelectFieldConfig, InputSliderFieldConfig, InputSwitchFieldConfig, InputTextFieldConfig, InputTextareaFieldConfig } from './fieldConfigs.js';
3
+ /** Shared field metadata from Statamic (all field types). */
4
+ export type FormBuilderFieldBase = {
5
+ handle: string;
6
+ display: string | null;
7
+ width: number;
8
+ instructions: string | null;
9
+ /** UI-layer specific style hints (typed by the UI package). */
10
+ styleConfig?: Record<string, unknown>;
11
+ if?: Record<string, string> | null;
12
+ unless?: Record<string, string> | null;
13
+ prefix?: string;
14
+ };
15
+ export type FormBuilderField = (FormBuilderFieldBase & {
16
+ type: 'input_text';
17
+ config: InputTextFieldConfig;
18
+ }) | (FormBuilderFieldBase & {
19
+ type: 'input_textarea';
20
+ config: InputTextareaFieldConfig;
21
+ }) | (FormBuilderFieldBase & {
22
+ type: 'input_email';
23
+ config: InputEmailFieldConfig;
24
+ }) | (FormBuilderFieldBase & {
25
+ type: 'input_phone';
26
+ config: InputPhoneFieldConfig;
27
+ }) | (FormBuilderFieldBase & {
28
+ type: 'input_number';
29
+ config: InputNumberFieldConfig;
30
+ }) | (FormBuilderFieldBase & {
31
+ type: 'input_select';
32
+ config: InputSelectFieldConfig;
33
+ }) | (FormBuilderFieldBase & {
34
+ type: 'input_date';
35
+ config: InputDateFieldConfig;
36
+ }) | (FormBuilderFieldBase & {
37
+ type: 'input_daterange';
38
+ config: InputDateFieldConfig;
39
+ }) | (FormBuilderFieldBase & {
40
+ type: 'input_slider';
41
+ config: InputSliderFieldConfig;
42
+ }) | (FormBuilderFieldBase & {
43
+ type: 'input_checkboxes';
44
+ config: InputCheckboxesFieldConfig;
45
+ }) | (FormBuilderFieldBase & {
46
+ type: 'input_radio_buttons';
47
+ config: InputRadioButtonsFieldConfig;
48
+ }) | (FormBuilderFieldBase & {
49
+ type: 'input_switch';
50
+ config: InputSwitchFieldConfig;
51
+ }) | (FormBuilderFieldBase & {
52
+ type: 'input_file_upload';
53
+ config: InputFileUploadFieldConfig;
54
+ }) | (FormBuilderFieldBase & {
55
+ type: 'display_text';
56
+ config: DisplayTextFieldConfig;
57
+ }) | (FormBuilderFieldBase & {
58
+ type: 'spacer';
59
+ config: BaseFieldConfig;
60
+ });
61
+ export type FormBuilderFieldType = FormBuilderField['type'];
62
+ export type InputFormBuilderField = Exclude<FormBuilderField, {
63
+ type: 'display_text' | 'spacer';
64
+ }>;
65
+ export declare function isDisplayOrSpacerField(field: FormBuilderField): field is Extract<FormBuilderField, {
66
+ type: 'display_text' | 'spacer';
67
+ }>;
68
+ export declare function isInputField(field: FormBuilderField): field is InputFormBuilderField;
69
+ export declare function isFileUploadField(field: FormBuilderField): field is Extract<FormBuilderField, {
70
+ type: 'input_file_upload';
71
+ }>;
72
+ export declare function isMultiValueField(field: FormBuilderField): boolean;
73
+ export type InputRules = {
74
+ required?: boolean;
75
+ min?: number;
76
+ max?: number;
77
+ maxlength?: number;
78
+ pattern?: string;
79
+ type?: string;
80
+ value?: string;
81
+ };
82
+ export type DefaultInputField = {
83
+ name: string;
84
+ id: string;
85
+ ariaLabel: string;
86
+ rules: InputRules;
87
+ disabled?: boolean;
88
+ autofocus?: boolean;
89
+ onBlur?: (event: FocusEvent) => void;
90
+ onChange?: (event: Event) => void;
91
+ };
92
+ /** @deprecated Use DefaultInputField */
93
+ export type defaultInputField = DefaultInputField;
94
+ export type FormBuilderConfiguration = {
95
+ handle: string;
96
+ honeypot: string | null;
97
+ rules: {
98
+ [key: string]: string;
99
+ };
100
+ } & ({
101
+ sections: Array<{
102
+ display: string | null;
103
+ instructions: string | null;
104
+ fields: Array<FormBuilderField>;
105
+ }>;
106
+ } | {
107
+ fields: Array<FormBuilderField>;
108
+ });
109
+ export type FormBuilderTexts = {
110
+ send_button_text: string;
111
+ message_loading: string;
112
+ message_success: string;
113
+ message_error: string;
114
+ };
115
+ export type FormBuilderRequestState = 'not_submitted' | 'loading' | 'success' | 'error';
116
+ export type FormBuilderSubmitter = (args: {
117
+ form: FormBuilderConfiguration;
118
+ state: Record<string, unknown>;
119
+ serverUrl: string;
120
+ locale: string;
121
+ }) => Promise<unknown>;
122
+ export type UseFormBuilderOptions = {
123
+ serverUrl?: string;
124
+ submitter?: FormBuilderSubmitter;
125
+ };
126
+ export { formBuilderStateKey } from './injectionKeys.js';
@@ -0,0 +1,22 @@
1
+ export function isDisplayOrSpacerField(field) {
2
+ return field.type === "display_text" || field.type === "spacer";
3
+ }
4
+ export function isInputField(field) {
5
+ return !isDisplayOrSpacerField(field);
6
+ }
7
+ export function isFileUploadField(field) {
8
+ return field.type === "input_file_upload";
9
+ }
10
+ export function isMultiValueField(field) {
11
+ if (field.type === "input_checkboxes" || field.type === "input_radio_buttons") {
12
+ return true;
13
+ }
14
+ if (field.type === "input_select") {
15
+ return field.config.multiple === true;
16
+ }
17
+ if (field.type === "input_file_upload") {
18
+ return (field.config.max_files ?? 1) > 1;
19
+ }
20
+ return false;
21
+ }
22
+ export { formBuilderStateKey } from "./injectionKeys.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * GraphQL fragment for the Statamic form builder page component.
3
+ * Copy into your app's GraphQL documents / codegen sources.
4
+ */
5
+ export declare const COMPONENT_FORM_BUILDER_FRAGMENT = "\n fragment ComponentFormBuilder on Fieldset_ComponentFormBuilder {\n __typename\n id\n type\n send_button_text\n form {\n __typename\n handle\n honeypot\n rules\n title\n sections {\n __typename\n display\n instructions\n fields {\n __typename\n config\n display\n handle\n instructions\n type\n width\n if\n unless\n }\n }\n }\n message_loading\n message_success\n message_error\n }\n";