@evanschleret/formforgeclient 1.2.4 → 2.0.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 (83) hide show
  1. package/README.md +10 -0
  2. package/dist/module.cjs +1 -0
  3. package/dist/module.d.cts +1 -0
  4. package/dist/module.d.mts +1 -0
  5. package/dist/module.d.ts +1 -0
  6. package/dist/module.json +1 -1
  7. package/dist/module.mjs +1 -0
  8. package/dist/runtime/api/client.js +4 -2
  9. package/dist/runtime/api/request.d.ts +1 -0
  10. package/dist/runtime/api/schema.js +4 -4
  11. package/dist/runtime/assets/formforge.css +1 -0
  12. package/dist/runtime/composables/index.d.ts +1 -1
  13. package/dist/runtime/composables/useFormForgeBuilder.d.ts +24 -2
  14. package/dist/runtime/composables/useFormForgeBuilder.js +299 -43
  15. package/dist/runtime/composables/useFormForgeForm.js +15 -5
  16. package/dist/runtime/composables/useFormForgeI18n.d.ts +245 -19
  17. package/dist/runtime/composables/useFormForgeI18n.js +245 -19
  18. package/dist/runtime/composables/useFormForgeSubmit.js +31 -9
  19. package/dist/runtime/index.d.ts +1 -0
  20. package/dist/runtime/renderers/default/FormForgeBuilder.d.vue.ts +21 -2
  21. package/dist/runtime/renderers/default/FormForgeBuilder.vue +689 -738
  22. package/dist/runtime/renderers/default/FormForgeBuilder.vue.d.ts +21 -2
  23. package/dist/runtime/renderers/default/FormForgeBuilderBlockSettingsModal.d.vue.ts +17 -0
  24. package/dist/runtime/renderers/default/FormForgeBuilderBlockSettingsModal.vue +32 -0
  25. package/dist/runtime/renderers/default/FormForgeBuilderBlockSettingsModal.vue.d.ts +17 -0
  26. package/dist/runtime/renderers/default/FormForgeRenderer.d.vue.ts +3 -4
  27. package/dist/runtime/renderers/default/FormForgeRenderer.vue +344 -294
  28. package/dist/runtime/renderers/default/FormForgeRenderer.vue.d.ts +3 -4
  29. package/dist/runtime/renderers/default/FormForgeRendererField.d.vue.ts +22 -0
  30. package/dist/runtime/renderers/default/FormForgeRendererField.vue +237 -0
  31. package/dist/runtime/renderers/default/FormForgeRendererField.vue.d.ts +22 -0
  32. package/dist/runtime/renderers/default/FormForgeRendererPage.d.vue.ts +18 -0
  33. package/dist/runtime/renderers/default/FormForgeRendererPage.vue +31 -0
  34. package/dist/runtime/renderers/default/FormForgeRendererPage.vue.d.ts +18 -0
  35. package/dist/runtime/renderers/default/FormForgeResponse.vue +4 -3
  36. package/dist/runtime/renderers/default/builder/FormForgeBuilderAddressFieldsCard.d.vue.ts +11 -0
  37. package/dist/runtime/renderers/default/builder/FormForgeBuilderAddressFieldsCard.vue +118 -0
  38. package/dist/runtime/renderers/default/builder/FormForgeBuilderAddressFieldsCard.vue.d.ts +11 -0
  39. package/dist/runtime/renderers/default/builder/FormForgeBuilderBlockCard.d.vue.ts +46 -0
  40. package/dist/runtime/renderers/default/builder/FormForgeBuilderBlockCard.vue +205 -0
  41. package/dist/runtime/renderers/default/builder/FormForgeBuilderBlockCard.vue.d.ts +46 -0
  42. package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceDisplayField.d.vue.ts +11 -0
  43. package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceDisplayField.vue +37 -0
  44. package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceDisplayField.vue.d.ts +11 -0
  45. package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceOptionsField.d.vue.ts +11 -0
  46. package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceOptionsField.vue +195 -0
  47. package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceOptionsField.vue.d.ts +11 -0
  48. package/dist/runtime/renderers/default/builder/FormForgeBuilderDescriptionField.d.vue.ts +14 -0
  49. package/dist/runtime/renderers/default/builder/FormForgeBuilderDescriptionField.vue +91 -0
  50. package/dist/runtime/renderers/default/builder/FormForgeBuilderDescriptionField.vue.d.ts +14 -0
  51. package/dist/runtime/renderers/default/builder/FormForgeBuilderLogicPanel.d.vue.ts +13 -0
  52. package/dist/runtime/renderers/default/builder/FormForgeBuilderLogicPanel.vue +387 -0
  53. package/dist/runtime/renderers/default/builder/FormForgeBuilderLogicPanel.vue.d.ts +13 -0
  54. package/dist/runtime/renderers/default/builder/FormForgeBuilderQuestionRow.d.vue.ts +44 -0
  55. package/dist/runtime/renderers/default/builder/FormForgeBuilderQuestionRow.vue +328 -0
  56. package/dist/runtime/renderers/default/builder/FormForgeBuilderQuestionRow.vue.d.ts +44 -0
  57. package/dist/runtime/renderers/default/builder/FormForgeBuilderTemporalModeField.d.vue.ts +11 -0
  58. package/dist/runtime/renderers/default/builder/FormForgeBuilderTemporalModeField.vue +47 -0
  59. package/dist/runtime/renderers/default/builder/FormForgeBuilderTemporalModeField.vue.d.ts +11 -0
  60. package/dist/runtime/renderers/default/builder/FormForgeBuilderValidationRulesSection.d.vue.ts +14 -0
  61. package/dist/runtime/renderers/default/builder/FormForgeBuilderValidationRulesSection.vue +595 -0
  62. package/dist/runtime/renderers/default/builder/FormForgeBuilderValidationRulesSection.vue.d.ts +14 -0
  63. package/dist/runtime/renderers/default/builder/builderFieldHelpers.d.ts +3 -0
  64. package/dist/runtime/renderers/default/builder/builderFieldHelpers.js +4 -0
  65. package/dist/runtime/types/index.d.ts +1 -1
  66. package/dist/runtime/types/management.d.ts +12 -0
  67. package/dist/runtime/types/schema.d.ts +72 -4
  68. package/dist/runtime/utils/defaults.d.ts +7 -0
  69. package/dist/runtime/utils/defaults.js +86 -0
  70. package/dist/runtime/utils/page-logic.d.ts +24 -0
  71. package/dist/runtime/utils/page-logic.js +351 -0
  72. package/dist/runtime/utils/rich-text.d.ts +3 -0
  73. package/dist/runtime/utils/rich-text.js +72 -0
  74. package/dist/runtime/utils/schema.d.ts +1 -1
  75. package/dist/runtime/utils/schema.js +70 -16
  76. package/dist/runtime/utils/temporal.d.ts +10 -0
  77. package/dist/runtime/utils/temporal.js +28 -0
  78. package/dist/runtime/utils/validation.d.ts +5 -0
  79. package/dist/runtime/utils/validation.js +36 -0
  80. package/dist/runtime/validation/zod.d.ts +5 -2
  81. package/dist/runtime/validation/zod.js +563 -54
  82. package/dist/types.d.mts +2 -0
  83. package/package.json +18 -14
