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