@flowuent-org/diagramming-core 1.0.8 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/apps/diagramming/src/AutomationDiagramData.ts +22 -0
  2. package/apps/diagramming/src/components/AddNodeView.tsx +252 -252
  3. package/apps/diagramming/src/main.tsx +463 -463
  4. package/apps/diagramming/src/node-data.ts +664 -664
  5. package/apps/diagramming/src/stencil-items.ts +31 -31
  6. package/apps/diagramming/src/vite-env.d.ts +1 -1
  7. package/package.json +1 -1
  8. package/packages/diagrams/NODE_DATA_UPDATE_API.md +430 -430
  9. package/packages/diagrams/README.md +7 -463
  10. package/packages/diagrams/UNDO_REDO_API.md +306 -306
  11. package/packages/diagrams/package.json +27 -27
  12. package/packages/diagrams/project.json +42 -42
  13. package/packages/diagrams/rollup.config.js +26 -26
  14. package/packages/diagrams/src/DiagramFlow.tsx +7 -7
  15. package/packages/diagrams/src/index.ts +116 -116
  16. package/packages/diagrams/src/index.ts.bak +99 -99
  17. package/packages/diagrams/src/lib/atoms/CardEditableTitle.tsx +76 -76
  18. package/packages/diagrams/src/lib/atoms/ExpressionInput.tsx +437 -437
  19. package/packages/diagrams/src/lib/components/DiagramPanel.tsx +331 -331
  20. package/packages/diagrams/src/lib/components/automation/AISuggestionsModal.tsx +269 -0
  21. package/packages/diagrams/src/lib/components/automation/AISuggestionsPanel.tsx +227 -0
  22. package/packages/diagrams/src/lib/components/automation/AutomationAISuggestionNode.tsx +178 -115
  23. package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +133 -27
  24. package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +134 -28
  25. package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +132 -27
  26. package/packages/diagrams/src/lib/components/automation/AutomationNoteNode.tsx +124 -17
  27. package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +122 -15
  28. package/packages/diagrams/src/lib/components/automation/index.ts +3 -0
  29. package/packages/diagrams/src/lib/contexts/onWorkflowNodeDelete.ts +65 -65
  30. package/packages/diagrams/src/lib/organisms/CustomEdge/useCreateBendPoint.tsx +121 -121
  31. package/packages/diagrams/src/lib/organisms/WorkFlowNode/NodeActionButtons.tsx +45 -45
  32. package/packages/diagrams/src/lib/templates/node-forms/CallForm.tsx +370 -370
  33. package/packages/diagrams/src/lib/templates/systemFlow/components/FloatingEdge.tsx +219 -219
  34. package/packages/diagrams/src/lib/types/card-node.ts +68 -68
  35. package/packages/diagrams/src/lib/types/node-types.ts +29 -29
  36. package/packages/diagrams/src/lib/utils/AutomationExecutionEngine.ts +1179 -1179
  37. package/packages/diagrams/tsconfig.lib.json +25 -25
  38. package/tsconfig.base.json +29 -30
  39. package/TRANSLATION_FIX_SUMMARY.md +0 -118
  40. package/packages/diagrams/I18N_SETUP.md +0 -126