@@ -1,6 +1,15 @@
1
1
  import type { FormForgeJsonObject, FormForgeJsonValue } from './json.js';
2
2
  import type { FormForgeCategory } from './category.js';
3
- export type FormForgeFieldType = 'text' | 'textarea' | 'email' | 'number' | 'select' | 'select_menu' | 'radio' | 'checkbox' | 'checkbox_group' | 'switch' | 'date' | 'time' | 'datetime' | 'date_range' | 'datetime_range' | 'file';
3
+ export type FormForgeFieldType = 'text' | 'textarea' | 'email' | 'number' | 'select' | 'select_menu' | 'radio' | 'checkbox' | 'consent' | 'checkbox_group' | 'switch' | 'temporal' | 'date' | 'time' | 'file' | 'address';
4
+ export type FormForgeTemporalMode = 'date' | 'time';
5
+ export type FormForgeTemporalHourCycle = 12 | 24;
6
+ export type FormForgeAddressFieldKey = 'line1' | 'line2' | 'city' | 'state' | 'zip' | 'country';
7
+ export interface FormForgeAddressFieldSchema {
8
+ key: FormForgeAddressFieldKey;
9
+ label: string;
10
+ visible: boolean;
11
+ required: boolean;
12
+ }
4
13
  export type FormForgeOptionValue = string | number | boolean | null;
