df-ae-forms-package 1.1.3 → 1.1.5

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
@@ -1002,20 +1002,15 @@ const DfFormInput = ({ id, properties, validationErrors = {}, formValue = '', in
1002
1002
  touchedFields[id] = true;
1003
1003
  }
1004
1004
  }, [isTouched, id, touchedFields]);
1005
- // Reset touched state and value when switching modes
1005
+ // Reset touched state when switching modes
1006
1006
  useEffect(() => {
1007
+ setIsTouched(false);
1008
+ // Only reset value when explicitly moving to edit mode to show current default
1007
1009
  if (mode === 'edit') {
1008
- setIsTouched(false);
1009
- // Reset value to default value when switching to edit mode
1010
1010
  const defaultValue = properties?.basic?.defaultValue || '';
1011
1011
  setValue(defaultValue);
1012
1012
  }
1013
- else if (mode === 'test') {
1014
- setIsTouched(false);
1015
- // Reset value to empty when switching to test mode for fresh start
1016
- setValue('');
1017
- }
1018
- }, [mode, properties?.basic?.defaultValue]);
1013
+ }, [mode]);
1019
1014
  // Update value when formValue prop changes (but don't override user input)
1020
1015
  // CRITICAL: Only update if formValue is actually for THIS component's ID
1021
1016
  // Use componentIdRef to ensure we're checking against the correct component ID