@@ -1,437 +1,437 @@
1
- import React, { useState, useEffect } from 'react';
2
- import styled from '@emotion/styled';
3
- import { Divider, Stack, Typography } from '@mui/material';
4
- import { Expression, OperationExpression } from '../types/ndoe-form-types';
5
- import { useTranslation } from 'react-i18next';
6
- import { BlockTypesKeys } from '../organisms/Card/card.params';
7
- import ValidationMessage from './ValidationMessage';
8
- import { validationEngine } from '../utils/validationEngine';
9
- import { ValidationResult } from '../types/validation-types';
10
-
11
- // Styled Components
12
- export const StyledSelect: ReturnType<typeof styled.select> = styled.select`
13
- width: 100%;
14
- padding: 8px;
15
- font-size: 14px;
16
- border-radius: 4px;
17
- border: 1px solid #ccc;
18
- background-color: transparent;
19
- color: #FFFFFF;
20
-
21
- &:hover {
22
- border-color: #000000;
23
- }
24
-
25
- &:focus {
26
- outline: none;
27
- border-color: #1976d2;
28
- }
29
- `;
30
-
31
- export const StyledInput: ReturnType<typeof styled.input> = styled.input`
32
- width: 100%;
33
- padding: 8px;
34
- font-size: 14px;
35
- border-radius: 4px;
36
- border: 1px solid #ccc;
37
- background-color: transparent;
38
- color: #FFFFFF;
39
-
40
- &:hover {
41
- border-color: #000000;
42
- }
43
-
44
- &:focus {
45
- outline: none;
46
- border-color: #1976d2;
47
- }
48
- `;
49
-
50
- // New styled components for the integrated input with dropdown
51
- const InputWithDropdownContainer = styled.div`
52
- position: relative;
53
- display: flex;
54
- align-items: center;
55
- width: 100%;
56
- `;
57
-
58
- const StyledInputWithDropdown = styled.input`
59
- width: 100%;
60
- padding: 8px;
61
- padding-right: 110px; /* Make room for dropdown */
62
- font-size: 14px;
63
- border-radius: 4px;
64
- border: 1px solid #ccc;
65
- background-color: transparent;
66
- color: #FFFFFF;
67
-
68
- &:hover {
69
- border-color: #000000;
70
- }
71
-
72
- &:focus {
73
- outline: none;
74
- border-color: #1976d2;
75
- }
76
- `;
77
-
78
- const DropdownOverlay = styled.div`
79
- position: absolute;
80
- right: 2px;
81
- top: 50%;
82
- transform: translateY(-50%);
83
- z-index: 10;
84
- pointer-events: auto;
85
- width: 100px;
86
- `;
87
-
88
- const StyledSelectOverlay = styled.select`
89
- min-width: 100px;
90
- height: 32px;
91
- background-color: transparent;
92
- cursor: pointer;
93
- border: none;
94
- outline: none;
95
- padding: 4px 8px;
96
- font-size: 12px;
97
- color: #FFFFFF;
98
- text-align: center;
99
- border-radius: 4px;
100
-
101
- &:hover {
102
- background-color: rgba(255, 255, 255, 0.1);
103
- }
104
-
105
- &:focus {
106
- background-color: rgba(255, 255, 255, 0.15);
107
- }
108
-
109
- option {
110
- background-color: #393939;
111
- color: #FFFFFF;
112
- padding: 8px;
113
- }
114
- `;
115
-
116
- const OperatorContainer = styled.div`
117
- display: flex;
118
- align-items: center;
119
- justify-content: space-between;
120
- gap: 10px;
121
- `;
122
-
123
- const OperandWrapper = styled.div`
124
- flex: 1;
125
- `;
126
-
127
- interface ExpressionInputProps {
128
- value: Expression;
129
- onChange: (newValue: Expression) => void;
130
- label: string;
131
- className?: string;
132
- showDataTypeDropdown?: boolean;
133
- dataType?: string;
134
- onDataTypeChange?: (dataType: string) => void;
135
- onValidationChange?: (isValid: boolean, errors: string[]) => void;
136
- }
137
-
138
- const operationOperandCounts: { [key: string]: number } = {
139
- '+': 2,
140
- '-': 2,
141
- '*': 2,
142
- '/': 2,
143
- '>': 2,
144
- '>=': 2,
145
- '<': 2,
146
- '<=': 2,
147
- '=': 2,
148
- '==': 2,
149
- '!=': 2,
150
- neg: 1,
151
- not: 1,
152
- and: 2,
153
- or: 2,
154
- };
155
-
156
- const ExpressionInput: React.FC<ExpressionInputProps> = ({
157
- value,
158
- onChange,
159
- label,
160
- className = '',
161
- showDataTypeDropdown = false,
162
- dataType = 'String',
163
- onDataTypeChange,
164
- onValidationChange,
165
- }) => {
166
- const { t } = useTranslation();
167
- const isOperation = value && typeof value === 'object' && 'operation' in value;
168
-
169
- // State for validation results
170
- const [operandValidations, setOperandValidations] = useState<ValidationResult[][]>([]);
171
- const [showOperandValidations, setShowOperandValidations] = useState<boolean[]>([]);
172
- const [primitiveValidations, setPrimitiveValidations] = useState<ValidationResult[]>([]);
173
- const [showPrimitiveValidations, setShowPrimitiveValidations] = useState<boolean>(false);
174
-
175
- // Validate primitive value using validation engine
176
- const validatePrimitiveValue = (value: string): ValidationResult[] => {
177
- return validationEngine.validatePrimitiveValue(value, dataType);
178
- };
179
-
180
- // Re-validate primitive value when data type changes
181
- useEffect(() => {
182
- if (!isOperation && typeof value === 'string') {
183
- const validations = validatePrimitiveValue(value);
184
- setPrimitiveValidations(validations);
185
- setShowPrimitiveValidations(validations.length > 0);
186
- }
187
- }, [dataType, value, isOperation]);
188
-
189
- const handlePrimitiveChange = (e: React.ChangeEvent<HTMLInputElement>) => {
190
- const newValue = e.target.value;
191
- onChange(newValue); // Update the parent directly
192
-
193
- // Validate and show validation results if needed
194
- const validations = validatePrimitiveValue(newValue);
195
- setPrimitiveValidations(validations);
196
- setShowPrimitiveValidations(validations.length > 0);
197
-
198
- // Notify parent component about validation state
199
- if (onValidationChange) {
200
- const hasErrors = validationEngine.hasErrors(validations);
201
- const errorMessages = validationEngine.getErrors(validations).map(v => v.message);
202
- onValidationChange(!hasErrors, errorMessages);
203
- }
204
- };
205
-
206
- const handleOperationTypeChange = (
207
- e: React.ChangeEvent<HTMLSelectElement>,
208
- ) => {
209
- const newOperation = e.target.value;
210
- const operandCount = operationOperandCounts[newOperation] || 2;
211
- const newOperands = Array(operandCount).fill('');
212
- onChange({ operation: newOperation, operands: newOperands });
213
-
214
- // Reset validation states for new operation
215
- setOperandValidations(Array(operandCount).fill([]));
216
- setShowOperandValidations(Array(operandCount).fill(false));
217
- };
218
-
219
- // Validate operands when they change
220
- useEffect(() => {
221
- if (isOperation) {
222
- const operationValue = value as OperationExpression;
223
- const validations: ValidationResult[][] = [];
224
- const showValidations: boolean[] = [];
225
-
226
- operationValue.operands?.forEach((operand, index) => {
227
- // Use validation engine to validate operand
228
- const operandValidations = validationEngine.validateOperationOperands([operand], dataType);
229
- validations.push(operandValidations);
230
-
231
- // Show validation if operand has been interacted with (has value or was touched)
232
- showValidations.push(!!operand || showOperandValidations[index] || false);
233
- });
234
-
235
- setOperandValidations(validations);
236
- setShowOperandValidations(showValidations);
237
-
238
- // Notify parent component about validation state
239
- if (onValidationChange) {
240
- const allValidations = validations.flat();
241
- const hasErrors = validationEngine.hasErrors(allValidations);
242
- const errorMessages = validationEngine.getErrors(allValidations).map(v => v.message);
243
- onValidationChange(!hasErrors, errorMessages);
244
- }
245
- }
246
- }, [value, dataType, isOperation, showOperandValidations, onValidationChange]);
247
-
248
- const handleOperandChange = (index: number, operandValue: string) => {
249
- if (isOperation) {
250
- const operationValue = value as OperationExpression;
251
- const updatedOperands = [...operationValue.operands];
252
- updatedOperands[index] = operandValue;
253
-
254
- // Mark this operand as touched for validation display
255
- const newShowValidations = [...showOperandValidations];
256
- newShowValidations[index] = true;
257
- setShowOperandValidations(newShowValidations);
258
-
259
- // Clear validation when user starts typing
260
- if (operandValidations[index] && operandValidations[index].length > 0) {
261
- const newValidations = [...operandValidations];
262
- newValidations[index] = [];
263
- setOperandValidations(newValidations);
264
- }
265
-
266
- onChange({ ...operationValue, operands: updatedOperands });
267
- }
268
- };
269
-
270
- const handleTypeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
271
- const selectedValue = e.target.value;
272
- if (selectedValue === 'primitive') {
273
- onChange(String(value ?? '')); // Switch to primitive
274
- // Clear operand validations when switching to primitive
275
- setOperandValidations([]);
276
- setShowOperandValidations([]);
277
- // Clear primitive validations
278
- setPrimitiveValidations([]);
279
- setShowPrimitiveValidations(false);
280
- } else {
281
- // Always initialize with a proper operation structure when switching to operation mode
282
- onChange({ operation: '+', operands: ['', ''] });
283
- // Initialize validation states for new operation
284
- setOperandValidations([[], []]); // Start with empty validations
285
- setShowOperandValidations([false, false]); // Don't show validations initially
286
- // Clear primitive validations
287
- setPrimitiveValidations([]);
288
- setShowPrimitiveValidations(false);
289
- }
290
- };
291
-
292
- const renderOperandInput = (operand: string, index: number) => (
293
- <div>
294
- <StyledInput
295
- type="text"
296
- value={operand}
297
- onChange={(e) => handleOperandChange(index, e.target.value)}
298
- placeholder={`Operand ${index + 1}`}
299
- style={{
300
- borderColor: showOperandValidations[index] && operandValidations[index]?.some(v => v.severity === 'error') ? '#d32f2f' : undefined
301
- }}
302
- />
303
- {showOperandValidations[index] && operandValidations[index]?.map((validation, vIndex) => (
304
- <ValidationMessage
305
- key={vIndex}
306
- message={validation.message}
307
- severity={validation.severity}
308
- show={true}
309
- />
310
- ))}
311
- </div>
312
- );
313
-
314
- const renderPrimitiveInput = () => {
315
- if (showDataTypeDropdown) {
316
- return (
317
- <div>
318
- <InputWithDropdownContainer>
319
- <StyledInputWithDropdown
320
- className="zd__primitive-input-with-dropdown"
321
- type="text"
322
- value={String(value ?? '')}
323
- onChange={handlePrimitiveChange}
324
- placeholder="Enter value"
325
- style={{
326
- borderColor: showPrimitiveValidations ? '#d32f2f' : undefined
327
- }}
328
- />
329
- <DropdownOverlay>
330
- <StyledSelectOverlay
331
- value={dataType}
332
- onChange={(e) => onDataTypeChange?.(e.target.value)}
333
- >
334
- {BlockTypesKeys.map((type) => (
335
- <option key={type} value={type}>
336
- {type}
337
- </option>
338
- ))}
339
- </StyledSelectOverlay>
340
- </DropdownOverlay>
341
- </InputWithDropdownContainer>
342
- {showPrimitiveValidations && primitiveValidations.map((validation, vIndex) => (
343
- <ValidationMessage
344
- key={vIndex}
345
- message={validation.message}
346
- severity={validation.severity}
347
- show={true}
348
- />
349
- ))}
350
- </div>
351
- );
352
- }
353
-
354
- return (
355
- <div>
356
- <StyledInput
357
- type="text"
358
- value={String(value ?? '')}
359
- onChange={handlePrimitiveChange}
360
- placeholder="Enter value"
361
- style={{
362
- borderColor: showPrimitiveValidations ? '#d32f2f' : undefined
363
- }}
364
- />
365
- {showPrimitiveValidations && primitiveValidations.map((validation, vIndex) => (
366
- <ValidationMessage
367
- key={vIndex}
368
- message={validation.message}
369
- severity={validation.severity}
370
- show={true}
371
- />
372
- ))}
373
- </div>
374
- );
375
- };
376
-
377
- return (
378
- <Stack
379
- spacing={1}
380
- >
381
- <Divider />
382
- <Typography component="label" sx={{ color: '#8E8E93' }}>
383
- {label}
384
- </Typography>
385
-
386
- {/* Type Selection */}
387
- <StyledSelect
388
- value={isOperation ? 'operation' : 'primitive'}
389
- onChange={handleTypeChange}
390
- >
391
- <option value="primitive" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('expression.primitive')}</option>
392
- <option value="operation" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('expression.operation')}</option>
393
- </StyledSelect>
394
-
395
- {/* Primitive or Operation Input */}
396
- {!isOperation ? (
397
- renderPrimitiveInput()
398
- ) : (
399
- <Stack spacing={1}>
400
- {/* Operation Selection */}
401
- <StyledSelect
402
- value={(value as OperationExpression).operation || '+'}
403
- onChange={handleOperationTypeChange}
404
- >
405
- <option value="+" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.add')}</option>
406
- <option value="-" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.subtract')}</option>
407
- <option value="*" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.multiply')}</option>
408
- <option value="/" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.divide')}</option>
409
- <option value=">" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.greaterThan')}</option>
410
- <option value=">=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.greaterThanOrEqual')}</option>
411
- <option value="<" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.lessThan')}</option>
412
- <option value="<=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.lessThanOrEqual')}</option>
413
- <option value="=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.assign')}</option>
414
- <option value="==" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.equal')}</option>
415
- <option value="!=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.notEqual')}</option>
416
- <option value="neg" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.negate')}</option>
417
- <option value="not" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.not')}</option>
418
- <option value="and" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.and')}</option>
419
- <option value="or" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.or')}</option>
420
- </StyledSelect>
421
-
422
- {/* Operand Inputs */}
423
- <OperatorContainer>
424
- {(value as OperationExpression).operands?.map((operand, index) => (
425
- <OperandWrapper key={index}>
426
- {renderOperandInput(operand || '', index)}
427
- </OperandWrapper>
428
- )) || []}
429
- </OperatorContainer>
430
- </Stack>
431
- )}
432
- <Divider />
433
- </Stack>
434
- );
435
- };
436
-
437
- export default ExpressionInput;
1
+ import React, { useState, useEffect } from 'react';
2
+ import styled from '@emotion/styled';
3
+ import { Divider, Stack, Typography } from '@mui/material';
4
+ import { Expression, OperationExpression } from '../types/ndoe-form-types';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { BlockTypesKeys } from '../organisms/Card/card.params';
7
+ import ValidationMessage from './ValidationMessage';
8
+ import { validationEngine } from '../utils/validationEngine';
9
+ import { ValidationResult } from '../types/validation-types';
10
+
11
+ // Styled Components
12
+ export const StyledSelect: ReturnType<typeof styled.select> = styled.select`
13
+ width: 100%;
14
+ padding: 8px;
15
+ font-size: 14px;
16
+ border-radius: 4px;
17
+ border: 1px solid #ccc;
18
+ background-color: transparent;
19
+ color: #FFFFFF;
20
+
21
+ &:hover {
22
+ border-color: #000000;
23
+ }
24
+
25
+ &:focus {
26
+ outline: none;
27
+ border-color: #1976d2;
28
+ }
29
+ `;
30
+
31
+ export const StyledInput: ReturnType<typeof styled.input> = styled.input`
32
+ width: 100%;
33
+ padding: 8px;
34
+ font-size: 14px;
35
+ border-radius: 4px;
36
+ border: 1px solid #ccc;
37
+ background-color: transparent;
38
+ color: #FFFFFF;
39
+
40
+ &:hover {
41
+ border-color: #000000;
42
+ }
43
+
44
+ &:focus {
45
+ outline: none;
46
+ border-color: #1976d2;
47
+ }
48
+ `;
49
+
50
+ // New styled components for the integrated input with dropdown
51
+ const InputWithDropdownContainer = styled.div`
52
+ position: relative;
53
+ display: flex;
54
+ align-items: center;
55
+ width: 100%;
56
+ `;
57
+
58
+ const StyledInputWithDropdown = styled.input`
59
+ width: 100%;
60
+ padding: 8px;
61
+ padding-right: 110px; /* Make room for dropdown */
62
+ font-size: 14px;
63
+ border-radius: 4px;
64
+ border: 1px solid #ccc;
65
+ background-color: transparent;
66
+ color: #FFFFFF;
67
+
68
+ &:hover {
69
+ border-color: #000000;
70
+ }
71
+
72
+ &:focus {
73
+ outline: none;
74
+ border-color: #1976d2;
75
+ }
76
+ `;
77
+
78
+ const DropdownOverlay = styled.div`
79
+ position: absolute;
80
+ right: 2px;
81
+ top: 50%;
82
+ transform: translateY(-50%);
83
+ z-index: 10;
84
+ pointer-events: auto;
85
+ width: 100px;
86
+ `;
87
+
88
+ const StyledSelectOverlay = styled.select`
89
+ min-width: 100px;
90
+ height: 32px;
91
+ background-color: transparent;
92
+ cursor: pointer;
93
+ border: none;
94
+ outline: none;
95
+ padding: 4px 8px;
96
+ font-size: 12px;
97
+ color: #FFFFFF;
98
+ text-align: center;
99
+ border-radius: 4px;
100
+
101
+ &:hover {
102
+ background-color: rgba(255, 255, 255, 0.1);
103
+ }
104
+
105
+ &:focus {
106
+ background-color: rgba(255, 255, 255, 0.15);
107
+ }
108
+
109
+ option {
110
+ background-color: #393939;
111
+ color: #FFFFFF;
112
+ padding: 8px;
113
+ }
114
+ `;
115
+
116
+ const OperatorContainer = styled.div`
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: space-between;
120
+ gap: 10px;
121
+ `;
122
+
123
+ const OperandWrapper = styled.div`
124
+ flex: 1;
125
+ `;
126
+
127
+ interface ExpressionInputProps {
128
+ value: Expression;
129
+ onChange: (newValue: Expression) => void;
130
+ label: string;
131
+ className?: string;
132
+ showDataTypeDropdown?: boolean;
133
+ dataType?: string;
134
+ onDataTypeChange?: (dataType: string) => void;
135
+ onValidationChange?: (isValid: boolean, errors: string[]) => void;
136
+ }
137
+
138
+ const operationOperandCounts: { [key: string]: number } = {
139
+ '+': 2,
140
+ '-': 2,
141
+ '*': 2,
142
+ '/': 2,
143
+ '>': 2,
144
+ '>=': 2,
145
+ '<': 2,
146
+ '<=': 2,
147
+ '=': 2,
148
+ '==': 2,
149
+ '!=': 2,
150
+ neg: 1,
151
+ not: 1,
152
+ and: 2,
153
+ or: 2,
154
+ };
155
+
156
+ const ExpressionInput: React.FC<ExpressionInputProps> = ({
157
+ value,
158
+ onChange,
159
+ label,
160
+ className = '',
161
+ showDataTypeDropdown = false,
162
+ dataType = 'String',
163
+ onDataTypeChange,
164
+ onValidationChange,
165
+ }) => {
166
+ const { t } = useTranslation();
167
+ const isOperation = value && typeof value === 'object' && 'operation' in value;
168
+
169
+ // State for validation results
170
+ const [operandValidations, setOperandValidations] = useState<ValidationResult[][]>([]);
171
+ const [showOperandValidations, setShowOperandValidations] = useState<boolean[]>([]);
172
+ const [primitiveValidations, setPrimitiveValidations] = useState<ValidationResult[]>([]);
173
+ const [showPrimitiveValidations, setShowPrimitiveValidations] = useState<boolean>(false);
174
+
175
+ // Validate primitive value using validation engine
176
+ const validatePrimitiveValue = (value: string): ValidationResult[] => {
177
+ return validationEngine.validatePrimitiveValue(value, dataType);
178
+ };
179
+
180
+ // Re-validate primitive value when data type changes
181
+ useEffect(() => {
182
+ if (!isOperation && typeof value === 'string') {
183
+ const validations = validatePrimitiveValue(value);
184
+ setPrimitiveValidations(validations);
185
+ setShowPrimitiveValidations(validations.length > 0);
186
+ }
187
+ }, [dataType, value, isOperation]);
188
+
189
+ const handlePrimitiveChange = (e: React.ChangeEvent<HTMLInputElement>) => {
190
+ const newValue = e.target.value;
191
+ onChange(newValue); // Update the parent directly
192
+
193
+ // Validate and show validation results if needed
194
+ const validations = validatePrimitiveValue(newValue);
195
+ setPrimitiveValidations(validations);
196
+ setShowPrimitiveValidations(validations.length > 0);
197
+
198
+ // Notify parent component about validation state
199
+ if (onValidationChange) {
200
+ const hasErrors = validationEngine.hasErrors(validations);
201
+ const errorMessages = validationEngine.getErrors(validations).map(v => v.message);
202
+ onValidationChange(!hasErrors, errorMessages);
203
+ }
204
+ };
205
+
206
+ const handleOperationTypeChange = (
207
+ e: React.ChangeEvent<HTMLSelectElement>,
208
+ ) => {
209
+ const newOperation = e.target.value;
210
+ const operandCount = operationOperandCounts[newOperation] || 2;
211
+ const newOperands = Array(operandCount).fill('');
212
+ onChange({ operation: newOperation, operands: newOperands });
213
+
214
+ // Reset validation states for new operation
215
+ setOperandValidations(Array(operandCount).fill([]));
216
+ setShowOperandValidations(Array(operandCount).fill(false));
217
+ };
218
+
219
+ // Validate operands when they change
220
+ useEffect(() => {
221
+ if (isOperation) {
222
+ const operationValue = value as OperationExpression;
223
+ const validations: ValidationResult[][] = [];
224
+ const showValidations: boolean[] = [];
225
+
226
+ operationValue.operands?.forEach((operand, index) => {
227
+ // Use validation engine to validate operand
228
+ const operandValidations = validationEngine.validateOperationOperands([operand], dataType);
229
+ validations.push(operandValidations);
230
+
231
+ // Show validation if operand has been interacted with (has value or was touched)
232
+ showValidations.push(!!operand || showOperandValidations[index] || false);
233
+ });
234
+
235
+ setOperandValidations(validations);
236
+ setShowOperandValidations(showValidations);
237
+
238
+ // Notify parent component about validation state
239
+ if (onValidationChange) {
240
+ const allValidations = validations.flat();
241
+ const hasErrors = validationEngine.hasErrors(allValidations);
242
+ const errorMessages = validationEngine.getErrors(allValidations).map(v => v.message);
243
+ onValidationChange(!hasErrors, errorMessages);
244
+ }
245
+ }
246
+ }, [value, dataType, isOperation, showOperandValidations, onValidationChange]);
247
+
248
+ const handleOperandChange = (index: number, operandValue: string) => {
249
+ if (isOperation) {
250
+ const operationValue = value as OperationExpression;
251
+ const updatedOperands = [...operationValue.operands];
252
+ updatedOperands[index] = operandValue;
253
+
254
+ // Mark this operand as touched for validation display
255
+ const newShowValidations = [...showOperandValidations];
256
+ newShowValidations[index] = true;
257
+ setShowOperandValidations(newShowValidations);
258
+
259
+ // Clear validation when user starts typing
260
+ if (operandValidations[index] && operandValidations[index].length > 0) {
261
+ const newValidations = [...operandValidations];
262
+ newValidations[index] = [];
263
+ setOperandValidations(newValidations);
264
+ }
265
+
266
+ onChange({ ...operationValue, operands: updatedOperands });
267
+ }
268
+ };
269
+
270
+ const handleTypeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
271
+ const selectedValue = e.target.value;
272
+ if (selectedValue === 'primitive') {
273
+ onChange(String(value ?? '')); // Switch to primitive
274
+ // Clear operand validations when switching to primitive
275
+ setOperandValidations([]);
276
+ setShowOperandValidations([]);
277
+ // Clear primitive validations
278
+ setPrimitiveValidations([]);
279
+ setShowPrimitiveValidations(false);
280
+ } else {
281
+ // Always initialize with a proper operation structure when switching to operation mode
282
+ onChange({ operation: '+', operands: ['', ''] });
283
+ // Initialize validation states for new operation
284
+ setOperandValidations([[], []]); // Start with empty validations
285
+ setShowOperandValidations([false, false]); // Don't show validations initially
286
+ // Clear primitive validations
287
+ setPrimitiveValidations([]);
288
+ setShowPrimitiveValidations(false);
289
+ }
290
+ };
291
+
292
+ const renderOperandInput = (operand: string, index: number) => (
293
+ <div>
294
+ <StyledInput
295
+ type="text"
296
+ value={operand}
297
+ onChange={(e) => handleOperandChange(index, e.target.value)}
298
+ placeholder={`Operand ${index + 1}`}
299
+ style={{
300
+ borderColor: showOperandValidations[index] && operandValidations[index]?.some(v => v.severity === 'error') ? '#d32f2f' : undefined
301
+ }}
302
+ />
303
+ {showOperandValidations[index] && operandValidations[index]?.map((validation, vIndex) => (
304
+ <ValidationMessage
305
+ key={vIndex}
306
+ message={validation.message}
307
+ severity={validation.severity}
308
+ show={true}
309
+ />
310
+ ))}
311
+ </div>
312
+ );
313
+
314
+ const renderPrimitiveInput = () => {
315
+ if (showDataTypeDropdown) {
316
+ return (
317
+ <div>
318
+ <InputWithDropdownContainer>
319
+ <StyledInputWithDropdown
320
+ className="zd__primitive-input-with-dropdown"
321
+ type="text"
322
+ value={String(value ?? '')}
323
+ onChange={handlePrimitiveChange}
324
+ placeholder="Enter value"
325
+ style={{
326
+ borderColor: showPrimitiveValidations ? '#d32f2f' : undefined
327
+ }}
328
+ />
329
+ <DropdownOverlay>
330
+ <StyledSelectOverlay
331
+ value={dataType}
332
+ onChange={(e) => onDataTypeChange?.(e.target.value)}
333
+ >
334
+ {BlockTypesKeys.map((type) => (
335
+ <option key={type} value={type}>
336
+ {type}
337
+ </option>
338
+ ))}
339
+ </StyledSelectOverlay>
340
+ </DropdownOverlay>
341
+ </InputWithDropdownContainer>
342
+ {showPrimitiveValidations && primitiveValidations.map((validation, vIndex) => (
343
+ <ValidationMessage
344
+ key={vIndex}
345
+ message={validation.message}
346
+ severity={validation.severity}
347
+ show={true}
348
+ />
349
+ ))}
350
+ </div>
351
+ );
352
+ }
353
+
354
+ return (
355
+ <div>
356
+ <StyledInput
357
+ type="text"
358
+ value={String(value ?? '')}
359
+ onChange={handlePrimitiveChange}
360
+ placeholder="Enter value"
361
+ style={{
362
+ borderColor: showPrimitiveValidations ? '#d32f2f' : undefined
363
+ }}
364
+ />
365
+ {showPrimitiveValidations && primitiveValidations.map((validation, vIndex) => (
366
+ <ValidationMessage
367
+ key={vIndex}
368
+ message={validation.message}
369
+ severity={validation.severity}
370
+ show={true}
371
+ />
372
+ ))}
373
+ </div>
374
+ );
375
+ };
376
+
377
+ return (
378
+ <Stack
379
+ spacing={1}
380
+ >
381
+ <Divider />
382
+ <Typography component="label" sx={{ color: '#8E8E93' }}>
383
+ {label}
384
+ </Typography>
385
+
386
+ {/* Type Selection */}
387
+ <StyledSelect
388
+ value={isOperation ? 'operation' : 'primitive'}
389
+ onChange={handleTypeChange}
390
+ >
391
+ <option value="primitive" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('expression.primitive')}</option>
392
+ <option value="operation" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('expression.operation')}</option>
393
+ </StyledSelect>
394
+
395
+ {/* Primitive or Operation Input */}
396
+ {!isOperation ? (
397
+ renderPrimitiveInput()
398
+ ) : (
399
+ <Stack spacing={1}>
400
+ {/* Operation Selection */}
401
+ <StyledSelect
402
+ value={(value as OperationExpression).operation || '+'}
403
+ onChange={handleOperationTypeChange}
404
+ >
405
+ <option value="+" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.add')}</option>
406
+ <option value="-" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.subtract')}</option>
407
+ <option value="*" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.multiply')}</option>
408
+ <option value="/" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.divide')}</option>
409
+ <option value=">" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.greaterThan')}</option>
410
+ <option value=">=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.greaterThanOrEqual')}</option>
411
+ <option value="<" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.lessThan')}</option>
412
+ <option value="<=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.lessThanOrEqual')}</option>
413
+ <option value="=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.assign')}</option>
414
+ <option value="==" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.equal')}</option>
415
+ <option value="!=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.notEqual')}</option>
416
+ <option value="neg" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.negate')}</option>
417
+ <option value="not" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.not')}</option>
418
+ <option value="and" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.and')}</option>
419
+ <option value="or" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.or')}</option>
420
+ </StyledSelect>
421
+
422
+ {/* Operand Inputs */}
423
+ <OperatorContainer>
424
+ {(value as OperationExpression).operands?.map((operand, index) => (
425
+ <OperandWrapper key={index}>
426
+ {renderOperandInput(operand || '', index)}
427
+ </OperandWrapper>
428
+ )) || []}
429
+ </OperatorContainer>
430
+ </Stack>
431
+ )}
432
+ <Divider />
433
+ </Stack>
434
+ );
435
+ };
436
+
437
+ export default ExpressionInput;