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