5
14
  export interface FormForgeFieldOptionObject {
6
15
  label?: string;
@@ -35,19 +44,43 @@ export interface FormForgeFieldSchemaBase<TType extends FormForgeFieldType> {
35
44
  disabled?: boolean;
36
45
  readonly?: boolean;
37
46
  options?: FormForgeFieldOption[];
47
+ display?: 'list' | 'menu';
48
+ temporal_mode?: FormForgeTemporalMode;
49
+ hour_cycle?: FormForgeTemporalHourCycle;
50
+ consent_label?: string;
51
+ address_fields?: FormForgeAddressFieldSchema[];
38
52
  }
39
53
  export type FormForgeTextLikeFieldSchema = FormForgeFieldSchemaBase<'text' | 'textarea' | 'email'>;
40
54
  export type FormForgeNumberFieldSchema = FormForgeFieldSchemaBase<'number'>;
41
55
  export type FormForgeSelectLikeFieldSchema = FormForgeFieldSchemaBase<'select' | 'select_menu' | 'radio' | 'checkbox_group'>;
42
- export type FormForgeBooleanFieldSchema = FormForgeFieldSchemaBase<'checkbox' | 'switch'>;
43
- export type FormForgeDateLikeFieldSchema = FormForgeFieldSchemaBase<'date' | 'time' | 'datetime' | 'date_range' | 'datetime_range'>;
56
+ export type FormForgeBooleanFieldSchema = FormForgeFieldSchemaBase<'checkbox' | 'consent' | 'switch'>;
57
+ export type FormForgeDateLikeFieldSchema = FormForgeFieldSchemaBase<'date' | 'time'>;
58
+ export type FormForgeTemporalFieldSchema = FormForgeFieldSchemaBase<'temporal'>;
59
+ export type FormForgeAddressFieldSchemaValue = FormForgeFieldSchemaBase<'address'>;
44
60
  export interface FormForgeFileFieldSchema extends FormForgeFieldSchemaBase<'file'> {
45
61
  accept?: string[];
46
62
  max_size?: number | null;
47
63
  max_files?: number | null;
48
64
  storage?: FormForgeFieldStorage | null;
49
65
  }
50
- export type FormForgeFieldSchema = FormForgeTextLikeFieldSchema | FormForgeNumberFieldSchema | FormForgeSelectLikeFieldSchema | FormForgeBooleanFieldSchema | FormForgeDateLikeFieldSchema | FormForgeFileFieldSchema;
66
+ export type FormForgeFieldSchema = FormForgeTextLikeFieldSchema | FormForgeNumberFieldSchema | FormForgeSelectLikeFieldSchema | FormForgeBooleanFieldSchema | FormForgeTemporalFieldSchema | FormForgeDateLikeFieldSchema | FormForgeAddressFieldSchemaValue | FormForgeFileFieldSchema;
67
+ export type FormForgeFieldValidationMatch = 'all' | 'any';
68
+ export type FormForgeFieldValidationOperator = 'min' | 'max' | 'after' | 'before' | 'between' | 'not_between' | 'regex' | 'eq' | 'neq' | 'contains' | 'not_contains';
69
+ export interface FormForgeTemporalValidationRangeValue {
70
+ start: string | null;
71
+ end: string | null;
72
+ }
73
+ export interface FormForgeFieldValidationRule {
74
+ validation_key: string;
75
+ target?: string | null;
76
+ operator: FormForgeFieldValidationOperator;
77
+ value: string | number | null | FormForgeTemporalValidationRangeValue;
78
+ unit?: 'characters' | null;
79
+ }
80
+ export interface FormForgeFieldValidationConfig {
81
+ match: FormForgeFieldValidationMatch;
82
+ rules: FormForgeFieldValidationRule[];
83
+ }
51
84
  export interface FormForgePageSchema {
52
85
  page_key: string;
53
86
  title: string;
@@ -55,6 +88,35 @@ export interface FormForgePageSchema {
55
88
  meta: FormForgeJsonObject;
56
89
  fields: FormForgeFieldSchema[];
57
90
  }
91
+ export type FormForgePageLogicMatch = 'all' | 'any';
92
+ export type FormForgePageLogicOperator = 'eq' | 'neq' | 'contains' | 'not_contains' | 'starts_with' | 'not_starts_with' | 'ends_with' | 'not_ends_with' | 'is_submitted' | 'is_not_submitted' | 'accepted' | 'ignored';
93
+ export type FormForgePageLogicThenAction = 'require' | 'goto_block';
94
+ export type FormForgePageLogicFallbackAction = 'next' | 'goto_block';
95
+ export interface FormForgePageLogicClause {
96
+ field_key: string;
97
+ operator: FormForgePageLogicOperator;
98
+ value: FormForgeJsonValue;
99
+ }
100
+ export interface FormForgePageLogicThen {
101
+ action: FormForgePageLogicThenAction;
102
+ field_key?: string | null;
103
+ block_index?: number | null;
104
+ }
105
+ export interface FormForgePageLogicFallback {
106
+ action: FormForgePageLogicFallbackAction;
107
+ block_index?: number | null;
108
+ }
109
+ export interface FormForgePageLogicRule {
110
+ rule_key: string;
111
+ match: FormForgePageLogicMatch;
112
+ when: FormForgePageLogicClause[];
113
+ then: FormForgePageLogicThen[];
114
+ fallback: FormForgePageLogicFallback;
115
+ }
116
+ export interface FormForgePageLogic {
117
+ version?: number;
118
+ rules: FormForgePageLogicRule[];
119
+ }
58
120
  export type FormForgeConditionTargetType = 'page' | 'field';
59
121
  export type FormForgeConditionAction = 'show' | 'hide' | 'skip' | 'require' | 'disable';
60
122
  export type FormForgeConditionMatch = 'all' | 'any';
@@ -79,10 +141,16 @@ export interface FormForgeDraftSettings {
79
141
  export interface FormForgeFormSchema {
80
142
  key: string;
81
143
  version: string;
144
+ schema_version: number;
82
145
  title: string;
146
+ publish_at?: string | null;
147
+ pause_at?: string | null;
148
+ response_limit?: number | null;
149
+ submission_code_required?: boolean;
83
150
  category?: string | null;
84
151
  category_item?: FormForgeCategory | null;
85
152
  is_published: boolean;
153
+ public_url?: string | null;
86
154
  fields: FormForgeFieldSchema[];
87
155
  pages: FormForgePageSchema[];
88
156
  conditions: FormForgeCondition[];
@@ -0,0 +1,7 @@
1
+ import type { FormForgeAddressFieldSchema, FormForgeFieldType, FormForgeTemporalMode } from '../types/index.js';
2
+ export type FormForgeLocale = 'en' | 'fr';
3
+ export declare function createDefaultAddressFields(locale?: string): FormForgeAddressFieldSchema[];
4
+ export declare function resolveDefaultFieldLabel(type: FormForgeFieldType, locale?: string, temporalMode?: FormForgeTemporalMode): string;
5
+ export declare function resolveDefaultAnswerPlaceholder(locale?: string): string;
6
+ export declare function resolveDefaultConsentLabel(locale?: string): string;
7
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1,86 @@
1
+ const DEFAULT_ADDRESS_FIELDS = {
2
+ en: [
3
+ { key: "line1", label: "Address line 1", visible: true, required: true },
4
+ { key: "line2", label: "Address line 2", visible: false, required: false },
5
+ { key: "city", label: "City", visible: true, required: true },
6
+ { key: "state", label: "State", visible: false, required: false },
7
+ { key: "zip", label: "Zip", visible: true, required: true },
8
+ { key: "country", label: "Country", visible: true, required: true }
9
+ ],
10
+ fr: [
11
+ { key: "line1", label: "Ligne d'adresse 1", visible: true, required: true },
12
+ { key: "line2", label: "Ligne d'adresse 2", visible: false, required: false },
13
+ { key: "city", label: "Ville", visible: true, required: true },
14
+ { key: "state", label: "\xC9tat", visible: false, required: false },
15
+ { key: "zip", label: "Code postal", visible: true, required: true },
16
+ { key: "country", label: "Pays", visible: true, required: true }
17
+ ]
18
+ };
19
+ const DEFAULT_FIELD_LABELS = {
20
+ en: {
21
+ text: "Free text",
22
+ textarea: "Long text",
23
+ email: "Email",
24
+ number: "Number",
25
+ select: "Dropdown",
26
+ select_menu: "Dropdown menu",
27
+ radio: "Single choice",
28
+ checkbox: "Checkbox",
29
+ consent: "Consent",
30
+ checkbox_group: "Multiple choice",
31
+ switch: "Switch",
32
+ temporal: "Date",
33
+ date: "Date",
34
+ time: "Time",
35
+ file: "File upload",
36
+ address: "Address"
37
+ },
38
+ fr: {
39
+ text: "Texte libre",
40
+ textarea: "R\xE9ponse longue",
41
+ email: "E-mail",
42
+ number: "Nombre",
43
+ select: "S\xE9lection",
44
+ select_menu: "Menu d\xE9roulant",
45
+ radio: "Choix unique",
46
+ checkbox: "Case \xE0 cocher",
47
+ consent: "Consentement",
48
+ checkbox_group: "Choix multiples",
49
+ switch: "Interrupteur",
50
+ temporal: "Date",
51
+ date: "Date",
52
+ time: "Heure",
53
+ file: "T\xE9l\xE9versement de fichier",
54
+ address: "Adresse"
55
+ }
56
+ };
57
+ const DEFAULT_ANSWER_PLACEHOLDER = {
58
+ en: "Enter your answer here ...",
59
+ fr: "Entrez votre r\xE9ponse ici ..."
60
+ };
61
+ const DEFAULT_CONSENT_LABEL = {
62
+ en: "I agree",
63
+ fr: "J'accepte"
64
+ };
65
+ function normalizeLocale(locale) {
66
+ if (typeof locale !== "string" || locale.trim() === "") {
67
+ return "en";
68
+ }
69
+ return locale.toLowerCase().startsWith("fr") ? "fr" : "en";
70
+ }
71
+ export function createDefaultAddressFields(locale) {
72
+ return DEFAULT_ADDRESS_FIELDS[normalizeLocale(locale)].map((field) => ({ ...field }));
73
+ }
74
+ export function resolveDefaultFieldLabel(type, locale, temporalMode) {
75
+ const resolvedLocale = normalizeLocale(locale);
76
+ if (type === "temporal") {
77
+ return temporalMode === "time" ? DEFAULT_FIELD_LABELS[resolvedLocale].time : DEFAULT_FIELD_LABELS[resolvedLocale].date;
78
+ }
79
+ return DEFAULT_FIELD_LABELS[resolvedLocale][type] ?? DEFAULT_FIELD_LABELS[resolvedLocale].text;
80
+ }
81
+ export function resolveDefaultAnswerPlaceholder(locale) {
82
+ return DEFAULT_ANSWER_PLACEHOLDER[normalizeLocale(locale)];
83
+ }
84
+ export function resolveDefaultConsentLabel(locale) {
85
+ return DEFAULT_CONSENT_LABEL[normalizeLocale(locale)];
86
+ }
@@ -0,0 +1,24 @@
1
+ import type { FormForgeFieldSchema, FormForgeFieldType, FormForgePageLogic, FormForgePageLogicClause, FormForgePageLogicFallback, FormForgePageLogicOperator, FormForgePageLogicRule, FormForgePageLogicThen, FormForgePageSchema } from '../types/index.js';
2
+ export declare function isTextLikeFieldType(type: FormForgeFieldType): boolean;
3
+ export declare function isChoiceFieldType(type: FormForgeFieldType): boolean;
4
+ export declare function isConsentFieldType(type: FormForgeFieldType): boolean;
5
+ export declare function pageLogicOperatorsForFieldType(type: FormForgeFieldType): FormForgePageLogicOperator[];
6
+ export declare function pageLogicOperatorRequiresValue(type: FormForgeFieldType, operator: FormForgePageLogicOperator): boolean;
7
+ export declare function createPageLogicRule(): FormForgePageLogicRule;
8
+ export declare function createPageLogic(): FormForgePageLogic;
9
+ export declare function normalizePageLogic(value: unknown): FormForgePageLogic;
10
+ export declare function normalizePageLogicRule(value: Partial<FormForgePageLogicRule>): FormForgePageLogicRule;
11
+ export declare function normalizePageLogicClause(value: Partial<FormForgePageLogicClause>): FormForgePageLogicClause;
12
+ export declare function normalizePageLogicThen(value: Partial<FormForgePageLogicThen> | undefined): FormForgePageLogicThen;
13
+ export declare function normalizePageLogicFallback(value: Partial<FormForgePageLogicFallback> | undefined): FormForgePageLogicFallback;
14
+ export declare function createPageLogicThen(): FormForgePageLogicThen;
15
+ export declare function ensurePageLogic(page: FormForgePageSchema): FormForgePageLogic;
16
+ export declare function getPageLogic(page: FormForgePageSchema): FormForgePageLogic;
17
+ export declare function getPageLogicRuleQuestions(page: FormForgePageSchema): FormForgeFieldSchema[];
18
+ export declare function getFuturePageQuestions(pages: FormForgePageSchema[], pageIndex: number): FormForgeFieldSchema[];
19
+ export declare function findFieldByKey(page: FormForgePageSchema, fieldKey: string): FormForgeFieldSchema | undefined;
20
+ export declare function evaluatePageLogicClause(clause: FormForgePageLogicClause, field: FormForgeFieldSchema | undefined, actualValue: unknown): boolean;
21
+ export declare function evaluatePageLogicRule(rule: FormForgePageLogicRule, page: FormForgePageSchema, getActualValue: (field: FormForgeFieldSchema) => unknown): boolean;
22
+ export declare function resolvePageLogicJumpTarget(rule: FormForgePageLogicRule, currentPageIndex: number, pageCount: number): number | null;
23
+ export declare function normalizeJumpIndex(value: unknown, currentPageIndex: number, pageCount: number): number | null;
24
+ //# sourceMappingURL=page-logic.d.ts.map
@@ -0,0 +1,351 @@
1
+ const TEXT_FIELD_TYPES = ["text", "textarea", "email"];
2
+ const CHOICE_FIELD_TYPES = ["select", "select_menu", "radio", "checkbox_group"];
3
+ const TEMPORAL_FIELD_TYPES = ["temporal", "date", "time"];
4
+ const TEXT_OPERATORS = [
5
+ "eq",
6
+ "neq",
7
+ "contains",
8
+ "not_contains",
9
+ "starts_with",
10
+ "not_starts_with",
11
+ "ends_with",
12
+ "not_ends_with",
13
+ "is_submitted"
14
+ ];
15
+ const ADDRESS_OPERATORS = [
16
+ "is_submitted",
17
+ "is_not_submitted"
18
+ ];
19
+ const CONSENT_OPERATORS = ["accepted", "ignored"];
20
+ const CHOICE_OPERATORS = ["eq", "neq"];
21
+ export function isTextLikeFieldType(type) {
22
+ return TEXT_FIELD_TYPES.includes(type);
23
+ }
24
+ export function isChoiceFieldType(type) {
25
+ return CHOICE_FIELD_TYPES.includes(type);
26
+ }
27
+ export function isConsentFieldType(type) {
28
+ return type === "consent";
29
+ }
30
+ export function pageLogicOperatorsForFieldType(type) {
31
+ if (type === "address") {
32
+ return ADDRESS_OPERATORS;
33
+ }
34
+ if (TEMPORAL_FIELD_TYPES.includes(type)) {
35
+ return ADDRESS_OPERATORS;
36
+ }
37
+ if (isConsentFieldType(type)) {
38
+ return CONSENT_OPERATORS;
39
+ }
40
+ if (isChoiceFieldType(type)) {
41
+ return CHOICE_OPERATORS;
42
+ }
43
+ return TEXT_OPERATORS;
44
+ }
45
+ export function pageLogicOperatorRequiresValue(type, operator) {
46
+ if (type === "address") {
47
+ return false;
48
+ }
49
+ if (TEMPORAL_FIELD_TYPES.includes(type)) {
50
+ return false;
51
+ }
52
+ if (isConsentFieldType(type)) {
53
+ return false;
54
+ }
55
+ if (isChoiceFieldType(type)) {
56
+ return operator === "eq" || operator === "neq";
57
+ }
58
+ return operator !== "is_submitted";
59
+ }
60
+ export function createPageLogicRule() {
61
+ return {
62
+ rule_key: `lr_${Math.random().toString(36).slice(2, 8)}`,
63
+ match: "all",
64
+ when: [
65
+ {
66
+ field_key: "",
67
+ operator: "eq",
68
+ value: null
69
+ }
70
+ ],
71
+ then: [createPageLogicThen()],
72
+ fallback: {
73
+ action: "next",
74
+ block_index: null
75
+ }
76
+ };
77
+ }
78
+ export function createPageLogic() {
79
+ return {
80
+ version: 1,
81
+ rules: []
82
+ };
83
+ }
84
+ export function normalizePageLogic(value) {
85
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
86
+ return createPageLogic();
87
+ }
88
+ const candidate = value;
89
+ const rules = Array.isArray(candidate.rules) ? candidate.rules : [];
90
+ return {
91
+ version: typeof candidate.version === "number" && Number.isFinite(candidate.version) ? candidate.version : 1,
92
+ rules: rules.filter((rule) => rule !== null && typeof rule === "object" && !Array.isArray(rule)).map((rule) => normalizePageLogicRule(rule))
93
+ };
94
+ }
95
+ export function normalizePageLogicRule(value) {
96
+ const when = Array.isArray(value.when) ? value.when : [];
97
+ const nextWhen = when.filter((clause) => clause !== null && typeof clause === "object" && !Array.isArray(clause)).map((clause) => normalizePageLogicClause(clause));
98
+ const thenSource = value.then;
99
+ const thenList = Array.isArray(thenSource) ? thenSource : [thenSource];
100
+ return {
101
+ rule_key: typeof value.rule_key === "string" && value.rule_key !== "" ? value.rule_key : `lr_${Math.random().toString(36).slice(2, 8)}`,
102
+ match: value.match === "any" ? "any" : "all",
103
+ when: nextWhen.length > 0 ? nextWhen : [createPageLogicRule().when[0]],
104
+ then: (() => {
105
+ const normalizedThen = thenList.filter((entry) => entry !== null && entry !== "").map((entry) => normalizePageLogicThen(entry));
106
+ const dedupedThen = [];
107
+ const seenRequireFieldKeys = /* @__PURE__ */ new Set();
108
+ for (const thenAction of normalizedThen) {
109
+ if (thenAction.action === "require" && typeof thenAction.field_key === "string" && thenAction.field_key !== "") {
110
+ if (seenRequireFieldKeys.has(thenAction.field_key)) {
111
+ continue;
112
+ }
113
+ seenRequireFieldKeys.add(thenAction.field_key);
114
+ }
115
+ dedupedThen.push(thenAction);
116
+ }
117
+ return dedupedThen.length > 0 ? dedupedThen : [createPageLogicThen()];
118
+ })(),
119
+ fallback: normalizePageLogicFallback(value.fallback)
120
+ };
121
+ }
122
+ export function normalizePageLogicClause(value) {
123
+ return {
124
+ field_key: typeof value.field_key === "string" ? value.field_key : "",
125
+ operator: isPageLogicOperator(value.operator) ? value.operator : "eq",
126
+ value: value.value ?? null
127
+ };
128
+ }
129
+ export function normalizePageLogicThen(value) {
130
+ return {
131
+ action: value?.action === "goto_block" ? "goto_block" : "require",
132
+ field_key: typeof value?.field_key === "string" ? value.field_key : null,
133
+ block_index: normalizeNullableBlockIndex(value?.block_index)
134
+ };
135
+ }
136
+ export function normalizePageLogicFallback(value) {
137
+ return {
138
+ action: value?.action === "goto_block" ? "goto_block" : "next",
139
+ block_index: normalizeNullableBlockIndex(value?.block_index)
140
+ };
141
+ }
142
+ export function createPageLogicThen() {
143
+ return {
144
+ action: "require",
145
+ field_key: null,
146
+ block_index: null
147
+ };
148
+ }
149
+ export function ensurePageLogic(page) {
150
+ const meta = ensurePageMeta(page);
151
+ if (!isPageLogic(meta.logic)) {
152
+ meta.logic = createPageLogic();
153
+ } else {
154
+ meta.logic = normalizePageLogic(meta.logic);
155
+ }
156
+ return meta.logic;
157
+ }
158
+ export function getPageLogic(page) {
159
+ const meta = ensurePageMeta(page);
160
+ return normalizePageLogic(meta.logic);
161
+ }
162
+ export function getPageLogicRuleQuestions(page) {
163
+ return Array.isArray(page.fields) ? page.fields : [];
164
+ }
165
+ export function getFuturePageQuestions(pages, pageIndex) {
166
+ const fields = [];
167
+ for (let index = pageIndex + 1; index < pages.length; index += 1) {
168
+ const page = pages[index];
169
+ if (!page || !Array.isArray(page.fields)) {
170
+ continue;
171
+ }
172
+ fields.push(...page.fields);
173
+ }
174
+ return fields;
175
+ }
176
+ export function findFieldByKey(page, fieldKey) {
177
+ return Array.isArray(page.fields) ? page.fields.find((field) => field.field_key === fieldKey) : void 0;
178
+ }
179
+ export function evaluatePageLogicClause(clause, field, actualValue) {
180
+ if (field === void 0) {
181
+ return false;
182
+ }
183
+ if (clause.operator === "accepted") {
184
+ return actualValue === true;
185
+ }
186
+ if (clause.operator === "ignored") {
187
+ return actualValue !== true;
188
+ }
189
+ if (clause.operator === "is_submitted") {
190
+ return !isEmptyValue(actualValue);
191
+ }
192
+ if (clause.operator === "is_not_submitted") {
193
+ return isEmptyValue(actualValue);
194
+ }
195
+ if (clause.operator === "eq") {
196
+ return areValuesEqual(actualValue, clause.value);
197
+ }
198
+ if (clause.operator === "neq") {
199
+ return !areValuesEqual(actualValue, clause.value);
200
+ }
201
+ if (typeof actualValue !== "string") {
202
+ return false;
203
+ }
204
+ const actual = actualValue;
205
+ const expected = typeof clause.value === "string" ? clause.value : String(clause.value ?? "");
206
+ if (clause.operator === "contains") {
207
+ return actual.includes(expected);
208
+ }
209
+ if (clause.operator === "not_contains") {
210
+ return !actual.includes(expected);
211
+ }
212
+ if (clause.operator === "starts_with") {
213
+ return actual.startsWith(expected);
214
+ }
215
+ if (clause.operator === "not_starts_with") {
216
+ return !actual.startsWith(expected);
217
+ }
218
+ if (clause.operator === "ends_with") {
219
+ return actual.endsWith(expected);
220
+ }
221
+ if (clause.operator === "not_ends_with") {
222
+ return !actual.endsWith(expected);
223
+ }
224
+ return false;
225
+ }
226
+ export function evaluatePageLogicRule(rule, page, getActualValue) {
227
+ if (!Array.isArray(rule.when) || rule.when.length === 0) {
228
+ return false;
229
+ }
230
+ const results = rule.when.map((clause) => {
231
+ const field = findFieldByKey(page, clause.field_key);
232
+ const actualValue = field === void 0 ? void 0 : getActualValue(field);
233
+ return evaluatePageLogicClause(clause, field, actualValue);
234
+ });
235
+ if (rule.match === "any") {
236
+ return results.includes(true);
237
+ }
238
+ return !results.includes(false);
239
+ }
240
+ export function resolvePageLogicJumpTarget(rule, currentPageIndex, pageCount) {
241
+ const fallbackIndex = normalizeJumpIndex(rule.fallback.block_index, currentPageIndex, pageCount);
242
+ for (const thenAction of rule.then) {
243
+ const thenIndex = normalizeJumpIndex(thenAction.block_index, currentPageIndex, pageCount);
244
+ if (thenAction.action === "goto_block" && thenIndex !== null) {
245
+ return thenIndex;
246
+ }
247
+ }
248
+ if (rule.fallback.action === "goto_block" && fallbackIndex !== null) {
249
+ return fallbackIndex;
250
+ }
251
+ return null;
252
+ }
253
+ export function normalizeJumpIndex(value, currentPageIndex, pageCount) {
254
+ const numeric = normalizeNullableBlockIndex(value);
255
+ if (numeric === null) {
256
+ return null;
257
+ }
258
+ const zeroBased = numeric - 1;
259
+ if (zeroBased < 0 || zeroBased >= pageCount) {
260
+ return null;
261
+ }
262
+ if (zeroBased <= currentPageIndex) {
263
+ return null;
264
+ }
265
+ return zeroBased;
266
+ }
267
+ function normalizeNullableBlockIndex(value) {
268
+ if (typeof value !== "number" || !Number.isFinite(value)) {
269
+ return null;
270
+ }
271
+ const normalized = Math.trunc(value);
272
+ return normalized > 0 ? normalized : null;
273
+ }
274
+ function ensurePageMeta(page) {
275
+ if (typeof page.meta !== "object" || page.meta === null || Array.isArray(page.meta)) {
276
+ page.meta = {};
277
+ }
278
+ return page.meta;
279
+ }
280
+ function isPageLogic(value) {
281
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Array.isArray(value.rules);
282
+ }
283
+ function isPageLogicOperator(value) {
284
+ return typeof value === "string" && [
285
+ "eq",
286
+ "neq",
287
+ "contains",
288
+ "not_contains",
289
+ "starts_with",
290
+ "not_starts_with",
291
+ "ends_with",
292
+ "not_ends_with",
293
+ "is_submitted",
294
+ "is_not_submitted",
295
+ "accepted",
296
+ "ignored"
297
+ ].includes(value);
298
+ }
299
+ function areValuesEqual(left, right) {
300
+ if (Object.is(left, right)) {
301
+ return true;
302
+ }
303
+ if (Array.isArray(left) && Array.isArray(right)) {
304
+ if (left.length !== right.length) {
305
+ return false;
306
+ }
307
+ return left.every((entry, index) => areValuesEqual(entry, right[index]));
308
+ }
309
+ if (isPlainObject(left) && isPlainObject(right)) {
310
+ const leftKeys = Object.keys(left);
311
+ const rightKeys = Object.keys(right);
312
+ if (leftKeys.length !== rightKeys.length) {
313
+ return false;
314
+ }
315
+ for (const key of leftKeys) {
316
+ if (!areValuesEqual(left[key], right[key])) {
317
+ return false;
318
+ }
319
+ }
320
+ return true;
321
+ }
322
+ return false;
323
+ }
324
+ function isPlainObject(value) {
325
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
326
+ return false;
327
+ }
328
+ if (typeof File !== "undefined" && value instanceof File) {
329
+ return false;
330
+ }
331
+ return true;
332
+ }
333
+ function isEmptyValue(value) {
334
+ if (value === null || value === void 0) {
335
+ return true;
336
+ }
337
+ if (typeof value === "string") {
338
+ return value.trim() === "";
339
+ }
340
+ if (Array.isArray(value)) {
341
+ return value.length === 0;
342
+ }
343
+ if (isPlainObject(value)) {
344
+ const entries = Object.entries(value);
345
+ if (entries.length === 0) {
346
+ return true;
347
+ }
348
+ return entries.every(([, entry]) => isEmptyValue(entry));
349
+ }
350
+ return false;
351
+ }
@@ -0,0 +1,3 @@
1
+ export declare function sanitizeFormForgeRichText(value: string | null | undefined): string;
2
+ export declare function sanitizeFormForgeInlineRichText(value: string | null | undefined): string;
3
+ //# sourceMappingURL=rich-text.d.ts.map
@@ -0,0 +1,72 @@
1
+ const ALLOWED_TAGS = /* @__PURE__ */ new Set(["a", "b", "br", "em", "i", "li", "ol", "p", "strong", "u", "ul"]);
2
+ function sanitizeHref(value) {
3
+ const href = value.trim();
4
+ if (href === "") {
5
+ return null;
6
+ }
7
+ if (/^(https?:|mailto:|tel:|\/|#)/i.test(href)) {
8
+ return href;
9
+ }
10
+ return null;
11
+ }
12
+ function sanitizeAttributes(tagName, rawAttributes) {
13
+ if (tagName !== "a") {
14
+ return "";
15
+ }
16
+ const hrefMatch = rawAttributes.match(/\bhref\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>`]+))/i);
17
+ if (hrefMatch === null) {
18
+ return "";
19
+ }
20
+ const hrefValue = hrefMatch[2] ?? hrefMatch[3] ?? hrefMatch[4] ?? "";
21
+ const safeHref = sanitizeHref(hrefValue);
22
+ if (safeHref === null) {
23
+ return "";
24
+ }
25
+ return ` href="${safeHref.replaceAll('"', "&quot;")}"`;
26
+ }
27
+ export function sanitizeFormForgeRichText(value) {
28
+ if (typeof value !== "string" || value === "") {
29
+ return "";
30
+ }
31
+ const tokens = value.match(/<\/?[^>]+>|[^<]+/g);
32
+ if (tokens === null) {
33
+ return "";
34
+ }
35
+ let output = "";
36
+ const openTags = [];
37
+ for (const token of tokens) {
38
+ if (!token.startsWith("<")) {
39
+ output += token;
40
+ continue;
41
+ }
42
+ const closing = token.startsWith("</");
43
+ const match = token.match(/^<\/?\s*([a-zA-Z0-9-]+)([^>]*)\/?\s*>$/);
44
+ if (match === null) {
45
+ continue;
46
+ }
47
+ const tagName = match[1]?.toLowerCase() ?? "";
48
+ if (!ALLOWED_TAGS.has(tagName)) {
49
+ continue;
50
+ }
51
+ if (tagName === "br") {
52
+ output += "<br>";
53
+ continue;
54
+ }
55
+ if (closing) {
56
+ const openTag = openTags.at(-1);
57
+ if (openTag !== tagName) {
58
+ continue;
59
+ }
60
+ openTags.pop();
61
+ output += `</${tagName}>`;
62
+ continue;
63
+ }
64
+ const attributes = sanitizeAttributes(tagName, match[2] ?? "");
65
+ output += `<${tagName}${attributes}>`;
66
+ openTags.push(tagName);
67
+ }
68
+ return output;
69
+ }
70
+ export function sanitizeFormForgeInlineRichText(value) {
71
+ return sanitizeFormForgeRichText(value).replaceAll(/<\/?p[^>]*>/gi, "").trim();
72
+ }
@@ -1,3 +1,3 @@
1
1
  import type { FormForgeFormSchema, FormForgeJsonObject } from '../types/index.js';
2
- export declare function normalizeFormForgeSchema(payload: FormForgeJsonObject): FormForgeFormSchema;
2
+ export declare function normalizeFormForgeSchema(payload: FormForgeJsonObject, locale?: string): FormForgeFormSchema;
3
3
  //# sourceMappingURL=schema.d.ts.map