@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.
- package/Common/CheckboxGroup.d.ts +13 -0
- package/Common/CheckboxGroup.js +50 -0
- package/Common/ConditionalRuleEngine.d.ts +296 -0
- package/Common/ConditionalRuleEngine.js +566 -0
- package/Common/CustomTabComponent.d.ts +17 -0
- package/Common/CustomTabComponent.js +30 -0
- package/Common/CustomValidationEngine.d.ts +58 -0
- package/Common/CustomValidationEngine.js +146 -0
- package/Common/DataGridComponent.d.ts +20 -0
- package/Common/DataGridComponent.js +273 -0
- package/Common/FormValidator.d.ts +485 -0
- package/Common/FormValidator.js +575 -0
- package/Common/MinMaxRuleEngine.d.ts +105 -0
- package/Common/MinMaxRuleEngine.js +365 -0
- package/Common/StaticHtmlRenderer.d.ts +14 -0
- package/Common/StaticHtmlRenderer.js +24 -0
- package/Common/expressions/DependencyTracker.d.ts +30 -0
- package/Common/expressions/DependencyTracker.js +128 -0
- package/Common/expressions/ExpressionEngine.d.ts +95 -0
- package/Common/expressions/ExpressionEngine.js +506 -0
- package/Common/expressions/ExpressionParser.d.ts +41 -0
- package/Common/expressions/ExpressionParser.js +269 -0
- package/Common/expressions/useGridExpressions.d.ts +12 -0
- package/Common/expressions/useGridExpressions.js +71 -0
- package/Common/globalFormId.d.ts +26 -0
- package/Common/globalFormId.js +45 -0
- package/Common/json-converter.d.ts +444 -0
- package/Common/json-converter.js +1184 -0
- package/Common/parser.d.ts +98 -0
- package/Common/parser.js +753 -0
- package/Common/utils.d.ts +130 -0
- package/Common/utils.js +574 -0
- package/README.md +53 -0
- package/index.d.ts +6 -0
- package/index.js +6 -0
- package/license +13 -0
- package/package.json +54 -0
- package/src/FormBuilder.d.ts +211 -0
- package/src/FormBuilder.js +1299 -0
- package/src/components/Database/ConnectionForm.d.ts +8 -0
- package/src/components/Database/ConnectionForm.js +28 -0
- package/src/components/TopBar.d.ts +10 -0
- package/src/components/TopBar.js +7 -0
- package/src/components/icons/ComponentIcons.d.ts +44 -0
- package/src/components/icons/ComponentIcons.js +40 -0
- package/src/components/layout/FormFieldWrapper.d.ts +41 -0
- package/src/components/layout/FormFieldWrapper.js +112 -0
- package/src/components/layout/LeftPane.d.ts +23 -0
- package/src/components/layout/LeftPane.js +310 -0
- package/src/components/layout/ListBoxPane.d.ts +24 -0
- package/src/components/layout/ListBoxPane.js +94 -0
- package/src/components/layout/MainSection.d.ts +39 -0
- package/src/components/layout/MainSection.js +1766 -0
- package/src/components/layout/Preview.d.ts +12 -0
- package/src/components/layout/Preview.js +337 -0
- package/src/components/layout/RightPane.d.ts +22 -0
- package/src/components/layout/RightPane.js +370 -0
- package/src/components/theme/ThemeSwitcher.d.ts +14 -0
- package/src/components/theme/ThemeSwitcher.js +24 -0
- package/src/components/theme/theme-constant.d.ts +37 -0
- package/src/components/theme/theme-constant.js +23 -0
- package/src/config/component-properties.json +4929 -0
- package/src/config/form-setting.json +56 -0
- package/src/config/locale.d.ts +449 -0
- package/src/config/locale.js +449 -0
- package/src/context/FormSchemaContext.d.ts +32 -0
- package/src/context/FormSchemaContext.js +155 -0
- package/src/context/LocalizationContext.d.ts +27 -0
- package/src/context/LocalizationContext.js +43 -0
- package/src/context/ThemeContext.d.ts +23 -0
- package/src/context/ThemeContext.js +161 -0
- package/src/context/contexts.d.ts +6 -0
- package/src/context/contexts.js +6 -0
- package/src/context/defaultFormSchema.d.ts +6 -0
- package/src/context/defaultFormSchema.js +20 -0
- package/src/defaultPalette.d.ts +9 -0
- package/src/defaultPalette.js +45 -0
- package/src/defaultTemplate.d.ts +3 -0
- package/src/defaultTemplate.js +185 -0
- package/src/import/schemaImportUtils.d.ts +24 -0
- package/src/import/schemaImportUtils.js +121 -0
- package/src/index.d.ts +16 -0
- package/src/index.js +6 -0
- package/src/types/ActionButtons.d.ts +44 -0
- package/src/types/ActionButtons.js +27 -0
- package/src/types/ComponentProperties.d.ts +33 -0
- package/src/types/ComponentProperties.js +2 -0
- package/src/types/FormSchema.d.ts +585 -0
- package/src/types/FormSchema.js +147 -0
- package/src/types/connection.d.ts +19 -0
- package/src/types/connection.js +9 -0
- package/src/utils/AttributesEditor.d.ts +13 -0
- package/src/utils/AttributesEditor.js +117 -0
- package/src/utils/ButtonGroupEditor.d.ts +15 -0
- package/src/utils/ButtonGroupEditor.js +141 -0
- package/src/utils/ChoiceBasedFieldEditor.d.ts +16 -0
- package/src/utils/ChoiceBasedFieldEditor.js +87 -0
- package/src/utils/ClipboardHelper.d.ts +26 -0
- package/src/utils/ClipboardHelper.js +83 -0
- package/src/utils/ColumnBuilder.d.ts +11 -0
- package/src/utils/ColumnBuilder.js +153 -0
- package/src/utils/ColumnSpanEditor.d.ts +8 -0
- package/src/utils/ColumnSpanEditor.js +98 -0
- package/src/utils/ConditionDataEditor.d.ts +29 -0
- package/src/utils/ConditionDataEditor.js +177 -0
- package/src/utils/ConditionalEditorUtils.d.ts +27 -0
- package/src/utils/ConditionalEditorUtils.js +310 -0
- package/src/utils/ConditionsEditor.d.ts +20 -0
- package/src/utils/ConditionsEditor.js +571 -0
- package/src/utils/CustomAccordion.d.ts +13 -0
- package/src/utils/CustomAccordion.js +38 -0
- package/src/utils/CustomValidationEditor.d.ts +11 -0
- package/src/utils/CustomValidationEditor.js +208 -0
- package/src/utils/DataSourceCollector.d.ts +3 -0
- package/src/utils/DataSourceCollector.js +252 -0
- package/src/utils/ExpressionEditor.d.ts +13 -0
- package/src/utils/ExpressionEditor.js +99 -0
- package/src/utils/FormSettingsPanel.d.ts +9 -0
- package/src/utils/FormSettingsPanel.js +60 -0
- package/src/utils/LookupConfig.d.ts +11 -0
- package/src/utils/LookupConfig.js +159 -0
- package/src/utils/MinMaxValueEditor.d.ts +25 -0
- package/src/utils/MinMaxValueEditor.js +188 -0
- package/src/utils/OptionsEditor.d.ts +8 -0
- package/src/utils/OptionsEditor.js +79 -0
- package/src/utils/PropertyRenderer.d.ts +22 -0
- package/src/utils/PropertyRenderer.js +210 -0
- package/src/utils/ValueRenderer.d.ts +2 -0
- package/src/utils/ValueRenderer.js +91 -0
- package/src/utils/ej2DbAdaptor.d.ts +6 -0
- package/src/utils/ej2DbAdaptor.js +38 -0
- package/src/utils/mappingHeuristics.d.ts +10 -0
- package/src/utils/mappingHeuristics.js +33 -0
- package/src/utils/renderComponent.d.ts +18 -0
- package/src/utils/renderComponent.js +300 -0
- package/styles/tailwind3.css +67 -0
- package/styles/themes/action-buttons.css +110 -0
- package/styles/themes/datagrid.css +380 -0
- package/styles/themes/drag-reorder.css +248 -0
- package/styles/themes/import-dialog.css +292 -0
- package/styles/themes/index.css +658 -0
- package/styles/themes/left-pane.css +253 -0
- package/styles/themes/options-editor.css +63 -0
- package/styles/themes/preview.css +702 -0
- 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;
|
package/Common/utils.js
ADDED
|
@@ -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
|
+
};
|