df-ae-forms-package 1.1.5 → 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]);
@@ -2996,8 +2995,6 @@ const DfFormLocation = ({ id, properties, validationErrors = {}, formValue = nul
2996
2995
  }
2997
2996
  }
2998
2997
  catch (error) {
2999
- // Capacitor not properly initialized, fall through to web geolocation
3000
- console.warn('Capacitor Geolocation not available, falling back to web geolocation');
3001
2998
  }
3002
2999
  }
3003
3000
  // Fallback to standard web geolocation API (for browsers)
@@ -3711,7 +3708,6 @@ const AttachmentThumbnails = ({ attachments, onRemove }) => {
3711
3708
  newUrls.set(index, url);
3712
3709
  }
3713
3710
  catch (e) {
3714
- console.warn('Failed to create object URL for attachment:', e);
3715
3711
  }
3716
3712
  }
3717
3713
  else if (file && file.url && file.type && file.type.startsWith('image/')) {
@@ -4622,7 +4618,6 @@ formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChan
4622
4618
  zIndex: 5
4623
4619
  }, children: jsxs("button", { onClick: (e) => {
4624
4620
  e.stopPropagation();
4625
- console.log('[TableView] Add Entry button clicked (grid view)', 'entries:', dataEntries.length, 'max:', maxEntries);
4626
4621
  onAddEntry();
4627
4622
  }, disabled: dataEntries.length >= maxEntries, style: {
4628
4623
  padding: '8px 16px',
@@ -4748,7 +4743,6 @@ formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChan
4748
4743
  justifyContent: 'center'
4749
4744
  }, children: jsxs("button", { onClick: (e) => {
4750
4745
  e.stopPropagation();
4751
- console.log('[TableView] Add Entry button clicked (list view)', 'entries:', dataEntries.length, 'max:', maxEntries);
4752
4746
  onAddEntry();
4753
4747
  }, disabled: dataEntries.length >= maxEntries, style: {
4754
4748
  padding: '8px 16px',
@@ -5001,12 +4995,8 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
5001
4995
  // In edit/preview modes, don't handle value changes as components are read-only
5002
4996
  }, [mode, onValueChange]);
5003
4997
  const handleAddEntry = useCallback(() => {
5004
- console.log('[DfFormDataGrid] handleAddEntry called - Component ID:', id);
5005
4998
  // Safety check: ensure we have entries array
5006
4999
  const currentEntries = Array.isArray(properties.entries) ? properties.entries : [];
5007
- console.log('[DfFormDataGrid] gridComponents:', gridComponents.length);
5008
- console.log('[DfFormDataGrid] current entries count:', currentEntries.length);
5009
- console.log('[DfFormDataGrid] onValueChange exists:', !!onValueChange);
5010
5000
  // Use timestamp and random string to ensure uniqueness even if entries are deleted
5011
5001
  const uniqueSuffix = `${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
5012
5002
  const newEntryId = `entry-${uniqueSuffix}`;
@@ -5028,11 +5018,8 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
5028
5018
  }),
5029
5019
  styles: {}
5030
5020
  };
5031
- console.log('[DfFormDataGrid] newEntry created:', newEntry.id, 'with', newEntry.components.length, 'components');
5032
5021
  const updatedEntries = [...currentEntries, newEntry];
5033
- console.log('[DfFormDataGrid] updatedEntries count:', updatedEntries.length);
5034
5022
  if (onValueChange) {
5035
- console.log('[DfFormDataGrid] calling onValueChange with updated datagrid structure - Entries:', updatedEntries.length);
5036
5023
  onValueChange({
5037
5024
  id: gridId, // Use sanitized gridId
5038
5025
  value: {
@@ -5042,13 +5029,9 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
5042
5029
  });
5043
5030
  // Notify parent if callback provided
5044
5031
  if (onEntryAdd) {
5045
- console.log('[DfFormDataGrid] calling onEntryAdd callback');
5046
5032
  onEntryAdd();
5047
5033
  }
5048
5034
  }
5049
- else {
5050
- console.warn('[DfFormDataGrid] Cannot add entry: onValueChange is missing');
5051
- }
5052
5035
  }, [onValueChange, properties, gridId, gridComponents, onEntryAdd]);
5053
5036
  const handleRemoveEntry = useCallback((entryIndex) => {
5054
5037
  // Safety check: ensure we have entries array
@@ -5057,7 +5040,6 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
5057
5040
  .filter((_, index) => index !== entryIndex)
5058
5041
  .map((entry, index) => ({ ...entry, index })); // Only update index, preserve unique ID
5059
5042
  if (onValueChange) {
5060
- console.log('[DfFormDataGrid] Removing entry at index:', entryIndex, 'New count:', updatedEntries.length);
5061
5043
  onValueChange({
5062
5044
  id: gridId, // Use sanitized gridId
5063
5045
  value: { ...properties, entries: updatedEntries }
@@ -5445,8 +5427,8 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5445
5427
  const [validationErrors, setValidationErrors] = useState({});
5446
5428
  const [formSubmitted, setFormSubmitted] = useState(false);
5447
5429
  const [touchedFields, setTouchedFields] = useState({});
5448
- // Conditional logic disabled temporarily
5449
- // const [componentVisibility, setComponentVisibility] = useState<Record<string, boolean>>({})
5430
+ // Component visibility state - driven by conditional logic evaluation
5431
+ const [componentVisibility, setComponentVisibility] = useState({});
5450
5432
  // Track raised issues for threshold conditions (Set of condition IDs)
5451
5433
  const [raisedThresholdIssues, setRaisedThresholdIssues] = useState(new Set());
5452
5434
  // Track threshold action completions: Map<conditionId, { notesCompleted, attachmentsCompleted, emailSent }>
@@ -5544,6 +5526,11 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5544
5526
  if (component.children && Array.isArray(component.children)) {
5545
5527
  initializeComponentValues(component.children, values);
5546
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
+ }
5547
5534
  });
5548
5535
  };
5549
5536
  const initializeFormState = useCallback(() => {
@@ -5615,15 +5602,12 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5615
5602
  const labelPart = safeLabel ? `-${safeLabel}` : '';
5616
5603
  validatedComponent.id = `${name}${labelPart}-${index}`;
5617
5604
  }
5618
- console.warn(`[DfFormPreview] Fixed missing/invalid ID: ${validatedComponent.id}`);
5619
5605
  }
5620
5606
  else {
5621
5607
  // ID is a valid string, check for duplicates
5622
5608
  if (seenIds.has(validatedComponent.id)) {
5623
- console.error(`[DfFormPreview] Duplicate component ID detected: ${validatedComponent.id}`);
5624
5609
  // Generate a unique ID for duplicate - using index to keep it somewhat stable
5625
5610
  validatedComponent.id = `${validatedComponent.id}-dup-${index}`;
5626
- console.warn(`[DfFormPreview] Generated new unique ID: ${validatedComponent.id}`);
5627
5611
  }
5628
5612
  }
5629
5613
  seenIds.add(validatedComponent.id);
@@ -5678,7 +5662,6 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5678
5662
  validatedFormComponents = getValidatedComponents(localFormComponents);
5679
5663
  // Synchronize local state if components were mutated (IDs added/fixed)
5680
5664
  if (JSON.stringify(validatedFormComponents) !== JSON.stringify(localFormComponents)) {
5681
- console.log('[DfFormPreview] Synchronizing local components after ID validation');
5682
5665
  setLocalFormComponents(validatedFormComponents);
5683
5666
  onFormDataChange?.(validatedFormComponents);
5684
5667
  }
@@ -5731,13 +5714,229 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5731
5714
  // Initialize notes and attachments state
5732
5715
  setComponentNotes(prev => ({ ...initialNotes, ...prev }));
5733
5716
  setComponentAttachments(prev => ({ ...initialAttachments, ...prev }));
5734
- // Conditional logic evaluation disabled temporarily to fix table issues
5735
- // evaluateConditionalLogic()
5717
+ // Evaluate conditional logic on initialization
5718
+ evaluateConditionalLogic(initialValues);
5736
5719
  }, [initialFormData, localFormComponents]);
5737
- // Conditional logic disabled temporarily - all components are always visible
5738
- const shouldShowComponent = useCallback((_componentId) => {
5739
- 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;
5740
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;
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]);
5741
5940
  // Handle form value changes and re-evaluate conditional logic
5742
5941
  const onFormValueChange = useCallback((change) => {
5743
5942
  // CRITICAL: Validate that change.id is valid and unique
@@ -5789,6 +5988,13 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5789
5988
  // Only update formValues for actual value changes, not structure updates
5790
5989
  if (!isStructureUpdate) {
5791
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
+ }
5792
5998
  setFormValues(newFormValues);
5793
5999
  }
5794
6000
  // Clear raised issues for this component when value changes
@@ -6529,7 +6735,6 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6529
6735
  componentCounterRef.current += 1;
6530
6736
  cachedId = `generated-${name}-${componentCounterRef.current}-${Math.random().toString(36).substr(2, 9)}`;
6531
6737
  componentIdCacheRef.current.set(cacheKey, cachedId);
6532
- console.warn('[DfFormPreview] Generated and cached ID for component:', cachedId, 'key:', cacheKey);
6533
6738
  }
6534
6739
  finalComponentId = cachedId;
6535
6740
  // Create a new component object with the cached ID (don't mutate original)
@@ -6545,9 +6750,7 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6545
6750
  if (formValue !== undefined) {
6546
6751
  // Check if this value is being used by multiple components
6547
6752
  const componentsWithSameValue = localFormComponents.filter(comp => comp.id !== componentId && formValues[comp.id] === formValue);
6548
- if (componentsWithSameValue.length > 0) {
6549
- console.warn(`[DfFormPreview] Component ${componentId} shares form value with other components:`, componentsWithSameValue.map(c => c.id));
6550
- }
6753
+ if (componentsWithSameValue.length > 0) ;
6551
6754
  }
6552
6755
  const commonProps = {
6553
6756
  id: componentId, // Use the validated component ID
@@ -6760,10 +6963,8 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6760
6963
  }, onValueChange: (change) => {
6761
6964
  const changeId = ensureStringId$1(change.id);
6762
6965
  const componentId = ensureStringId$1(component.id);
6763
- console.log(`[DfFormPreview] datagrid onValueChange - Target: ${changeId}, Component: ${componentId}`);
6764
6966
  // Handle datagrid value changes (entries updates)
6765
6967
  if (changeId === componentId && change.value && typeof change.value === 'object' && 'entries' in change.value) {
6766
- console.log('[DfFormPreview] datagrid entries update - entries count:', change.value.entries?.length);
6767
6968
  // Update localFormComponents with new entries structure
6768
6969
  const updatedComponents = localFormComponents.map(comp => {
6769
6970
  const currentCompId = ensureStringId$1(comp.id);
@@ -6806,7 +7007,6 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6806
7007
  }
6807
7008
  });
6808
7009
  if (valuesChanged) {
6809
- console.log('[DfFormPreview] Initializing form values for new datagrid entries');
6810
7010
  setFormValues(newValues);
6811
7011
  }
6812
7012
  }