@@ -2407,6 +2402,7 @@ const DfFormFileUpload = ({ id, properties, validationErrors = {}, formValue = n
2407
2402
  const [isDragOver, setIsDragOver] = useState(false);
2408
2403
  const [isTouched, setIsTouched] = useState(false);
2409
2404
  const fileInputRef = useRef(null);
2405
+ const lastFormValueRef = useRef(null); // Track the last formValue to prevent unnecessary resets
2410
2406
  // Convert FileList or File[] to IFilePreview[]
2411
2407
  const convertToFilePreviews = useCallback((fileList, startIndex = 0) => {
2412
2408
  if (!fileList)
@@ -2499,12 +2495,20 @@ const DfFormFileUpload = ({ id, properties, validationErrors = {}, formValue = n
2499
2495
  if (fileObject.url || fileObject.path) {
2500
2496
  preview = fileObject.url || fileObject.path;
2501
2497
  }
2502
- else if (fileType.startsWith('image/') && fileObject.data) {
2503
- // Handle base64 data
2498
+ else if (fileObject.data) {
2499
+ // Handle base64 data - works for all file types with data
2500
+ const resolvedType = fileType || 'application/octet-stream';
2504
2501
  const fileData = fileObject.data;
2505
- preview = typeof fileData === 'string'
2506
- ? (fileData.startsWith('data:') ? fileData : `data:${fileType};base64,${fileData}`)
2507
- : undefined;
2502
+ if (typeof fileData === 'string') {
2503
+ if (fileData.startsWith('data:')) {
2504
+ // Already a full data URI
2505
+ preview = fileData;
2506
+ }
2507
+ else {
2508
+ // Raw base64 - construct data URI
2509
+ preview = `data:${resolvedType};base64,${fileData}`;
2510
+ }
2511
+ }
2508
2512
  }
2509
2513
  // Use the original object to preserve data
2510
2514
  fileObj = fileObject;
@@ -2689,10 +2693,35 @@ const DfFormFileUpload = ({ id, properties, validationErrors = {}, formValue = n
2689
2693
  }
2690
2694
  }, [mode]);
2691
2695
  // Update value when formValue prop changes or on mount
2696
+ // CRITICAL: Use a stable reference check to prevent unnecessary resets
2692
2697
  useEffect(() => {
2693
- // Always convert formValue, even if it's null/undefined (will return empty array)
2698
+ // Skip if formValue is null/undefined (no data)
2699
+ if (formValue === null || formValue === undefined || (typeof formValue === 'string' && formValue === '')) {
2700
+ // Only clear files if we previously had files from formValue
2701
+ // Don't clear files that were added by user interaction
2702
+ if (lastFormValueRef.current !== null && lastFormValueRef.current !== undefined && lastFormValueRef.current !== '') {
2703
+ setFiles([]);
2704
+ lastFormValueRef.current = null;
2705
+ }
2706
+ return;
2707
+ }
2708
+ // Check if formValue actually changed (deep comparison for arrays)
2709
+ try {
2710
+ const currentStr = JSON.stringify(formValue);
2711
+ const prevStr = JSON.stringify(lastFormValueRef.current);
2712
+ if (currentStr === prevStr) {
2713
+ return; // No change, skip
2714
+ }
2715
+ }
2716
+ catch {
2717
+ // If stringify fails, proceed with update
2718
+ }
2719
+ // Convert and set new files
2694
2720
  const newFiles = convertToFilePreviews(formValue);
2695
- setFiles(newFiles);
2721
+ if (newFiles.length > 0) {
2722
+ setFiles(newFiles);
2723
+ lastFormValueRef.current = formValue;
2724
+ }
2696
2725
  }, [formValue, convertToFilePreviews]);
2697
2726
  // Mark as touched when form is submitted
2698
2727
  useEffect(() => {
@@ -4965,8 +4994,7 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4965
4994
  // This allows the form data to be updated for interactive components
4966
4995
  if (onValueChange) {
4967
4996
  onValueChange({
4968
- id: change.id,
4969
- value: change.value
4997
+ ...change
4970
4998
  });
4971
4999
  }
4972
5000
  }
@@ -5437,7 +5465,38 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5437
5465
  const initializeComponentValues = (components, values) => {
5438
5466
  components.forEach(component => {
5439
5467
  const componentId = ensureStringId$1(component.id);
5440
- if (componentId) {
5468
+ if (!componentId)
5469
+ return; // Skip components without valid IDs
5470
+ // CRITICAL: Handle file component FIRST — file data is an array, not a simple string
5471
+ // The general handler below would misinterpret an empty file array
5472
+ if (component.name === 'file' && component.basic) {
5473
+ // Don't overwrite if already initialized
5474
+ if (values[componentId] !== undefined && values[componentId] !== '' && values[componentId] !== null) ;
5475
+ else {
5476
+ // Check all possible locations where file data could be stored
5477
+ const fileData = component.basic.value ||
5478
+ component.basic.files ||
5479
+ component.basic.attachments;
5480
+ if (fileData && (Array.isArray(fileData) ? fileData.length > 0 : true)) {
5481
+ values[componentId] = fileData;
5482
+ }
5483
+ else {
5484
+ values[componentId] = null; // Use null, not empty string, for file components
5485
+ }
5486
+ }
5487
+ }
5488
+ // Handle instructions component
5489
+ else if (component.name === 'instructions' && component.basic) {
5490
+ if (!component.basic.instructions) {
5491
+ component.basic.instructions = [];
5492
+ }
5493
+ const instructionValue = component.basic.value || component.basic.instructions;
5494
+ if (instructionValue) {
5495
+ values[componentId] = instructionValue;
5496
+ }
5497
+ }
5498
+ // General handler for all other components
5499
+ else {
5441
5500
  // ALWAYS prioritize existing form state (values param) if it exists
5442
5501
  if (values[componentId] !== undefined) ;
5443
5502
  // Then use captured value in basic.value
@@ -5452,10 +5511,10 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5452
5511
  else {
5453
5512
  // For checkbox and multi-select, empty array
5454
5513
  if (component.name === 'checkbox' || component.name === 'select') {
5455
- values[component.id] = [];
5514
+ values[componentId] = [];
5456
5515
  }
5457
5516
  else {
5458
- values[component.id] = '';
5517
+ values[componentId] = '';
5459
5518
  }
5460
5519
  }
5461
5520
  }
@@ -5477,31 +5536,10 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5477
5536
  }
5478
5537
  });
5479
5538
  }
5480
- // Handle file component - initialize with file data if present
5481
- if (component.name === 'file' && component.basic) {
5482
- const fileData = component.basic.files || component.basic.attachments || component.basic.value;
5483
- if (fileData) {
5484
- values[component.id] = fileData;
5485
- }
5486
- }
5487
5539
  // Initialize notes and attachments
5488
- if (component.id && component.basic) {
5540
+ if (componentId && component.basic) {
5489
5541
  if (component.basic.notes) ;
5490
5542
  }
5491
- // Handle instructions component - ensure instructions array exists and initialize formValue
5492
- if (component.name === 'instructions' && component.basic) {
5493
- if (!component.basic.instructions) {
5494
- // Initialize empty instructions array if not present
5495
- component.basic.instructions = [];
5496
- }
5497
- // CRITICAL: Initialize formValue for instructions from API data
5498
- // Check if component has value from API (could be in basic.value, basic.instructions, or formData)
5499
- const instructionValue = component.basic.value || component.basic.instructions;
5500
- if (instructionValue) {
5501
- // Store instruction data in formValues so it can be passed to DfFormInstruction
5502
- values[component.id] = instructionValue;
5503
- }
5504
- }
5505
5543
  // Handle nested components in section children
5506
5544
  if (component.children && Array.isArray(component.children)) {
5507
5545
  initializeComponentValues(component.children, values);
@@ -5661,8 +5699,34 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5661
5699
  }
5662
5700
  // Set the state
5663
5701
  setFormValues(prev => {
5664
- // Merge with previous state to avoid blowing away user input on re-renders
5665
- return { ...initialValues, ...prev };
5702
+ // Smart merge: initial values should win over empty/stale prev values
5703
+ // but user-entered values in prev should win over initial defaults
5704
+ const merged = { ...initialValues };
5705
+ Object.keys(prev).forEach(key => {
5706
+ const prevVal = prev[key];
5707
+ const initVal = initialValues[key];
5708
+ // If prev has a meaningful value (not empty/null/undefined), keep it
5709
+ if (prevVal !== undefined && prevVal !== null && prevVal !== '') {
5710
+ // But if initVal is an array (like file data) and prevVal is a simple empty value,
5711
+ // prefer the initialValue
5712
+ if (Array.isArray(initVal) && initVal.length > 0 && !Array.isArray(prevVal)) {
5713
+ // initialValue is a populated array but prev is not — keep initial
5714
+ merged[key] = initVal;
5715
+ }
5716
+ else {
5717
+ merged[key] = prevVal;
5718
+ }
5719
+ }
5720
+ // If prev has empty string but initial has actual data, use initial
5721
+ else if (initVal !== undefined && initVal !== null && initVal !== '') {
5722
+ merged[key] = initVal;
5723
+ }
5724
+ // Otherwise keep prev (even if empty — preserves user clearing a field)
5725
+ else {
5726
+ merged[key] = prevVal;
5727
+ }
5728
+ });
5729
+ return merged;
5666
5730
  });
5667
5731
  // Initialize notes and attachments state
5668
5732
  setComponentNotes(prev => ({ ...initialNotes, ...prev }));
@@ -5682,9 +5746,35 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5682
5746
  return;
5683
5747
  }
5684
5748
  // CRITICAL: Check if multiple components share this ID (ID collision detection)
5685
- const componentsWithSameId = localFormComponents.filter(comp => comp.id === change.id);
5749
+ // Use recursive search for accurate detection across all levels
5750
+ const checkIdCollision = (components, targetId) => {
5751
+ let matches = [];
5752
+ components.forEach(comp => {
5753
+ if (ensureStringId$1(comp.id) === targetId)
5754
+ matches.push(comp);
5755
+ if (comp.name === 'section' && comp.children) {
5756
+ matches = [...matches, ...checkIdCollision(comp.children, targetId)];
5757
+ }
5758
+ if (comp.name === 'table' && comp.cells) {
5759
+ comp.cells.forEach((row) => {
5760
+ normalizeTableRow(row).forEach((cell) => {
5761
+ if (cell.components)
5762
+ matches = [...matches, ...checkIdCollision(cell.components, targetId)];
5763
+ });
5764
+ });
5765
+ }
5766
+ if (comp.name === 'datagrid' && comp.entries) {
5767
+ comp.entries.forEach((entry) => {
5768
+ if (entry.components)
5769
+ matches = [...matches, ...checkIdCollision(entry.components, targetId)];
5770
+ });
5771
+ }
5772
+ });
5773
+ return matches;
5774
+ };
5775
+ const componentsWithSameId = checkIdCollision(localFormComponents, change.id);
5686
5776
  if (componentsWithSameId.length > 1) {
5687
- console.error(`[DfFormPreview] ID COLLISION DETECTED! Multiple components share ID "${change.id}":`, componentsWithSameId.map(c => ({ id: c.id, name: c.name, label: c.basic?.label })));
5777
+ console.error(`[DfFormPreview] ID COLLISION DETECTED! Multiple components share ID "${change.id}":`, componentsWithSameId.map(c => ({ id: ensureStringId$1(c.id), name: c.name, label: c.basic?.label })));
5688
5778
  // Don't update - this would cause all components with this ID to get the same value
5689
5779
  return;
5690
5780
  }
@@ -5703,7 +5793,7 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5703
5793
  }
5704
5794
  // Clear raised issues for this component when value changes
5705
5795
  // This ensures that if threshold condition changes, user must raise issue again
5706
- const component = localFormComponents.find(comp => comp.id === change.id);
5796
+ const component = findComponentById(localFormComponents, change.id);
5707
5797
  if (component) {
5708
5798
  const threshold = component?.threshold;
5709
5799
  if (threshold && threshold.conditions && threshold.conditions.length > 0) {
@@ -5719,7 +5809,8 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5719
5809
  // Recursive function to update component values
5720
5810
  const updateComponentValue = (components) => {
5721
5811
  return components.map(component => {
5722
- if (component.id === change.id) {
5812
+ const componentId = ensureStringId$1(component.id);
5813
+ if (componentId === change.id) {
5723
5814
  // CRITICAL: Handle table/datagrid structure updates (cells, entries, etc.)
5724
5815
  // When a table sends onValueChange with cells data, update the whole component structure
5725
5816
  if (change.value && typeof change.value === 'object' && 'cells' in change.value) {
@@ -5735,13 +5826,12 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5735
5826
  ...change.value
5736
5827
  };
5737
5828
  }
5738
- if ('defaultValue' in component.basic) {
5829
+ if ('value' in component.basic || 'defaultValue' in component.basic) {
5739
5830
  return {
5740
5831
  ...component,
5741
5832
  basic: {
5742
5833
  ...component.basic,
5743
- value: change.value ?? component.basic.defaultValue,
5744
- defaultValue: change.value
5834
+ value: change.value
5745
5835
  }
5746
5836
  };
5747
5837
  }
@@ -6795,12 +6885,14 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6795
6885
  } }));
6796
6886
  case 'file':
6797
6887
  // Get file value from formValues, or from component basic properties
6798
- // CRITICAL: Check basic.value first (API data structure), then formValue
6799
- const fileFormValue = formValue ||
6800
- component.basic?.value ||
6801
- component.basic?.files ||
6802
- component.basic?.attachments ||
6803
- null;
6888
+ // CRITICAL: formValue could be '' (empty string) which is falsy — check explicitly
6889
+ const hasFormValue = formValue !== undefined && formValue !== null && formValue !== '';
6890
+ const fileFormValue = hasFormValue
6891
+ ? formValue
6892
+ : (component.basic?.value ||
6893
+ component.basic?.files ||
6894
+ component.basic?.attachments ||
6895
+ null);
6804
6896
  return (jsx(DfFormFileUpload, { ...commonProps, properties: component, formValue: fileFormValue }));
6805
6897
  default:
6806
6898
  return (jsx("div", { className: "form-group", children: jsxs("div", { className: "form-group-label", children: ["Unsupported Component: ", component.name] }) }));