@syncfusion/ej2-react-form-builder 0.1.0-beta

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 (145) hide show
  1. package/Common/CheckboxGroup.d.ts +13 -0
  2. package/Common/CheckboxGroup.js +50 -0
  3. package/Common/ConditionalRuleEngine.d.ts +296 -0
  4. package/Common/ConditionalRuleEngine.js +566 -0
  5. package/Common/CustomTabComponent.d.ts +17 -0
  6. package/Common/CustomTabComponent.js +30 -0
  7. package/Common/CustomValidationEngine.d.ts +58 -0
  8. package/Common/CustomValidationEngine.js +146 -0
  9. package/Common/DataGridComponent.d.ts +20 -0
  10. package/Common/DataGridComponent.js +273 -0
  11. package/Common/FormValidator.d.ts +485 -0
  12. package/Common/FormValidator.js +575 -0
  13. package/Common/MinMaxRuleEngine.d.ts +105 -0
  14. package/Common/MinMaxRuleEngine.js +365 -0
  15. package/Common/StaticHtmlRenderer.d.ts +14 -0
  16. package/Common/StaticHtmlRenderer.js +24 -0
  17. package/Common/expressions/DependencyTracker.d.ts +30 -0
  18. package/Common/expressions/DependencyTracker.js +128 -0
  19. package/Common/expressions/ExpressionEngine.d.ts +95 -0
  20. package/Common/expressions/ExpressionEngine.js +506 -0
  21. package/Common/expressions/ExpressionParser.d.ts +41 -0
  22. package/Common/expressions/ExpressionParser.js +269 -0
  23. package/Common/expressions/useGridExpressions.d.ts +12 -0
  24. package/Common/expressions/useGridExpressions.js +71 -0
  25. package/Common/globalFormId.d.ts +26 -0
  26. package/Common/globalFormId.js +45 -0
  27. package/Common/json-converter.d.ts +444 -0
  28. package/Common/json-converter.js +1184 -0
  29. package/Common/parser.d.ts +98 -0
  30. package/Common/parser.js +753 -0
  31. package/Common/utils.d.ts +130 -0
  32. package/Common/utils.js +574 -0
  33. package/README.md +53 -0
  34. package/index.d.ts +6 -0
  35. package/index.js +6 -0
  36. package/license +13 -0
  37. package/package.json +54 -0
  38. package/src/FormBuilder.d.ts +211 -0
  39. package/src/FormBuilder.js +1299 -0
  40. package/src/components/Database/ConnectionForm.d.ts +8 -0
  41. package/src/components/Database/ConnectionForm.js +28 -0
  42. package/src/components/TopBar.d.ts +10 -0
  43. package/src/components/TopBar.js +7 -0
  44. package/src/components/icons/ComponentIcons.d.ts +44 -0
  45. package/src/components/icons/ComponentIcons.js +40 -0
  46. package/src/components/layout/FormFieldWrapper.d.ts +41 -0
  47. package/src/components/layout/FormFieldWrapper.js +112 -0
  48. package/src/components/layout/LeftPane.d.ts +23 -0
  49. package/src/components/layout/LeftPane.js +310 -0
  50. package/src/components/layout/ListBoxPane.d.ts +24 -0
  51. package/src/components/layout/ListBoxPane.js +94 -0
  52. package/src/components/layout/MainSection.d.ts +39 -0
  53. package/src/components/layout/MainSection.js +1766 -0
  54. package/src/components/layout/Preview.d.ts +12 -0
  55. package/src/components/layout/Preview.js +337 -0
  56. package/src/components/layout/RightPane.d.ts +22 -0
  57. package/src/components/layout/RightPane.js +370 -0
  58. package/src/components/theme/ThemeSwitcher.d.ts +14 -0
  59. package/src/components/theme/ThemeSwitcher.js +24 -0
  60. package/src/components/theme/theme-constant.d.ts +37 -0
  61. package/src/components/theme/theme-constant.js +23 -0
  62. package/src/config/component-properties.json +4929 -0
  63. package/src/config/form-setting.json +56 -0
  64. package/src/config/locale.d.ts +449 -0
  65. package/src/config/locale.js +449 -0
  66. package/src/context/FormSchemaContext.d.ts +32 -0
  67. package/src/context/FormSchemaContext.js +155 -0
  68. package/src/context/LocalizationContext.d.ts +27 -0
  69. package/src/context/LocalizationContext.js +43 -0
  70. package/src/context/ThemeContext.d.ts +23 -0
  71. package/src/context/ThemeContext.js +161 -0
  72. package/src/context/contexts.d.ts +6 -0
  73. package/src/context/contexts.js +6 -0
  74. package/src/context/defaultFormSchema.d.ts +6 -0
  75. package/src/context/defaultFormSchema.js +20 -0
  76. package/src/defaultPalette.d.ts +9 -0
  77. package/src/defaultPalette.js +45 -0
  78. package/src/defaultTemplate.d.ts +3 -0
  79. package/src/defaultTemplate.js +185 -0
  80. package/src/import/schemaImportUtils.d.ts +24 -0
  81. package/src/import/schemaImportUtils.js +121 -0
  82. package/src/index.d.ts +16 -0
  83. package/src/index.js +6 -0
  84. package/src/types/ActionButtons.d.ts +44 -0
  85. package/src/types/ActionButtons.js +27 -0
  86. package/src/types/ComponentProperties.d.ts +33 -0
  87. package/src/types/ComponentProperties.js +2 -0
  88. package/src/types/FormSchema.d.ts +585 -0
  89. package/src/types/FormSchema.js +147 -0
  90. package/src/types/connection.d.ts +19 -0
  91. package/src/types/connection.js +9 -0
  92. package/src/utils/AttributesEditor.d.ts +13 -0
  93. package/src/utils/AttributesEditor.js +117 -0
  94. package/src/utils/ButtonGroupEditor.d.ts +15 -0
  95. package/src/utils/ButtonGroupEditor.js +141 -0
  96. package/src/utils/ChoiceBasedFieldEditor.d.ts +16 -0
  97. package/src/utils/ChoiceBasedFieldEditor.js +87 -0
  98. package/src/utils/ClipboardHelper.d.ts +26 -0
  99. package/src/utils/ClipboardHelper.js +83 -0
  100. package/src/utils/ColumnBuilder.d.ts +11 -0
  101. package/src/utils/ColumnBuilder.js +153 -0
  102. package/src/utils/ColumnSpanEditor.d.ts +8 -0
  103. package/src/utils/ColumnSpanEditor.js +98 -0
  104. package/src/utils/ConditionDataEditor.d.ts +29 -0
  105. package/src/utils/ConditionDataEditor.js +177 -0
  106. package/src/utils/ConditionalEditorUtils.d.ts +27 -0
  107. package/src/utils/ConditionalEditorUtils.js +310 -0
  108. package/src/utils/ConditionsEditor.d.ts +20 -0
  109. package/src/utils/ConditionsEditor.js +571 -0
  110. package/src/utils/CustomAccordion.d.ts +13 -0
  111. package/src/utils/CustomAccordion.js +38 -0
  112. package/src/utils/CustomValidationEditor.d.ts +11 -0
  113. package/src/utils/CustomValidationEditor.js +208 -0
  114. package/src/utils/DataSourceCollector.d.ts +3 -0
  115. package/src/utils/DataSourceCollector.js +252 -0
  116. package/src/utils/ExpressionEditor.d.ts +13 -0
  117. package/src/utils/ExpressionEditor.js +99 -0
  118. package/src/utils/FormSettingsPanel.d.ts +9 -0
  119. package/src/utils/FormSettingsPanel.js +60 -0
  120. package/src/utils/LookupConfig.d.ts +11 -0
  121. package/src/utils/LookupConfig.js +159 -0
  122. package/src/utils/MinMaxValueEditor.d.ts +25 -0
  123. package/src/utils/MinMaxValueEditor.js +188 -0
  124. package/src/utils/OptionsEditor.d.ts +8 -0
  125. package/src/utils/OptionsEditor.js +79 -0
  126. package/src/utils/PropertyRenderer.d.ts +22 -0
  127. package/src/utils/PropertyRenderer.js +210 -0
  128. package/src/utils/ValueRenderer.d.ts +2 -0
  129. package/src/utils/ValueRenderer.js +91 -0
  130. package/src/utils/ej2DbAdaptor.d.ts +6 -0
  131. package/src/utils/ej2DbAdaptor.js +38 -0
  132. package/src/utils/mappingHeuristics.d.ts +10 -0
  133. package/src/utils/mappingHeuristics.js +33 -0
  134. package/src/utils/renderComponent.d.ts +18 -0
  135. package/src/utils/renderComponent.js +300 -0
  136. package/styles/tailwind3.css +67 -0
  137. package/styles/themes/action-buttons.css +110 -0
  138. package/styles/themes/datagrid.css +380 -0
  139. package/styles/themes/drag-reorder.css +248 -0
  140. package/styles/themes/import-dialog.css +292 -0
  141. package/styles/themes/index.css +658 -0
  142. package/styles/themes/left-pane.css +253 -0
  143. package/styles/themes/options-editor.css +63 -0
  144. package/styles/themes/preview.css +702 -0
  145. package/styles/themes/property-dialog.css +371 -0
