df-ae-forms-package 1.1.4 → 1.1.6

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/dist/index.esm.js CHANGED
@@ -818,7 +818,6 @@ const DfFormInput = ({ id, properties, validationErrors = {}, formValue = '', in
818
818
  // Update ref if ID changes (shouldn't happen, but safety check)
819
819
  useEffect(() => {
820
820
  if (id !== componentIdRef.current) {
821
- console.warn(`[DfFormInput] Component ID changed from ${componentIdRef.current} to ${id}`);
822
821
  componentIdRef.current = id;
823
822
  }
824
823
  }, [id]);
@@ -2402,6 +2401,7 @@ const DfFormFileUpload = ({ id, properties, validationErrors = {}, formValue = n
2402
2401
  const [isDragOver, setIsDragOver] = useState(false);
2403
2402
  const [isTouched, setIsTouched] = useState(false);
2404
2403
  const fileInputRef = useRef(null);
2404
+ const lastFormValueRef = useRef(null); // Track the last formValue to prevent unnecessary resets
2405
2405
  // Convert FileList or File[] to IFilePreview[]
2406
2406
  const convertToFilePreviews = useCallback((fileList, startIndex = 0) => {
2407
2407
  if (!fileList)
@@ -2494,12 +2494,20 @@ const DfFormFileUpload = ({ id, properties, validationErrors = {}, formValue = n
2494
2494
  if (fileObject.url || fileObject.path) {
2495
2495
  preview = fileObject.url || fileObject.path;
2496
2496
  }
2497
- else if (fileType.startsWith('image/') && fileObject.data) {
2498
- // Handle base64 data
2497
+ else if (fileObject.data) {
2498
+ // Handle base64 data - works for all file types with data
2499
+ const resolvedType = fileType || 'application/octet-stream';
2499
2500
  const fileData = fileObject.data;
2500
- preview = typeof fileData === 'string'
2501
- ? (fileData.startsWith('data:') ? fileData : `data:${fileType};base64,${fileData}`)
2502
- : undefined;
2501
+ if (typeof fileData === 'string') {
2502
+ if (fileData.startsWith('data:')) {
2503
+ // Already a full data URI
2504
+ preview = fileData;
2505
+ }
2506
+ else {
2507
+ // Raw base64 - construct data URI
2508
+ preview = `data:${resolvedType};base64,${fileData}`;
2509
+ }
2510
+ }
2503
2511
  }
2504
2512
  // Use the original object to preserve data
2505
2513
  fileObj = fileObject;
@@ -2684,10 +2692,35 @@ const DfFormFileUpload = ({ id, properties, validationErrors = {}, formValue = n
2684
2692
  }
2685
2693
  }, [mode]);
2686
2694
  // Update value when formValue prop changes or on mount
2695
+ // CRITICAL: Use a stable reference check to prevent unnecessary resets
2687
2696
  useEffect(() => {
2688
- // Always convert formValue, even if it's null/undefined (will return empty array)
2697
+ // Skip if formValue is null/undefined (no data)
2698
+ if (formValue === null || formValue === undefined || (typeof formValue === 'string' && formValue === '')) {
2699
+ // Only clear files if we previously had files from formValue
2700
+ // Don't clear files that were added by user interaction
2701
+ if (lastFormValueRef.current !== null && lastFormValueRef.current !== undefined && lastFormValueRef.current !== '') {
2702
+ setFiles([]);
2703
+ lastFormValueRef.current = null;
2704
+ }
2705
+ return;
2706
+ }
2707
+ // Check if formValue actually changed (deep comparison for arrays)
2708
+ try {
2709
+ const currentStr = JSON.stringify(formValue);
2710
+ const prevStr = JSON.stringify(lastFormValueRef.current);
2711
+ if (currentStr === prevStr) {
2712
+ return; // No change, skip
2713
+ }
2714
+ }
2715
+ catch {
2716
+ // If stringify fails, proceed with update
2717
+ }
2718
+ // Convert and set new files
2689
2719
  const newFiles = convertToFilePreviews(formValue);
2690
- setFiles(newFiles);
2720
+ if (newFiles.length > 0) {
2721
+ setFiles(newFiles);
2722
+ lastFormValueRef.current = formValue;
2723
+ }
2691
2724
  }, [formValue, convertToFilePreviews]);
2692
2725
  // Mark as touched when form is submitted
2693
2726
  useEffect(() => {
@@ -2962,8 +2995,6 @@ const DfFormLocation = ({ id, properties, validationErrors = {}, formValue = nul
2962
2995
  }
2963
2996
  }
2964
2997
  catch (error) {
2965
- // Capacitor not properly initialized, fall through to web geolocation
2966
- console.warn('Capacitor Geolocation not available, falling back to web geolocation');
2967
2998
  }
2968
2999
  }
2969
3000
  // Fallback to standard web geolocation API (for browsers)
@@ -3677,7 +3708,6 @@ const AttachmentThumbnails = ({ attachments, onRemove }) => {
3677
3708
  newUrls.set(index, url);
3678
3709
  }
3679
3710
  catch (e) {
3680
- console.warn('Failed to create object URL for attachment:', e);
3681
3711
  }
3682
3712
  }
3683
3713
  else if (file && file.url && file.type && file.type.startsWith('image/')) {
@@ -4588,7 +4618,6 @@ formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChan
4588
4618
  zIndex: 5
4589
4619
  }, children: jsxs("button", { onClick: (e) => {
4590
4620
  e.stopPropagation();
4591
- console.log('[TableView] Add Entry button clicked (grid view)', 'entries:', dataEntries.length, 'max:', maxEntries);
4592
4621
  onAddEntry();
4593
4622
  }, disabled: dataEntries.length >= maxEntries, style: {
4594
4623
  padding: '8px 16px',
@@ -4714,7 +4743,6 @@ formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChan
4714
4743
  justifyContent: 'center'
4715
4744
  }, children: jsxs("button", { onClick: (e) => {
4716
4745
  e.stopPropagation();
4717
- console.log('[TableView] Add Entry button clicked (list view)', 'entries:', dataEntries.length, 'max:', maxEntries);
4718
4746
  onAddEntry();
4719
4747
  }, disabled: dataEntries.length >= maxEntries, style: {
4720
4748
  padding: '8px 16px',
@@ -4967,12 +4995,8 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4967
4995
  // In edit/preview modes, don't handle value changes as components are read-only
4968
4996
  }, [mode, onValueChange]);
4969
4997
  const handleAddEntry = useCallback(() => {
4970
- console.log('[DfFormDataGrid] handleAddEntry called - Component ID:', id);
4971
4998
  // Safety check: ensure we have entries array
4972
4999
  const currentEntries = Array.isArray(properties.entries) ? properties.entries : [];
4973
- console.log('[DfFormDataGrid] gridComponents:', gridComponents.length);
4974
- console.log('[DfFormDataGrid] current entries count:', currentEntries.length);
4975
- console.log('[DfFormDataGrid] onValueChange exists:', !!onValueChange);
4976
5000
  // Use timestamp and random string to ensure uniqueness even if entries are deleted
4977
5001
  const uniqueSuffix = `${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
4978
5002
  const newEntryId = `entry-${uniqueSuffix}`;
@@ -4994,11 +5018,8 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4994
5018
  }),
4995
5019
  styles: {}
4996
5020
  };
4997
- console.log('[DfFormDataGrid] newEntry created:', newEntry.id, 'with', newEntry.components.length, 'components');
4998
5021
  const updatedEntries = [...currentEntries, newEntry];
4999
- console.log('[DfFormDataGrid] updatedEntries count:', updatedEntries.length);
5000
5022
  if (onValueChange) {
5001
- console.log('[DfFormDataGrid] calling onValueChange with updated datagrid structure - Entries:', updatedEntries.length);
5002
5023
  onValueChange({
5003
5024
  id: gridId, // Use sanitized gridId
5004
5025
  value: {
@@ -5008,13 +5029,9 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
5008
5029
  });
5009
5030
  // Notify parent if callback provided
5010
5031
  if (onEntryAdd) {
5011
- console.log('[DfFormDataGrid] calling onEntryAdd callback');
5012
5032
  onEntryAdd();
5013
5033
  }
5014
5034
  }
5015
- else {
5016
- console.warn('[DfFormDataGrid] Cannot add entry: onValueChange is missing');
5017
- }
5018
5035
  }, [onValueChange, properties, gridId, gridComponents, onEntryAdd]);
5019
5036
  const handleRemoveEntry = useCallback((entryIndex) => {
5020
5037
  // Safety check: ensure we have entries array
@@ -5023,7 +5040,6 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
5023
5040
  .filter((_, index) => index !== entryIndex)
5024
5041
  .map((entry, index) => ({ ...entry, index })); // Only update index, preserve unique ID
5025
5042
  if (onValueChange) {
5026
- console.log('[DfFormDataGrid] Removing entry at index:', entryIndex, 'New count:', updatedEntries.length);
5027
5043
  onValueChange({
5028
5044
  id: gridId, // Use sanitized gridId
5029
5045
  value: { ...properties, entries: updatedEntries }
@@ -5411,8 +5427,8 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5411
5427
  const [validationErrors, setValidationErrors] = useState({});
5412
5428
  const [formSubmitted, setFormSubmitted] = useState(false);
5413
5429
  const [touchedFields, setTouchedFields] = useState({});
5414
- // Conditional logic disabled temporarily
5415
- // const [componentVisibility, setComponentVisibility] = useState<Record<string, boolean>>({})
5430
+ // Component visibility state - driven by conditional logic evaluation
5431
+ const [componentVisibility, setComponentVisibility] = useState({});
5416
5432
  // Track raised issues for threshold conditions (Set of condition IDs)
5417
5433
  const [raisedThresholdIssues, setRaisedThresholdIssues] = useState(new Set());
5418
5434
  // Track threshold action completions: Map<conditionId, { notesCompleted, attachmentsCompleted, emailSent }>
@@ -5431,7 +5447,38 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5431
5447
  const initializeComponentValues = (components, values) => {
5432
5448
  components.forEach(component => {
5433
5449
  const componentId = ensureStringId$1(component.id);
5434
- if (componentId) {
5450
+ if (!componentId)
5451
+ return; // Skip components without valid IDs
5452
+ // CRITICAL: Handle file component FIRST — file data is an array, not a simple string
5453
+ // The general handler below would misinterpret an empty file array
5454
+ if (component.name === 'file' && component.basic) {
5455
+ // Don't overwrite if already initialized
5456
+ if (values[componentId] !== undefined && values[componentId] !== '' && values[componentId] !== null) ;
5457
+ else {
5458
+ // Check all possible locations where file data could be stored
5459
+ const fileData = component.basic.value ||
5460
+ component.basic.files ||
5461
+ component.basic.attachments;
5462
+ if (fileData && (Array.isArray(fileData) ? fileData.length > 0 : true)) {
5463
+ values[componentId] = fileData;
5464
+ }
5465
+ else {
5466
+ values[componentId] = null; // Use null, not empty string, for file components
5467
+ }
5468
+ }
5469
+ }
5470
+ // Handle instructions component
5471
+ else if (component.name === 'instructions' && component.basic) {
5472
+ if (!component.basic.instructions) {
5473
+ component.basic.instructions = [];
5474
+ }
5475
+ const instructionValue = component.basic.value || component.basic.instructions;
5476
+ if (instructionValue) {
5477
+ values[componentId] = instructionValue;
5478
+ }
5479
+ }
5480
+ // General handler for all other components
5481
+ else {
5435
5482
  // ALWAYS prioritize existing form state (values param) if it exists
5436
5483
  if (values[componentId] !== undefined) ;
5437
5484
  // Then use captured value in basic.value
@@ -5446,10 +5493,10 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5446
5493
  else {
5447
5494
  // For checkbox and multi-select, empty array
5448
5495
  if (component.name === 'checkbox' || component.name === 'select') {
5449
- values[component.id] = [];
5496
+ values[componentId] = [];
5450
5497
  }
5451
5498
  else {
5452
- values[component.id] = '';
5499
+ values[componentId] = '';
5453
5500
  }
5454
5501
  }
5455
5502
  }
@@ -5471,35 +5518,19 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5471
5518
  }
5472
5519
  });
5473
5520
  }
5474
- // Handle file component - initialize with file data if present
5475
- if (component.name === 'file' && component.basic) {
5476
- const fileData = component.basic.files || component.basic.attachments || component.basic.value;
5477
- if (fileData) {
5478
- values[component.id] = fileData;
5479
- }
5480
- }
5481
5521
  // Initialize notes and attachments
5482
- if (component.id && component.basic) {
5522
+ if (componentId && component.basic) {
5483
5523
  if (component.basic.notes) ;
5484
5524
  }
5485
- // Handle instructions component - ensure instructions array exists and initialize formValue
5486
- if (component.name === 'instructions' && component.basic) {
5487
- if (!component.basic.instructions) {
5488
- // Initialize empty instructions array if not present
5489
- component.basic.instructions = [];
5490
- }
5491
- // CRITICAL: Initialize formValue for instructions from API data
5492
- // Check if component has value from API (could be in basic.value, basic.instructions, or formData)
5493
- const instructionValue = component.basic.value || component.basic.instructions;
5494
- if (instructionValue) {
5495
- // Store instruction data in formValues so it can be passed to DfFormInstruction
5496
- values[component.id] = instructionValue;
5497
- }
5498
- }
5499
5525
  // Handle nested components in section children
5500
5526
  if (component.children && Array.isArray(component.children)) {
5501
5527
  initializeComponentValues(component.children, values);
5502
5528
  }
5529
+ // CRITICAL for conditional logic: Also store value under the label key
5530
+ // so condition lookups by label (e.g. "Temperature") work during init
5531
+ if (component.basic?.label && componentId && values[componentId] !== undefined) {
5532
+ values[component.basic.label] = values[componentId];
5533
+ }
5503
5534
  });
5504
5535
  };
5505
5536
  const initializeFormState = useCallback(() => {
@@ -5571,15 +5602,12 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5571
5602
  const labelPart = safeLabel ? `-${safeLabel}` : '';
5572
5603
  validatedComponent.id = `${name}${labelPart}-${index}`;
5573
5604
  }
5574
- console.warn(`[DfFormPreview] Fixed missing/invalid ID: ${validatedComponent.id}`);
5575
5605
  }
5576
5606
  else {
5577
5607
  // ID is a valid string, check for duplicates
5578
5608
  if (seenIds.has(validatedComponent.id)) {
5579
- console.error(`[DfFormPreview] Duplicate component ID detected: ${validatedComponent.id}`);
5580
5609
  // Generate a unique ID for duplicate - using index to keep it somewhat stable
5581
5610
  validatedComponent.id = `${validatedComponent.id}-dup-${index}`;
5582
- console.warn(`[DfFormPreview] Generated new unique ID: ${validatedComponent.id}`);
5583
5611
  }
5584
5612
  }
5585
5613
  seenIds.add(validatedComponent.id);
@@ -5634,7 +5662,6 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5634
5662
  validatedFormComponents = getValidatedComponents(localFormComponents);
5635
5663
  // Synchronize local state if components were mutated (IDs added/fixed)
5636
5664
  if (JSON.stringify(validatedFormComponents) !== JSON.stringify(localFormComponents)) {
5637
- console.log('[DfFormPreview] Synchronizing local components after ID validation');
5638
5665
  setLocalFormComponents(validatedFormComponents);
5639
5666
  onFormDataChange?.(validatedFormComponents);
5640
5667
  }
@@ -5655,19 +5682,261 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5655
5682
  }
5656
5683
  // Set the state
5657
5684
  setFormValues(prev => {
5658
- // Merge with previous state to avoid blowing away user input on re-renders
5659
- return { ...initialValues, ...prev };
5685
+ // Smart merge: initial values should win over empty/stale prev values
5686
+ // but user-entered values in prev should win over initial defaults
5687
+ const merged = { ...initialValues };
5688
+ Object.keys(prev).forEach(key => {
5689
+ const prevVal = prev[key];
5690
+ const initVal = initialValues[key];
5691
+ // If prev has a meaningful value (not empty/null/undefined), keep it
5692
+ if (prevVal !== undefined && prevVal !== null && prevVal !== '') {
5693
+ // But if initVal is an array (like file data) and prevVal is a simple empty value,
5694
+ // prefer the initialValue
5695
+ if (Array.isArray(initVal) && initVal.length > 0 && !Array.isArray(prevVal)) {
5696
+ // initialValue is a populated array but prev is not — keep initial
5697
+ merged[key] = initVal;
5698
+ }
5699
+ else {
5700
+ merged[key] = prevVal;
5701
+ }
5702
+ }
5703
+ // If prev has empty string but initial has actual data, use initial
5704
+ else if (initVal !== undefined && initVal !== null && initVal !== '') {
5705
+ merged[key] = initVal;
5706
+ }
5707
+ // Otherwise keep prev (even if empty — preserves user clearing a field)
5708
+ else {
5709
+ merged[key] = prevVal;
5710
+ }
5711
+ });
5712
+ return merged;
5660
5713
  });
5661
5714
  // Initialize notes and attachments state
5662
5715
  setComponentNotes(prev => ({ ...initialNotes, ...prev }));
5663
5716
  setComponentAttachments(prev => ({ ...initialAttachments, ...prev }));
5664
- // Conditional logic evaluation disabled temporarily to fix table issues
5665
- // evaluateConditionalLogic()
5717
+ // Evaluate conditional logic on initialization
5718
+ evaluateConditionalLogic(initialValues);
5666
5719
  }, [initialFormData, localFormComponents]);
5667
- // Conditional logic disabled temporarily - all components are always visible
5668
- const shouldShowComponent = useCallback((_componentId) => {
5669
- return true;
5720
+ // ======================== CONDITIONAL LOGIC ENGINE ========================
5721
+ // This is a pure visibility layer. It NEVER modifies component data.
5722
+ // It computes a { [componentId]: boolean } map based on each component's
5723
+ // `logic` property and the current form values.
5724
+ // Helper: Flatten all components (including nested) into a flat list
5725
+ const flattenAllComponents = useCallback((components) => {
5726
+ const flat = [];
5727
+ const traverse = (items) => {
5728
+ if (!items || !Array.isArray(items))
5729
+ return;
5730
+ for (const item of items) {
5731
+ flat.push(item);
5732
+ if (item.children && Array.isArray(item.children))
5733
+ traverse(item.children);
5734
+ if (item.cells && Array.isArray(item.cells)) {
5735
+ for (const row of item.cells) {
5736
+ const normalizedRow = normalizeTableRow(row);
5737
+ for (const cell of normalizedRow) {
5738
+ if (cell.components)
5739
+ traverse(cell.components);
5740
+ }
5741
+ }
5742
+ }
5743
+ if (item.entries && Array.isArray(item.entries)) {
5744
+ for (const entry of item.entries) {
5745
+ if (entry.components)
5746
+ traverse(entry.components);
5747
+ }
5748
+ }
5749
+ if (item.templateComponents && Array.isArray(item.templateComponents)) {
5750
+ traverse(item.templateComponents);
5751
+ }
5752
+ }
5753
+ };
5754
+ traverse(components);
5755
+ return flat;
5756
+ }, []);
5757
+ // Helper: Get the value of a component referenced by a condition's `when` field.
5758
+ // The `when` field is typically a component LABEL (e.g. "Temperature"), not an ID.
5759
+ const getConditionComponentValue = useCallback((conditionWhen, allComponents, currentFormValues) => {
5760
+ // Try to find the component by label, id, or _id
5761
+ const comp = allComponents.find(c => c.basic?.label === conditionWhen ||
5762
+ ensureStringId$1(c.id) === conditionWhen ||
5763
+ ensureStringId$1(c._id) === conditionWhen);
5764
+ if (!comp)
5765
+ return undefined;
5766
+ const compId = ensureStringId$1(comp.id || comp._id);
5767
+ // Check formValues by ID first
5768
+ if (compId && currentFormValues[compId] !== undefined) {
5769
+ return currentFormValues[compId];
5770
+ }
5771
+ // Also check by label (main website stores values by label too)
5772
+ if (comp.basic?.label && currentFormValues[comp.basic.label] !== undefined) {
5773
+ return currentFormValues[comp.basic.label];
5774
+ }
5775
+ // Fall back to component's basic.value or defaultValue
5776
+ if (comp.basic?.value !== undefined && comp.basic?.value !== null && comp.basic?.value !== '') {
5777
+ return comp.basic.value;
5778
+ }
5779
+ if (comp.basic?.defaultValue !== undefined && comp.basic?.defaultValue !== null && comp.basic?.defaultValue !== '') {
5780
+ return comp.basic.defaultValue;
5781
+ }
5782
+ return undefined;
5783
+ }, []);
5784
+ // Helper: Evaluate a single condition
5785
+ const evaluateSingleCondition = useCallback((condition, componentValue) => {
5786
+ const { operator, value } = condition;
5787
+ // isEmpty / isNotEmpty
5788
+ if (operator === 'isEmpty') {
5789
+ if (componentValue == null)
5790
+ return true;
5791
+ if (typeof componentValue === 'string')
5792
+ return componentValue.trim() === '';
5793
+ if (Array.isArray(componentValue))
5794
+ return componentValue.length === 0;
5795
+ return false;
5796
+ }
5797
+ if (operator === 'isNotEmpty') {
5798
+ if (componentValue == null)
5799
+ return false;
5800
+ if (typeof componentValue === 'string')
5801
+ return componentValue.trim() !== '';
5802
+ if (Array.isArray(componentValue))
5803
+ return componentValue.length > 0;
5804
+ return true;
5805
+ }
5806
+ // checked / notChecked (for checkboxes)
5807
+ if (operator === 'checked' || operator === 'notChecked') {
5808
+ let isChecked = false;
5809
+ if (typeof componentValue === 'boolean')
5810
+ isChecked = componentValue;
5811
+ else if (typeof componentValue === 'string')
5812
+ isChecked = componentValue.toLowerCase() === 'true' || componentValue === '1' || componentValue.length > 0;
5813
+ else if (Array.isArray(componentValue))
5814
+ isChecked = componentValue.length > 0;
5815
+ else if (typeof componentValue === 'number')
5816
+ isChecked = componentValue > 0;
5817
+ return operator === 'checked' ? isChecked : !isChecked;
5818
+ }
5819
+ // equals / notEquals
5820
+ if (operator === 'equals' || operator === 'notEquals') {
5821
+ let isEqual = false;
5822
+ // Loose comparison
5823
+ if (componentValue == value)
5824
+ isEqual = true;
5825
+ // String comparison (case-insensitive, trimmed)
5826
+ if (!isEqual && componentValue != null && value != null) {
5827
+ const strA = String(componentValue).trim();
5828
+ const strB = String(value).trim();
5829
+ // Numeric comparison within strings
5830
+ if (strA !== '' && strB !== '' && !isNaN(Number(strA)) && !isNaN(Number(strB))) {
5831
+ isEqual = Number(strA) === Number(strB);
5832
+ }
5833
+ if (!isEqual) {
5834
+ isEqual = strA.toLowerCase() === strB.toLowerCase();
5835
+ }
5836
+ }
5837
+ // Array contains check (for multi-select)
5838
+ if (!isEqual && Array.isArray(componentValue)) {
5839
+ isEqual = componentValue.some(v => String(v).trim().toLowerCase() === String(value).trim().toLowerCase());
5840
+ }
5841
+ return operator === 'equals' ? isEqual : !isEqual;
5842
+ }
5843
+ // contains / notContains
5844
+ if (operator === 'contains' || operator === 'notContains') {
5845
+ let doesContain = false;
5846
+ if (typeof componentValue === 'string' && value != null) {
5847
+ doesContain = componentValue.trim().toLowerCase().includes(String(value).trim().toLowerCase());
5848
+ }
5849
+ else if (Array.isArray(componentValue) && value != null) {
5850
+ doesContain = componentValue.some(v => String(v).trim().toLowerCase() === String(value).trim().toLowerCase());
5851
+ }
5852
+ return operator === 'contains' ? doesContain : !doesContain;
5853
+ }
5854
+ // Numeric comparisons
5855
+ if (['greaterThan', 'lessThan', 'greaterThanOrEqual', 'lessThanOrEqual'].includes(operator)) {
5856
+ const numA = typeof componentValue === 'number' ? componentValue : parseFloat(String(componentValue));
5857
+ const numB = typeof value === 'number' ? value : parseFloat(String(value));
5858
+ if (isNaN(numA) || isNaN(numB))
5859
+ return false;
5860
+ switch (operator) {
5861
+ case 'greaterThan': return numA > numB;
5862
+ case 'lessThan': return numA < numB;
5863
+ case 'greaterThanOrEqual': return numA >= numB;
5864
+ case 'lessThanOrEqual': return numA <= numB;
5865
+ default: return false;
5866
+ }
5867
+ }
5868
+ return false;
5670
5869
  }, []);
5870
+ // Main conditional logic evaluation function
5871
+ const evaluateConditionalLogic = useCallback((explicitValues) => {
5872
+ const currentComponents = localFormComponents;
5873
+ const currentValues = explicitValues || formValues;
5874
+ if (!currentComponents || currentComponents.length === 0)
5875
+ return;
5876
+ const allComponents = flattenAllComponents(currentComponents);
5877
+ const visibility = {};
5878
+ allComponents.forEach(component => {
5879
+ const compId = ensureStringId$1(component.id || component._id);
5880
+ if (!compId)
5881
+ return;
5882
+ // Container components are ALWAYS visible — their children handle their own logic
5883
+ if (['table', 'datagrid', 'section', 'heading', 'instructions'].includes(component.name)) {
5884
+ visibility[compId] = true;
5885
+ return;
5886
+ }
5887
+ // Get the logic property (API sends it as `logic`, builder uses `conditional`)
5888
+ const logic = component.logic || component.conditional;
5889
+ // No logic or action is 'always' → always show
5890
+ if (!logic || logic.action === 'always' || !logic.conditions || logic.conditions.length === 0) {
5891
+ visibility[compId] = true;
5892
+ return;
5893
+ }
5894
+ // Evaluate each condition
5895
+ const conditionResults = logic.conditions.map((condition) => {
5896
+ const componentValue = getConditionComponentValue(condition.when, allComponents, currentValues);
5897
+ return evaluateSingleCondition(condition, componentValue);
5898
+ });
5899
+ // Determine if conditions are met based on 'when' (all/any)
5900
+ let conditionsMet;
5901
+ if (logic.when === 'any') {
5902
+ conditionsMet = conditionResults.some((r) => r);
5903
+ }
5904
+ else {
5905
+ // Default to 'all'
5906
+ conditionsMet = conditionResults.every((r) => r);
5907
+ }
5908
+ // Apply action
5909
+ if (logic.action === 'show') {
5910
+ visibility[compId] = conditionsMet;
5911
+ }
5912
+ else if (logic.action === 'hide') {
5913
+ visibility[compId] = !conditionsMet;
5914
+ }
5915
+ else {
5916
+ visibility[compId] = true;
5917
+ }
5918
+ });
5919
+ // Only update state if visibility actually changed (prevent re-render loops)
5920
+ setComponentVisibility(prev => {
5921
+ const prevStr = JSON.stringify(prev);
5922
+ const newStr = JSON.stringify(visibility);
5923
+ if (prevStr === newStr)
5924
+ return prev;
5925
+ return visibility;
5926
+ });
5927
+ }, [localFormComponents, formValues, flattenAllComponents, getConditionComponentValue, evaluateSingleCondition]);
5928
+ // Re-evaluate conditional logic whenever form values change
5929
+ useEffect(() => {
5930
+ evaluateConditionalLogic();
5931
+ }, [formValues, evaluateConditionalLogic]);
5932
+ // Check if a component should be visible
5933
+ const shouldShowComponent = useCallback((componentId) => {
5934
+ // In preview mode (read-only submission view), always show everything
5935
+ if (isPreviewMode)
5936
+ return true;
5937
+ // Default to visible if not explicitly set to false
5938
+ return componentVisibility[componentId] !== false;
5939
+ }, [componentVisibility, isPreviewMode]);
5671
5940
  // Handle form value changes and re-evaluate conditional logic
5672
5941
  const onFormValueChange = useCallback((change) => {
5673
5942
  // CRITICAL: Validate that change.id is valid and unique
@@ -5719,6 +5988,13 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5719
5988
  // Only update formValues for actual value changes, not structure updates
5720
5989
  if (!isStructureUpdate) {
5721
5990
  newFormValues[change.id] = change.value;
5991
+ // CRITICAL for conditional logic: Also store value under the component's label.
5992
+ // Conditions reference components by label (e.g. "Temperature"), not by ID.
5993
+ const allComponents = flattenAllComponents(localFormComponents);
5994
+ const targetComp = allComponents.find(c => ensureStringId$1(c.id || c._id) === change.id);
5995
+ if (targetComp?.basic?.label) {
5996
+ newFormValues[targetComp.basic.label] = change.value;
5997
+ }
5722
5998
  setFormValues(newFormValues);
5723
5999
  }
5724
6000
  // Clear raised issues for this component when value changes
@@ -6459,7 +6735,6 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6459
6735
  componentCounterRef.current += 1;
6460
6736
  cachedId = `generated-${name}-${componentCounterRef.current}-${Math.random().toString(36).substr(2, 9)}`;
6461
6737
  componentIdCacheRef.current.set(cacheKey, cachedId);
6462
- console.warn('[DfFormPreview] Generated and cached ID for component:', cachedId, 'key:', cacheKey);
6463
6738
  }
6464
6739
  finalComponentId = cachedId;
6465
6740
  // Create a new component object with the cached ID (don't mutate original)
@@ -6475,9 +6750,7 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6475
6750
  if (formValue !== undefined) {
6476
6751
  // Check if this value is being used by multiple components
6477
6752
  const componentsWithSameValue = localFormComponents.filter(comp => comp.id !== componentId && formValues[comp.id] === formValue);
6478
- if (componentsWithSameValue.length > 0) {
6479
- console.warn(`[DfFormPreview] Component ${componentId} shares form value with other components:`, componentsWithSameValue.map(c => c.id));
6480
- }
6753
+ if (componentsWithSameValue.length > 0) ;
6481
6754
  }
6482
6755
  const commonProps = {
6483
6756
  id: componentId, // Use the validated component ID
@@ -6690,10 +6963,8 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6690
6963
  }, onValueChange: (change) => {
6691
6964
  const changeId = ensureStringId$1(change.id);
6692
6965
  const componentId = ensureStringId$1(component.id);
6693
- console.log(`[DfFormPreview] datagrid onValueChange - Target: ${changeId}, Component: ${componentId}`);
6694
6966
  // Handle datagrid value changes (entries updates)
6695
6967
  if (changeId === componentId && change.value && typeof change.value === 'object' && 'entries' in change.value) {
6696
- console.log('[DfFormPreview] datagrid entries update - entries count:', change.value.entries?.length);
6697
6968
  // Update localFormComponents with new entries structure
6698
6969
  const updatedComponents = localFormComponents.map(comp => {
6699
6970
  const currentCompId = ensureStringId$1(comp.id);
@@ -6736,7 +7007,6 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6736
7007
  }
6737
7008
  });
6738
7009
  if (valuesChanged) {
6739
- console.log('[DfFormPreview] Initializing form values for new datagrid entries');
6740
7010
  setFormValues(newValues);
6741
7011
  }
6742
7012
  }
@@ -6815,12 +7085,14 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6815
7085
  } }));
6816
7086
  case 'file':
6817
7087
  // Get file value from formValues, or from component basic properties
6818
- // CRITICAL: Check basic.value first (API data structure), then formValue
6819
- const fileFormValue = formValue ||
6820
- component.basic?.value ||
6821
- component.basic?.files ||
6822
- component.basic?.attachments ||
6823
- null;
7088
+ // CRITICAL: formValue could be '' (empty string) which is falsy — check explicitly
7089
+ const hasFormValue = formValue !== undefined && formValue !== null && formValue !== '';
7090
+ const fileFormValue = hasFormValue
7091
+ ? formValue
7092
+ : (component.basic?.value ||
7093
+ component.basic?.files ||
7094
+ component.basic?.attachments ||
7095
+ null);
6824
7096
  return (jsx(DfFormFileUpload, { ...commonProps, properties: component, formValue: fileFormValue }));
6825
7097
  default:
6826
7098
  return (jsx("div", { className: "form-group", children: jsxs("div", { className: "form-group-label", children: ["Unsupported Component: ", component.name] }) }));