@@ -0,0 +1,130 @@
1
+ import type { FormComponent, FormComponentConditions } from "../FormBuilder";
2
+ /**
3
+ * @private
4
+ */
5
+ export declare const VALIDATION_REGEX: {
6
+ [key: string]: RegExp;
7
+ };
8
+ /**
9
+ * @private
10
+ */
11
+ export interface AvailableField {
12
+ name: string;
13
+ type: string;
14
+ format?: string;
15
+ }
16
+ /**
17
+ * Maps button style to Syncfusion CSS classes
18
+ * @param style - Button style ('primary' | 'flat' | 'information' | 'error' | 'success' | 'warning')
19
+ * @returns Space-separated CSS class string
20
+ * @private
21
+ */
22
+ export declare const mapButtonStyleToClass: (style?: string) => string;
23
+ /**
24
+ * Resolves button type to valid HTML button type
25
+ * @param type - Button type ('submit' | 'reset' | 'button')
26
+ * @returns Resolved button type
27
+ * @private
28
+ */
29
+ export declare const resolveButtonType: (type?: "submit" | "reset" | "button") => "submit" | "reset" | "button";
30
+ /**
31
+ * Maps button position to CSS justifyContent value
32
+ * @param position - Button position ('Left' | 'Right' | 'Center')
33
+ * @returns CSS justifyContent value
34
+ * @private
35
+ */
36
+ export declare const mapPositionToJustifyContent: (position?: string) => string;
37
+ /**
38
+ * @private
39
+ */
40
+ export declare function splitWidths(input: Array<number | null>): number[];
41
+ /**
42
+ * @private
43
+ */
44
+ export declare function extractAllDescendantIds(component: FormComponent): string[];
45
+ /**
46
+ * @private
47
+ */
48
+ export declare function formatDateValues(component: FormComponent, value: any): any;
49
+ /**
50
+ * @private
51
+ */
52
+ export declare const getComponentFromSchema: (id?: string, formSchema?: any) => any | null;
53
+ /**
54
+ * Helper function to get the appropriate adaptor instance
55
+ * @param adaptorType - Type of adaptor ('WebApiAdaptor' | 'UrlAdaptor' | 'ODataV4Adaptor')
56
+ * @returns Adaptor instance
57
+ * @private
58
+ */
59
+ export declare const getAdaptorInstance: (adaptorType?: string) => any;
60
+ /**
61
+ * Extracts data from a nested path using dot notation and array indices
62
+ * @param data - The data object or array
63
+ * @param path - Path to the data (e.g., 'results.items', '[0].data', 'response[0].values')
64
+ * @returns Extracted data or empty array
65
+ * @private
66
+ */
67
+ export declare const extractDataFromPath: (data: any, path?: string) => any;
68
+ /**
69
+ * @private
70
+ */
71
+ export declare const resolveFieldBinding: (value: any, formValues: Record<string, any>, components?: FormComponent[]) => any;
72
+ /**
73
+ * Custom hook to fetch options from URL with custom headers
74
+ * @param componentOptions - The component options object which may contain URL and headers
75
+ * @param adaptorType - Type of adaptor to use ('WebApiAdaptor' | 'UrlAdaptor' | 'ODataV4Adaptor')
76
+ * @returns Array of fetched options
77
+ * @private
78
+ */
79
+ export declare const useFetchOptions: (componentOptions: any, formValues?: Record<string, any>, components?: FormComponent[]) => string[];
80
+ /**
81
+ * Custom hook to fetch lookup options from backend
82
+ * @param formName - Name of the form to fetch submissions from
83
+ * @param field - Field name to extract from submissions
84
+ * @returns Array of field values
85
+ * @private
86
+ */
87
+ export declare const useFetchLookupOptions: (formName?: string, field?: string) => string[];
88
+ /**
89
+ * Resolves component options from various sources
90
+ * Handles array options, URL-fetched options, and raw data options
91
+ * @param options - Component options (can be array, URL config, or raw data config)
92
+ * @param fetchedOptions - Pre-fetched options from URL
93
+ * @returns Resolved array of options
94
+ * @private
95
+ */
96
+ export declare const resolveOptions: (options: any, fetchedOptions: string[], fetchedLookupDataSource?: any[]) => any[];
97
+ /**
98
+ * @private
99
+ */
100
+ export declare const collectAvailableFieldNames: (components: FormComponent[]) => AvailableField[];
101
+ /**
102
+ * Resolve conditional data for a DataGrid component
103
+ * Evaluates all conditionalData rules and applies the first matching one
104
+ * Returns both dataSource and custom columns if defined
105
+ *
106
+ * @param conditions - Component conditions including conditionalData rules
107
+ * @param formValues - Current form field values
108
+ * @returns Object with dataSource and columns, or undefined if no condition matches
109
+ * @private
110
+ */
111
+ export declare const resolveConditionalData: (conditions: FormComponentConditions | undefined, formValues: Record<string, any>) => {
112
+ dataSource?: any[];
113
+ columns?: any[];
114
+ } | undefined;
115
+ /**
116
+ * Reads the current builder mode from the URL `mode` query parameter.
117
+ * @returns 'Simple' | 'Developer' | undefined if not present or invalid
118
+ * @private
119
+ */
120
+ export declare const getCurrentMode: () => "Simple" | "Developer" | undefined;
121
+ /**
122
+ * Navigates to a path-based route, e.g. '/builder', '/dashboard', '/templates',
123
+ * optionally with query string params, e.g. '/builder?formId=123'.
124
+ * The current `mode` query parameter is preserved and appended if it is not
125
+ * already present in the target.
126
+ *
127
+ * @param target - Target path (may include existing query params), e.g. '/builder?formId=123'
128
+ * @private
129
+ */
130
+ export declare const navigateTo: (target: string) => void;
@@ -0,0 +1,574 @@
1
+ import React from "react";
2
+ import { Internationalization } from '@syncfusion/ej2-base';
3
+ import { DataManager, Query, UrlAdaptor, WebApiAdaptor, ODataV4Adaptor } from '@syncfusion/ej2-data';
4
+ import { ConditionalRuleEngine } from "./ConditionalRuleEngine";
5
+ import { UI_TYPE_TO_SEMANTIC_TYPE } from './json-converter';
6
+ import { ExpressionEngine } from "./expressions/ExpressionEngine";
7
+ // import { submitUrl } from "../tempConstants";
8
+ /**
9
+ * @private
10
+ */
11
+ export const VALIDATION_REGEX = {
12
+ EMAIL: /^(?!.*\.\.)[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/,
13
+ // eslint-disable-next-line security/detect-unsafe-regex
14
+ URL: /^(https?:\/\/)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(\/[^\s]*)?$/,
15
+ DATE_ISO: /^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/,
16
+ DIGITS: /^[0-9]*$/,
17
+ PHONE: /^[+]?[0-9]{9,13}$/,
18
+ CREDIT_CARD: /^\d{13,16}$/
19
+ };
20
+ /**
21
+ * Maps button style to Syncfusion CSS classes
22
+ * @param style - Button style ('primary' | 'flat' | 'information' | 'error' | 'success' | 'warning')
23
+ * @returns Space-separated CSS class string
24
+ * @private
25
+ */
26
+ export const mapButtonStyleToClass = (style) => {
27
+ const styleMap = {
28
+ 'primary': 'e-primary',
29
+ 'flat': 'e-flat',
30
+ 'information': 'e-info',
31
+ 'error': 'e-danger',
32
+ 'success': 'e-success',
33
+ 'warning': 'e-warning'
34
+ };
35
+ return styleMap[style || 'primary'] || 'e-primary';
36
+ };
37
+ /**
38
+ * Resolves button type to valid HTML button type
39
+ * @param type - Button type ('submit' | 'reset' | 'button')
40
+ * @returns Resolved button type
41
+ * @private
42
+ */
43
+ export const resolveButtonType = (type) => {
44
+ return (type === 'submit' || type === 'reset') ? type : 'button';
45
+ };
46
+ /**
47
+ * Maps button position to CSS justifyContent value
48
+ * @param position - Button position ('Left' | 'Right' | 'Center')
49
+ * @returns CSS justifyContent value
50
+ * @private
51
+ */
52
+ export const mapPositionToJustifyContent = (position) => {
53
+ const positionMap = {
54
+ 'Left': 'flex-start',
55
+ 'Right': 'flex-end',
56
+ 'Center': 'center'
57
+ };
58
+ return positionMap[position || 'Left'] || 'flex-start';
59
+ };
60
+ /**
61
+ * @private
62
+ */
63
+ export function splitWidths(input) {
64
+ const result = input.map(v => (v === null ? NaN : Number(v)));
65
+ const sumKnown = result.reduce((s, v) => s + (isNaN(v) ? 0 : v), 0);
66
+ // If knowns already >= 100, unknowns become 0
67
+ const remainingInt = Math.max(0, Math.round(100 - sumKnown));
68
+ const unknownIndexes = result.map((v, i) => (isNaN(v) ? i : -1)).filter(i => i >= 0);
69
+ const k = unknownIndexes.length;
70
+ if (k === 0) {
71
+ // nothing to split; just clamp/round knowns so they sum to 100 if you prefer, but return as-is
72
+ return result.map(v => Math.round(v));
73
+ }
74
+ // Distribute integer percentage points to avoid fractional rounding problems
75
+ const base = Math.floor(remainingInt / k);
76
+ let remainder = remainingInt - base * k;
77
+ // Fill unknowns with base + 1 for first `remainder` items
78
+ unknownIndexes.forEach((idx) => {
79
+ const add = remainder > 0 ? base + 1 : base;
80
+ result[idx] = add;
81
+ remainder = Math.max(0, remainder - 1);
82
+ });
83
+ // Convert any NaN (shouldn't remain) to 0 and round knowns
84
+ return result.map(v => (isNaN(v) ? 0 : Math.round(v)));
85
+ }
86
+ /**
87
+ * @private
88
+ */
89
+ export function extractAllDescendantIds(component) {
90
+ const childIds = [];
91
+ function traverse(comps) {
92
+ for (const comp of comps) {
93
+ childIds.push(comp.id);
94
+ // Recursively traverse nested structures
95
+ if (comp.children && Array.isArray(comp.children)) {
96
+ traverse(comp.children);
97
+ }
98
+ // Handle table cells
99
+ if (comp.tableCells && Array.isArray(comp.tableCells)) {
100
+ comp.tableCells.forEach((row) => {
101
+ if (Array.isArray(row)) {
102
+ row.forEach((cell) => {
103
+ if (Array.isArray(cell)) {
104
+ traverse(cell);
105
+ }
106
+ });
107
+ }
108
+ });
109
+ }
110
+ // Handle tab items
111
+ if (comp.tabItems && Array.isArray(comp.tabItems)) {
112
+ comp.tabItems.forEach((tabItem) => {
113
+ if (tabItem.content && Array.isArray(tabItem.content)) {
114
+ traverse(tabItem.content);
115
+ }
116
+ });
117
+ }
118
+ }
119
+ }
120
+ // Start with direct children
121
+ if (component.children && Array.isArray(component.children)) {
122
+ traverse(component.children);
123
+ }
124
+ // Handle table cells
125
+ if (component.tableCells && Array.isArray(component.tableCells)) {
126
+ component.tableCells.forEach((row) => {
127
+ if (Array.isArray(row)) {
128
+ row.forEach((cell) => {
129
+ if (Array.isArray(cell)) {
130
+ traverse(cell);
131
+ }
132
+ });
133
+ }
134
+ });
135
+ }
136
+ // Handle tab items
137
+ if (component.tabItems && Array.isArray(component.tabItems)) {
138
+ component.tabItems.forEach((tabItem) => {
139
+ if (tabItem.content && Array.isArray(tabItem.content)) {
140
+ traverse(tabItem.content);
141
+ }
142
+ });
143
+ }
144
+ return childIds;
145
+ }
146
+ /**
147
+ * @private
148
+ */
149
+ export function formatDateValues(component, value) {
150
+ const intl = new Internationalization();
151
+ let dFormatter = intl.getDateFormat({
152
+ format: 'MM/dd/yyyy',
153
+ type: 'date'
154
+ });
155
+ if (component.type === 'date') {
156
+ value = dFormatter(new Date(value));
157
+ }
158
+ if (component.type === 'time') {
159
+ dFormatter = intl.getDateFormat({
160
+ format: 'hh:mm a',
161
+ type: 'time'
162
+ });
163
+ value = dFormatter(new Date(value));
164
+ }
165
+ if (component.type === 'dateTime') {
166
+ dFormatter = intl.getDateFormat({
167
+ format: 'MM/dd/yyyy hh:mm a',
168
+ type: 'dateTime'
169
+ });
170
+ value = dFormatter(new Date(value));
171
+ }
172
+ if (component.type === 'dateRange' && Array.isArray(value)) {
173
+ value = value.map(date => date ? dFormatter(new Date(date)) : null);
174
+ }
175
+ return value;
176
+ }
177
+ /**
178
+ * @private
179
+ */
180
+ export const getComponentFromSchema = (id, formSchema) => {
181
+ if (!id || !formSchema)
182
+ return null;
183
+ const findInComponents = (components) => {
184
+ for (const comp of components) {
185
+ if (comp.id === id || comp.name === id)
186
+ return comp;
187
+ if (comp.children) {
188
+ const found = findInComponents(comp.children);
189
+ if (found)
190
+ return found;
191
+ }
192
+ if (comp.tableCells) {
193
+ for (const row of comp.tableCells) {
194
+ for (const cell of row) {
195
+ const found = findInComponents(cell);
196
+ if (found)
197
+ return found;
198
+ }
199
+ }
200
+ }
201
+ if (comp.tabItems) {
202
+ for (const tab of comp.tabItems) {
203
+ if (tab.content) {
204
+ const found = findInComponents(tab.content);
205
+ if (found)
206
+ return found;
207
+ }
208
+ }
209
+ }
210
+ }
211
+ return null;
212
+ };
213
+ return findInComponents(formSchema.components || []);
214
+ };
215
+ /**
216
+ * Helper function to get the appropriate adaptor instance
217
+ * @param adaptorType - Type of adaptor ('WebApiAdaptor' | 'UrlAdaptor' | 'ODataV4Adaptor')
218
+ * @returns Adaptor instance
219
+ * @private
220
+ */
221
+ export const getAdaptorInstance = (adaptorType) => {
222
+ switch (adaptorType) {
223
+ case 'WebApiAdaptor':
224
+ return new WebApiAdaptor();
225
+ case 'ODataV4Adaptor':
226
+ return new ODataV4Adaptor();
227
+ case 'UrlAdaptor':
228
+ default:
229
+ return new UrlAdaptor();
230
+ }
231
+ };
232
+ /**
233
+ * Extracts data from a nested path using dot notation and array indices
234
+ * @param data - The data object or array
235
+ * @param path - Path to the data (e.g., 'results.items', '[0].data', 'response[0].values')
236
+ * @returns Extracted data or empty array
237
+ * @private
238
+ */
239
+ export const extractDataFromPath = (data, path) => {
240
+ if (!path || data === null || data === undefined)
241
+ return data;
242
+ // Split by dots but preserve array indices
243
+ const parts = path.split(/\.(?![^\[]*\])/); // Split by dots not inside brackets
244
+ let current = data;
245
+ for (const part of parts) {
246
+ if (current === null || current === undefined) {
247
+ return null;
248
+ }
249
+ // Handle array index notation like [0] or results[0]
250
+ const arrayMatch = part.match(/^(\w*)\[(\d+)\]$/);
251
+ if (arrayMatch) {
252
+ const propName = arrayMatch[1];
253
+ const index = parseInt(arrayMatch[2]);
254
+ // If propName is empty, current should be an array
255
+ if (!propName) {
256
+ current = current[index];
257
+ }
258
+ else {
259
+ current = current[propName]?.[index];
260
+ }
261
+ }
262
+ else {
263
+ // Regular property access
264
+ current = current[part];
265
+ }
266
+ }
267
+ return current;
268
+ };
269
+ /**
270
+ * Build name-indexed context from ID-indexed formValues
271
+ * Simple traversal to map field names to their values
272
+ * @private
273
+ */
274
+ const buildNameContext = (formValues, components) => {
275
+ const context = {};
276
+ const traverse = (comps) => {
277
+ comps.forEach(comp => {
278
+ // Map name -> value from formValues (keyed by ID)
279
+ if (comp.name && comp.id in formValues) {
280
+ context[comp.name] = formValues[comp.id];
281
+ }
282
+ if (comp.children)
283
+ traverse(comp.children);
284
+ if (comp.tabItems)
285
+ comp.tabItems.forEach(t => t.content && traverse(t.content));
286
+ if (comp.tableCells)
287
+ comp.tableCells.forEach(row => row.forEach(cell => traverse(cell)));
288
+ });
289
+ };
290
+ traverse(components);
291
+ return context;
292
+ };
293
+ /**
294
+ * @private
295
+ */
296
+ export const resolveFieldBinding = (value, formValues, components) => {
297
+ if (typeof value !== 'string' || !/{[^}]+}/.test(value)) {
298
+ return value; // No field references
299
+ }
300
+ // Build context: map field names to values from ID-keyed formValues
301
+ const context = components
302
+ ? buildNameContext(formValues, components)
303
+ : formValues;
304
+ try {
305
+ const engine = new ExpressionEngine();
306
+ const result = engine.evaluate(value, context);
307
+ return result !== null && result !== undefined ? String(result) : '';
308
+ }
309
+ catch (err) {
310
+ console.error('Field binding error:', err);
311
+ return value; // Return original on error
312
+ }
313
+ };
314
+ /**
315
+ * Custom hook to fetch options from URL with custom headers
316
+ * @param componentOptions - The component options object which may contain URL and headers
317
+ * @param adaptorType - Type of adaptor to use ('WebApiAdaptor' | 'UrlAdaptor' | 'ODataV4Adaptor')
318
+ * @returns Array of fetched options
319
+ * @private
320
+ */
321
+ export const useFetchOptions = (componentOptions, formValues, components) => {
322
+ const [fetchedOptions, setFetchedOptions] = React.useState([]);
323
+ React.useEffect(() => {
324
+ const isUrlType = componentOptions &&
325
+ typeof componentOptions === 'object' &&
326
+ !Array.isArray(componentOptions) &&
327
+ componentOptions.type === 'url' &&
328
+ componentOptions.url;
329
+ if (isUrlType) {
330
+ const urlConfig = componentOptions;
331
+ try {
332
+ const headers = {};
333
+ if (urlConfig.headers && Array.isArray(urlConfig.headers)) {
334
+ urlConfig.headers.forEach((h) => {
335
+ if (h.key?.trim()) {
336
+ headers[h.key] = h.value;
337
+ }
338
+ });
339
+ }
340
+ // ✅ Create DataManager with selected adaptor
341
+ const dataManager = new DataManager({
342
+ url: urlConfig.url,
343
+ adaptor: getAdaptorInstance(urlConfig.adaptorType),
344
+ headers: Object.keys(headers).length ? headers : undefined
345
+ });
346
+ // ✅ Build Query (dynamic support)
347
+ let query = new Query();
348
+ if (urlConfig.search && urlConfig.search.enabled && urlConfig.search.fields && urlConfig.search.fields.length > 0) {
349
+ const searchKey = urlConfig.search.key || 'search';
350
+ // 🆕 Resolve field bindings in search value
351
+ const resolvedSearchValue = formValues
352
+ ? resolveFieldBinding(searchKey, formValues, components)
353
+ : /[{}]/.test(searchKey) ? '' : searchKey;
354
+ query = query.search(resolvedSearchValue, urlConfig.search.fields);
355
+ }
356
+ // Apply sort query
357
+ if (urlConfig.sort && urlConfig.sort.enabled && urlConfig.sort.field) {
358
+ const sortDirection = urlConfig.sort.direction === 'descending' ? 'descending' : 'ascending';
359
+ query = query.sortBy(urlConfig.sort.field, sortDirection);
360
+ }
361
+ // Apply filter queries with field binding resolution
362
+ if (urlConfig.filters && Array.isArray(urlConfig.filters) && urlConfig.filters.length > 0) {
363
+ urlConfig.filters.forEach((f) => {
364
+ if (f.field && f.operator && f.value !== undefined && f.value !== '') {
365
+ const resolvedFilterValue = formValues
366
+ ? resolveFieldBinding(f.value, formValues, components)
367
+ : /[{}]/.test(f.value) ? '' : f.value;
368
+ if (resolvedFilterValue !== '') {
369
+ query = query.where(f.field, f.operator, resolvedFilterValue);
370
+ }
371
+ }
372
+ });
373
+ }
374
+ // Example: add params (query string)
375
+ if (urlConfig.queryParams) {
376
+ Object.keys(urlConfig.queryParams).forEach(key => {
377
+ query = query.addParams(key, urlConfig.queryParams[key]);
378
+ });
379
+ }
380
+ // Example: paging
381
+ if (urlConfig.skip)
382
+ query = query.skip(urlConfig.skip);
383
+ if (urlConfig.take)
384
+ query = query.take(urlConfig.take);
385
+ // ✅ Execute query
386
+ dataManager.executeQuery(query).then((res) => {
387
+ let data = res.result;
388
+ if (urlConfig.dataPath) {
389
+ data = extractDataFromPath(data, urlConfig.dataPath);
390
+ }
391
+ if (Array.isArray(data)) {
392
+ setFetchedOptions(data);
393
+ }
394
+ else if (typeof data === 'string') {
395
+ setFetchedOptions([data]);
396
+ }
397
+ else {
398
+ setFetchedOptions([]);
399
+ }
400
+ }).catch((err) => {
401
+ console.error('DataManager fetch error:', err);
402
+ setFetchedOptions([]);
403
+ });
404
+ }
405
+ catch (err) {
406
+ console.error('Setup error:', err);
407
+ setFetchedOptions([]);
408
+ }
409
+ }
410
+ }, [componentOptions, formValues, components]);
411
+ return fetchedOptions;
412
+ };
413
+ /**
414
+ * Custom hook to fetch lookup options from backend
415
+ * @param formName - Name of the form to fetch submissions from
416
+ * @param field - Field name to extract from submissions
417
+ * @returns Array of field values
418
+ * @private
419
+ */
420
+ export const useFetchLookupOptions = (formName, field) => {
421
+ const [lookupOptions, setLookupOptions] = React.useState([]);
422
+ React.useEffect(() => {
423
+ if (!formName)
424
+ return;
425
+ const fetchLookupData = async () => {
426
+ try {
427
+ const response = await fetch(/*submitUrl*/ '' + `${formName}`);
428
+ const submissions = await response.json();
429
+ if (field) {
430
+ const options = submissions
431
+ .map((submission) => submission[field])
432
+ .filter(Boolean);
433
+ setLookupOptions(options);
434
+ }
435
+ else {
436
+ setLookupOptions(Array.isArray(submissions) ? submissions : []);
437
+ }
438
+ }
439
+ catch (error) {
440
+ console.error('Error fetching lookup data:', error);
441
+ setLookupOptions([]);
442
+ }
443
+ };
444
+ fetchLookupData();
445
+ }, [formName, field]);
446
+ return lookupOptions;
447
+ };
448
+ /**
449
+ * Resolves component options from various sources
450
+ * Handles array options, URL-fetched options, and raw data options
451
+ * @param options - Component options (can be array, URL config, or raw data config)
452
+ * @param fetchedOptions - Pre-fetched options from URL
453
+ * @returns Resolved array of options
454
+ * @private
455
+ */
456
+ export const resolveOptions = (options, fetchedOptions, fetchedLookupDataSource) => {
457
+ // If lookup type with fetched data, return fetched lookup records
458
+ if (options && typeof options === 'object' && !Array.isArray(options) && options.type === 'lookup' && fetchedLookupDataSource) {
459
+ return fetchedLookupDataSource?.length > 0 ? fetchedLookupDataSource : [];
460
+ }
461
+ // If URL type with fetched data, return fetched options
462
+ if (options && typeof options === 'object' && !Array.isArray(options) && options.type === 'url') {
463
+ return fetchedOptions.length > 0 ? fetchedOptions : [];
464
+ }
465
+ // If already an array, return as-is
466
+ if (Array.isArray(options)) {
467
+ return options;
468
+ }
469
+ // If it's raw data type
470
+ if (options && typeof options === 'object' && !Array.isArray(options) && options.type === 'raw') {
471
+ return Array.isArray(options.data) ? options.data : [];
472
+ }
473
+ return [];
474
+ };
475
+ /**
476
+ * @private
477
+ */
478
+ export const collectAvailableFieldNames = (components) => {
479
+ const fieldNames = [];
480
+ const traverse = (comps) => {
481
+ comps.forEach(comp => {
482
+ // Only add fields that have a name and aren't layout components
483
+ if (comp.name && !['panel', 'table', 'tabs', 'card', 'message', 'button', 'splitButton', 'staticHtml'].includes(comp.type)) {
484
+ // Get semantic type mapping for this component
485
+ const semanticType = UI_TYPE_TO_SEMANTIC_TYPE[comp.type];
486
+ if (semanticType) {
487
+ fieldNames.push({
488
+ name: comp.name,
489
+ type: semanticType.type,
490
+ ...(semanticType.format && { format: semanticType.format })
491
+ });
492
+ }
493
+ }
494
+ // Traverse nested components
495
+ if (comp.children)
496
+ traverse(comp.children);
497
+ if (comp.tabItems) {
498
+ comp.tabItems.forEach(tab => {
499
+ if (tab.content)
500
+ traverse(tab.content);
501
+ });
502
+ }
503
+ if (comp.tableCells) {
504
+ comp.tableCells.forEach(row => {
505
+ row.forEach(cell => traverse(cell));
506
+ });
507
+ }
508
+ });
509
+ };
510
+ traverse(components);
511
+ return fieldNames;
512
+ };
513
+ /**
514
+ * Resolve conditional data for a DataGrid component
515
+ * Evaluates all conditionalData rules and applies the first matching one
516
+ * Returns both dataSource and custom columns if defined
517
+ *
518
+ * @param conditions - Component conditions including conditionalData rules
519
+ * @param formValues - Current form field values
520
+ * @returns Object with dataSource and columns, or undefined if no condition matches
521
+ * @private
522
+ */
523
+ export const resolveConditionalData = (conditions, formValues) => {
524
+ if (!conditions?.conditionalData || !Array.isArray(conditions.conditionalData)) {
525
+ return undefined;
526
+ }
527
+ // Evaluate each conditional data rule in order
528
+ for (const rule of conditions.conditionalData) {
529
+ if (ConditionalRuleEngine.evaluateCondition(rule.condition, formValues)) {
530
+ // Condition is met - resolve and return both data source and columns
531
+ return {
532
+ dataSource: rule.dataSource,
533
+ columns: rule?.columns?.length > 0 ? rule.columns : undefined
534
+ };
535
+ }
536
+ }
537
+ return undefined;
538
+ };
539
+ /**
540
+ * Reads the current builder mode from the URL `mode` query parameter.
541
+ * @returns 'Simple' | 'Developer' | undefined if not present or invalid
542
+ * @private
543
+ */
544
+ export const getCurrentMode = () => {
545
+ if (typeof window === 'undefined')
546
+ return undefined;
547
+ const urlMode = new URLSearchParams(window.location.search).get('mode');
548
+ if (urlMode === 'developer')
549
+ return 'Developer';
550
+ if (urlMode === 'simple')
551
+ return 'Simple';
552
+ return undefined;
553
+ };
554
+ /**
555
+ * Navigates to a path-based route, e.g. '/builder', '/dashboard', '/templates',
556
+ * optionally with query string params, e.g. '/builder?formId=123'.
557
+ * The current `mode` query parameter is preserved and appended if it is not
558
+ * already present in the target.
559
+ *
560
+ * @param target - Target path (may include existing query params), e.g. '/builder?formId=123'
561
+ * @private
562
+ */
563
+ export const navigateTo = (target) => {
564
+ if (typeof window === 'undefined') {
565
+ return;
566
+ }
567
+ try {
568
+ const url = new URL(target, window.location.origin);
569
+ window.location.href = url.toString();
570
+ }
571
+ catch {
572
+ window.location.href = target;
573
+ }
574
+ };