Enhance ComponentPreview and RepeatingTable Functionality
- Updated ComponentPreview.vue to improve the handling of repeating groups, including the addition of helper functions for field value retrieval and input handling. - Enhanced the rendering of repeating groups with better structure and improved user experience, including dynamic item addition and removal. - Modified RepeatingTable.vue to increase the maximum visible columns from 20 to 50, allowing for better data presentation and horizontal scrolling. - Improved column width calculations and added a minimum table width to ensure proper layout and usability. - Updated safeGetField.js to allow for optional warning suppression, enhancing flexibility in data access without unnecessary console warnings. - Refined styles across components for better visual consistency and usability.
This commit is contained in:
parent
a14252a844
commit
d0cef85b72
@ -23,12 +23,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Basic Input Types (including radio and checkbox) -->
|
<!-- Basic Input Types (including radio and checkbox) -->
|
||||||
<FormKit v-else-if="isInputType" :id="`preview-${component.id}`" :type="component.type" :name="component.props.name"
|
<FormKit v-else-if="isInputType" :id="`preview-${component.id}`" :type="component.type"
|
||||||
|
:name="getFieldName(component)"
|
||||||
:label="component.props.label" :help="component.props.help" :placeholder="component.props.placeholder"
|
:label="component.props.label" :help="component.props.help" :placeholder="component.props.placeholder"
|
||||||
:validation="component.props.validation" :validation-visibility="isPreview ? 'live' : 'blur'"
|
:validation="component.props.validation" :validation-visibility="isPreview ? 'live' : 'blur'"
|
||||||
:readonly="component.props.readonly || !isPreview"
|
:readonly="component.props.readonly || !isPreview"
|
||||||
:disabled="!isPreview || (component.props.readonly && ['select', 'searchSelect', 'checkbox', 'radio', 'switch'].includes(component.type))"
|
:disabled="!isPreview || (component.props.readonly && ['select', 'searchSelect', 'checkbox', 'radio', 'switch'].includes(component.type))"
|
||||||
:options="component.props.options || undefined" :value="component.props.value || undefined"
|
:options="component.props.options || undefined"
|
||||||
|
:value="getFieldValue(component)"
|
||||||
:accept="component.props.accept || undefined" :max="component.props.max || undefined"
|
:accept="component.props.accept || undefined" :max="component.props.max || undefined"
|
||||||
:mask="component.props.mask || undefined" :digits="component.props.digits || undefined"
|
:mask="component.props.mask || undefined" :digits="component.props.digits || undefined"
|
||||||
:multiple="component.props.multiple || undefined" :maxSize="component.props.maxSize || undefined"
|
:multiple="component.props.multiple || undefined" :maxSize="component.props.maxSize || undefined"
|
||||||
@ -42,7 +44,9 @@
|
|||||||
'readonly-checkbox': component.props.readonly && component.type === 'checkbox',
|
'readonly-checkbox': component.props.readonly && component.type === 'checkbox',
|
||||||
'readonly-radio': component.props.readonly && component.type === 'radio',
|
'readonly-radio': component.props.readonly && component.type === 'radio',
|
||||||
'readonly-switch': component.props.readonly && component.type === 'switch'
|
'readonly-switch': component.props.readonly && component.type === 'switch'
|
||||||
}" />
|
}"
|
||||||
|
@input="handleFieldInput(component, $event)"
|
||||||
|
@change="handleFieldChange(component, $event)" />
|
||||||
|
|
||||||
<!-- Heading -->
|
<!-- Heading -->
|
||||||
<div v-else-if="component.type === 'heading'" class="py-2">
|
<div v-else-if="component.type === 'heading'" class="py-2">
|
||||||
@ -228,35 +232,59 @@
|
|||||||
|
|
||||||
<!-- Preview mode - show functional repeating groups -->
|
<!-- Preview mode - show functional repeating groups -->
|
||||||
<div v-else class="repeating-groups space-y-4">
|
<div v-else class="repeating-groups space-y-4">
|
||||||
<div v-for="(group, groupIndex) in (safeGetField(component.props.name, previewFormData) || [])" :key="groupIndex"
|
|
||||||
class="group-item border border-gray-200 rounded-md p-3 bg-gray-50">
|
|
||||||
|
<!-- Group items -->
|
||||||
|
<div v-for="(group, groupIndex) in (safeGetField(component.props.name, previewFormData, { warn: false }) || [])"
|
||||||
|
:key="`${component.props.name}-${groupIndex}`"
|
||||||
|
class="group-item border border-gray-200 rounded-md p-3 bg-gray-50 mb-4">
|
||||||
|
|
||||||
|
<!-- Item header -->
|
||||||
<div class="flex justify-between items-center mb-3">
|
<div class="flex justify-between items-center mb-3">
|
||||||
<h4 class="text-sm font-medium text-gray-700">Item {{ groupIndex + 1 }}</h4>
|
<h4 class="text-sm font-medium text-gray-700">Item {{ groupIndex + 1 }}</h4>
|
||||||
<button v-if="(safeGetField(component.props.name, previewFormData)?.length || 0) > (component.props.minItems || 1)"
|
<button v-if="(safeGetField(component.props.name, previewFormData, { warn: false }) || []).length > (component.props.minItems || 1)"
|
||||||
type="button" class="text-red-500 hover:text-red-700 text-sm" @click="removeGroupItem(groupIndex)">
|
type="button"
|
||||||
{{ component.props.removeText || 'Remove' }}
|
class="text-red-500 hover:text-red-700 text-sm"
|
||||||
|
@click="removeRepeatingGroupItem(component.props.name, groupIndex)">
|
||||||
|
{{ component.props.removeText || 'Buang' }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Fields grid -->
|
||||||
<div class="grid grid-cols-12 gap-2">
|
<div class="grid grid-cols-12 gap-2">
|
||||||
<!-- Render children components for each group item -->
|
<div v-for="(child, childIndex) in component.props.children"
|
||||||
<template v-for="(child, childIndex) in component.props.children" :key="childIndex">
|
:key="`field-${groupIndex}-${childIndex}`"
|
||||||
<div
|
class="form-component"
|
||||||
class="form-component"
|
:style="{ gridColumn: child.props.gridColumn || 'span 6' }">
|
||||||
:style="{
|
|
||||||
gridColumn: child.props.gridColumn || 'span 6'
|
<!-- Render FormKit field -->
|
||||||
}"
|
<FormKit
|
||||||
>
|
:type="child.type"
|
||||||
<component-preview :component="child" :is-preview="true" />
|
:name="`${component.props.name}[${groupIndex}].${child.props.name}`"
|
||||||
</div>
|
:label="child.props.label"
|
||||||
</template>
|
:placeholder="child.props.placeholder"
|
||||||
|
:help="child.props.help"
|
||||||
|
:options="child.props.options"
|
||||||
|
:validation="child.props.validation"
|
||||||
|
:rows="child.type === 'textarea' ? (child.props.rows || 3) : undefined"
|
||||||
|
:model-value="getRepeatingGroupFieldValue(group, child.props.name, child.type)"
|
||||||
|
@update:model-value="updateRepeatingGroupField(component.props.name, groupIndex, child.props.name, $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button v-if="(safeGetField(component.props.name, previewFormData)?.length || 0) < (component.props.maxItems || 10)"
|
<!-- Empty state -->
|
||||||
type="button"
|
<div v-if="(safeGetField(component.props.name, previewFormData, { warn: false }) || []).length === 0"
|
||||||
class="inline-flex items-center px-3 py-1.5 border border-blue-600 text-blue-600 bg-white hover:bg-blue-50 rounded text-sm"
|
class="text-center py-8 text-gray-500">
|
||||||
@click="addGroupItem">
|
<p>No items yet. Click "{{ component.props.buttonText || 'Add Item' }}" to add the first item.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add button -->
|
||||||
|
<button v-if="(safeGetField(component.props.name, previewFormData, { warn: false }) || []).length < (component.props.maxItems || 10)"
|
||||||
|
type="button"
|
||||||
|
class="inline-flex items-center px-3 py-1.5 border border-blue-600 text-blue-600 bg-white hover:bg-blue-50 rounded text-sm"
|
||||||
|
@click="addRepeatingGroupItem(component.props.name, component.props.children)">
|
||||||
<Icon name="material-symbols:add-circle-outline" class="w-4 h-4 mr-1" />
|
<Icon name="material-symbols:add-circle-outline" class="w-4 h-4 mr-1" />
|
||||||
{{ component.props.buttonText || 'Add Item' }}
|
{{ component.props.buttonText || 'Add Item' }}
|
||||||
</button>
|
</button>
|
||||||
@ -270,7 +298,7 @@
|
|||||||
<label v-if="component.props.label" class="block text-sm font-medium text-gray-700 mb-1">
|
<label v-if="component.props.label" class="block text-sm font-medium text-gray-700 mb-1">
|
||||||
{{ component.props.label }}
|
{{ component.props.label }}
|
||||||
<span v-if="component.props.showItemCounter" class="text-xs text-gray-500 ml-2">
|
<span v-if="component.props.showItemCounter" class="text-xs text-gray-500 ml-2">
|
||||||
({{ (safeGetField(component.props.name, previewFormData) || []).length }}/{{ component.props.maxItems || 20 }})
|
({{ (safeGetField(component.props.name, previewFormData, { warn: false }) || []).length }}/{{ component.props.maxItems || 20 }})
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@ -289,7 +317,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bulk Operations Toolbar (if enabled) -->
|
<!-- Bulk Operations Toolbar (if enabled) -->
|
||||||
<div v-if="component.props.bulkOperations && (safeGetField(component.props.name, previewFormData) || []).length > 0"
|
<div v-if="component.props.bulkOperations && (safeGetField(component.props.name, previewFormData, { warn: false }) || []).length > 0"
|
||||||
class="mb-3 flex items-center justify-between bg-gray-50 p-2 rounded">
|
class="mb-3 flex items-center justify-between bg-gray-50 p-2 rounded">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<input type="checkbox" :checked="isAllSelected(component.props.name)"
|
<input type="checkbox" :checked="isAllSelected(component.props.name)"
|
||||||
@ -343,7 +371,7 @@
|
|||||||
@change="toggleItemSelection(component.props.name, index)"
|
@change="toggleItemSelection(component.props.name, index)"
|
||||||
class="mr-2 h-4 w-4 rounded border-gray-300" />
|
class="mr-2 h-4 w-4 rounded border-gray-300" />
|
||||||
<input :type="component.props.itemType || 'text'"
|
<input :type="component.props.itemType || 'text'"
|
||||||
:value="safeGetField(component.props.name, previewFormData)[index]"
|
:value="safeGetField(component.props.name, previewFormData, { warn: false })[index]"
|
||||||
:placeholder="component.props.placeholder"
|
:placeholder="component.props.placeholder"
|
||||||
:class="getItemInputClasses(component.props.name, index, item)"
|
:class="getItemInputClasses(component.props.name, index, item)"
|
||||||
@blur="validateItem(component.props.name, index, item)"
|
@blur="validateItem(component.props.name, index, item)"
|
||||||
@ -352,7 +380,7 @@
|
|||||||
class="ml-2 cursor-move text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity">
|
class="ml-2 cursor-move text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
<Icon name="material-symbols:drag-indicator" class="w-5 h-5" />
|
<Icon name="material-symbols:drag-indicator" class="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<button v-if="(safeGetField(component.props.name, previewFormData)?.length || 0) > (component.props.minItems || 0)"
|
<button v-if="(safeGetField(component.props.name, previewFormData, { warn: false })?.length || 0) > (component.props.minItems || 0)"
|
||||||
type="button" class="ml-2 text-red-500 hover:text-red-700" @click="removeListItem(index)">
|
type="button" class="ml-2 text-red-500 hover:text-red-700" @click="removeListItem(index)">
|
||||||
<Icon name="material-symbols:delete-outline" class="w-5 h-5" />
|
<Icon name="material-symbols:delete-outline" class="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
@ -363,7 +391,7 @@
|
|||||||
{{ validationErrors[component.props.name] }}
|
{{ validationErrors[component.props.name] }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button v-if="(safeGetField(component.props.name, previewFormData)?.length || 0) < (component.props.maxItems || 20)"
|
<button v-if="(safeGetField(component.props.name, previewFormData, { warn: false })?.length || 0) < (component.props.maxItems || 20)"
|
||||||
type="button"
|
type="button"
|
||||||
class="inline-flex items-center px-3 py-1.5 border border-blue-600 text-blue-600 bg-white hover:bg-blue-50 rounded text-sm"
|
class="inline-flex items-center px-3 py-1.5 border border-blue-600 text-blue-600 bg-white hover:bg-blue-50 rounded text-sm"
|
||||||
@click="addListItem">
|
@click="addListItem">
|
||||||
@ -372,7 +400,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Export Button (if enabled) -->
|
<!-- Export Button (if enabled) -->
|
||||||
<div v-if="component.props.exportFormat && (safeGetField(component.props.name, previewFormData) || []).length > 0"
|
<div v-if="component.props.exportFormat && (safeGetField(component.props.name, previewFormData, { warn: false }) || []).length > 0"
|
||||||
class="mt-2">
|
class="mt-2">
|
||||||
<button @click="exportItems(component.props.name, component.props.exportFormat)"
|
<button @click="exportItems(component.props.name, component.props.exportFormat)"
|
||||||
class="text-xs text-green-600 hover:text-green-800 flex items-center">
|
class="text-xs text-green-600 hover:text-green-800 flex items-center">
|
||||||
@ -387,12 +415,14 @@
|
|||||||
|
|
||||||
<!-- Repeating Table Component -->
|
<!-- Repeating Table Component -->
|
||||||
<div v-else-if="component.type === 'repeating-table'" class="repeating-table-wrapper">
|
<div v-else-if="component.type === 'repeating-table'" class="repeating-table-wrapper">
|
||||||
<RepeatingTable
|
<div class="table-container-wrapper">
|
||||||
:config="component.props"
|
<RepeatingTable
|
||||||
:model-value="getTableData(component.props.name)"
|
:config="component.props"
|
||||||
:is-preview="isPreview"
|
:model-value="getTableData(component.props.name)"
|
||||||
@update:model-value="updateTableData"
|
:is-preview="isPreview"
|
||||||
/>
|
@update:model-value="updateTableData"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Button Component -->
|
<!-- Button Component -->
|
||||||
@ -679,9 +709,11 @@ import { useNuxtApp } from '#app';
|
|||||||
import { useFormBuilderStore } from '~/stores/formBuilder';
|
import { useFormBuilderStore } from '~/stores/formBuilder';
|
||||||
import FormBuilderFieldSettingsModal from '~/components/FormBuilderFieldSettingsModal.vue';
|
import FormBuilderFieldSettingsModal from '~/components/FormBuilderFieldSettingsModal.vue';
|
||||||
import { safeGetField } from '~/composables/safeGetField';
|
import { safeGetField } from '~/composables/safeGetField';
|
||||||
import { onMounted, onUnmounted, watch, computed, nextTick } from 'vue';
|
import { onMounted, onUnmounted, watch, computed, nextTick, triggerRef, defineComponent, h } from 'vue';
|
||||||
import draggable from 'vuedraggable';
|
import draggable from 'vuedraggable';
|
||||||
|
|
||||||
|
// RepeatingGroupContainer component removed - using template approach instead
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
component: {
|
component: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -703,11 +735,20 @@ const emit = defineEmits(['select-nested-component', 'form-data-updated']);
|
|||||||
const formStore = useFormBuilderStore();
|
const formStore = useFormBuilderStore();
|
||||||
const previewFormData = computed(() => formStore.previewFormData || {});
|
const previewFormData = computed(() => formStore.previewFormData || {});
|
||||||
|
|
||||||
|
// Clean up: Debug watcher removed - repeating groups working correctly
|
||||||
|
|
||||||
// Enhanced dynamic list functionality
|
// Enhanced dynamic list functionality
|
||||||
const searchQuery = ref({});
|
const searchQuery = ref({});
|
||||||
const selectedItems = ref({});
|
const selectedItems = ref({});
|
||||||
const validationErrors = ref({});
|
const validationErrors = ref({});
|
||||||
|
|
||||||
|
// Force reactivity for repeating groups
|
||||||
|
const groupUpdateCounter = ref(0);
|
||||||
|
|
||||||
|
// Simplified reactive variables
|
||||||
|
const isInRepeatingGroup = ref(false);
|
||||||
|
const repeatingGroupContext = ref(null);
|
||||||
|
|
||||||
// New reactive state for form sections
|
// New reactive state for form sections
|
||||||
const sectionDropStates = ref({});
|
const sectionDropStates = ref({});
|
||||||
|
|
||||||
@ -750,7 +791,7 @@ onMounted(() => {
|
|||||||
validationErrors.value[listName] = '';
|
validationErrors.value[listName] = '';
|
||||||
|
|
||||||
// Initialize form data with default items if they exist and form data is empty
|
// Initialize form data with default items if they exist and form data is empty
|
||||||
const currentFormData = safeGetField(listName, formStore.previewFormData);
|
const currentFormData = safeGetField(listName, formStore.previewFormData, { warn: false });
|
||||||
const defaultItems = props.component.props.defaultItems;
|
const defaultItems = props.component.props.defaultItems;
|
||||||
|
|
||||||
if ((!currentFormData || currentFormData.length === 0) && defaultItems && defaultItems.length > 0) {
|
if ((!currentFormData || currentFormData.length === 0) && defaultItems && defaultItems.length > 0) {
|
||||||
@ -767,7 +808,7 @@ onMounted(() => {
|
|||||||
const groupName = props.component.props.name;
|
const groupName = props.component.props.name;
|
||||||
if (groupName) {
|
if (groupName) {
|
||||||
// Get current groups or initialize empty array
|
// Get current groups or initialize empty array
|
||||||
const currentGroups = safeGetField(groupName, formStore.previewFormData);
|
const currentGroups = safeGetField(groupName, formStore.previewFormData, { warn: false });
|
||||||
|
|
||||||
// If no groups exist and minItems is specified, create initial groups
|
// If no groups exist and minItems is specified, create initial groups
|
||||||
if ((!currentGroups || currentGroups.length === 0) && props.component.props.minItems > 0) {
|
if ((!currentGroups || currentGroups.length === 0) && props.component.props.minItems > 0) {
|
||||||
@ -794,6 +835,26 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize repeating tables
|
||||||
|
if (props.component.type === 'repeating-table') {
|
||||||
|
const tableName = props.component.props.name;
|
||||||
|
if (tableName) {
|
||||||
|
// Get current table data or initialize empty array
|
||||||
|
const currentData = safeGetField(tableName, formStore.previewFormData, { warn: false });
|
||||||
|
|
||||||
|
// If no data exists, initialize it
|
||||||
|
if (!currentData || !Array.isArray(currentData)) {
|
||||||
|
const initialData = Array.isArray(props.component.props.defaultData) ? [...props.component.props.defaultData] : [];
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
const updatedData = { ...formStore.previewFormData, [tableName]: initialData };
|
||||||
|
formStore.updatePreviewFormData(updatedData);
|
||||||
|
console.log('[ComponentPreview] Initialized repeating table data:', tableName, initialData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watch for changes to component props, especially defaultItems
|
// Watch for changes to component props, especially defaultItems
|
||||||
@ -824,7 +885,7 @@ watch(() => props.component.props.minItems, (newMinItems, oldMinItems) => {
|
|||||||
const groupName = props.component.props.name;
|
const groupName = props.component.props.name;
|
||||||
if (!groupName || newMinItems === oldMinItems) return;
|
if (!groupName || newMinItems === oldMinItems) return;
|
||||||
|
|
||||||
const currentGroups = safeGetField(groupName, formStore.previewFormData);
|
const currentGroups = safeGetField(groupName, formStore.previewFormData, { warn: false });
|
||||||
const minItems = newMinItems || 1;
|
const minItems = newMinItems || 1;
|
||||||
|
|
||||||
// If current groups are fewer than minItems, add missing groups
|
// If current groups are fewer than minItems, add missing groups
|
||||||
@ -853,29 +914,12 @@ watch(() => props.component.props.minItems, (newMinItems, oldMinItems) => {
|
|||||||
}
|
}
|
||||||
}, { deep: true, immediate: true });
|
}, { deep: true, immediate: true });
|
||||||
|
|
||||||
// Controlled update methods to prevent circular reactivity
|
// Legacy updateGroupField removed - now handled by RepeatingGroupContainer
|
||||||
const updateGroupField = (groupName, groupIndex, fieldName, newValue) => {
|
|
||||||
if (!props.isPreview) return;
|
|
||||||
|
|
||||||
const currentGroups = [...(safeGetField(groupName, formStore.previewFormData) || [])];
|
|
||||||
if (!currentGroups[groupIndex]) return;
|
|
||||||
|
|
||||||
// Only update if value actually changed
|
|
||||||
if (currentGroups[groupIndex][fieldName] === newValue) return;
|
|
||||||
|
|
||||||
currentGroups[groupIndex][fieldName] = newValue;
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
const updatedData = { ...formStore.previewFormData, [groupName]: currentGroups };
|
|
||||||
formStore.updatePreviewFormData(updatedData);
|
|
||||||
emit('form-data-updated', updatedData);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateListItem = (listName, index, newValue) => {
|
const updateListItem = (listName, index, newValue) => {
|
||||||
if (!props.isPreview) return;
|
if (!props.isPreview) return;
|
||||||
|
|
||||||
const currentItems = [...(safeGetField(listName, formStore.previewFormData) || [])];
|
const currentItems = [...(safeGetField(listName, formStore.previewFormData, { warn: false }) || [])];
|
||||||
if (currentItems[index] === newValue) return; // No change
|
if (currentItems[index] === newValue) return; // No change
|
||||||
|
|
||||||
// Validate and handle duplicates
|
// Validate and handle duplicates
|
||||||
@ -890,63 +934,7 @@ const updateListItem = (listName, index, newValue) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Repeating group and dynamic list functionality
|
// Legacy functions removed - now handled by RepeatingGroupContainer
|
||||||
const addGroupItem = () => {
|
|
||||||
if (!props.isPreview) return;
|
|
||||||
|
|
||||||
const groupName = props.component.props.name;
|
|
||||||
if (!groupName) return;
|
|
||||||
|
|
||||||
// Get current groups or initialize empty array
|
|
||||||
const currentGroups = safeGetField(groupName, formStore.previewFormData) || [];
|
|
||||||
|
|
||||||
// Create a new empty group
|
|
||||||
const newGroup = {};
|
|
||||||
|
|
||||||
// Add fields from configuration
|
|
||||||
if (props.component.props.fields) {
|
|
||||||
props.component.props.fields.forEach(field => {
|
|
||||||
newGroup[field.name] = '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the new group to the list
|
|
||||||
currentGroups.push(newGroup);
|
|
||||||
|
|
||||||
// Update the form data
|
|
||||||
const updatedData = { ...formStore.previewFormData, [groupName]: currentGroups };
|
|
||||||
formStore.updatePreviewFormData(updatedData);
|
|
||||||
|
|
||||||
// Also emit an event to notify parent components about the data change
|
|
||||||
emit('form-data-updated', updatedData);
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeGroupItem = (index) => {
|
|
||||||
if (!props.isPreview) return;
|
|
||||||
|
|
||||||
const groupName = props.component.props.name;
|
|
||||||
if (!groupName) return;
|
|
||||||
|
|
||||||
// Get current groups
|
|
||||||
const currentGroups = [...(safeGetField(groupName, formStore.previewFormData) || [])];
|
|
||||||
|
|
||||||
// Check if we can remove this item (respect minimum items)
|
|
||||||
const minItems = props.component.props.minItems || 1;
|
|
||||||
if (currentGroups.length <= minItems) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the group at the specified index
|
|
||||||
currentGroups.splice(index, 1);
|
|
||||||
|
|
||||||
// Update the form data
|
|
||||||
const updatedData = { ...formStore.previewFormData, [groupName]: currentGroups };
|
|
||||||
formStore.updatePreviewFormData(updatedData);
|
|
||||||
|
|
||||||
// Also emit an event to notify parent components about the data change
|
|
||||||
// This is important for FormKit integration
|
|
||||||
emit('form-data-updated', updatedData);
|
|
||||||
};
|
|
||||||
|
|
||||||
const addListItem = () => {
|
const addListItem = () => {
|
||||||
if (!props.isPreview) return;
|
if (!props.isPreview) return;
|
||||||
@ -955,7 +943,7 @@ const addListItem = () => {
|
|||||||
if (!listName) return;
|
if (!listName) return;
|
||||||
|
|
||||||
// Get current items or initialize empty array
|
// Get current items or initialize empty array
|
||||||
const currentItems = [...(safeGetField(listName, formStore.previewFormData) || [])];
|
const currentItems = [...(safeGetField(listName, formStore.previewFormData, { warn: false }) || [])];
|
||||||
|
|
||||||
// Add an empty item
|
// Add an empty item
|
||||||
currentItems.push('');
|
currentItems.push('');
|
||||||
@ -972,7 +960,7 @@ const removeListItem = (index) => {
|
|||||||
if (!listName) return;
|
if (!listName) return;
|
||||||
|
|
||||||
// Get current items
|
// Get current items
|
||||||
const currentItems = [...(safeGetField(listName, formStore.previewFormData) || [])];
|
const currentItems = [...(safeGetField(listName, formStore.previewFormData, { warn: false }) || [])];
|
||||||
|
|
||||||
// Remove the item at the specified index
|
// Remove the item at the specified index
|
||||||
currentItems.splice(index, 1);
|
currentItems.splice(index, 1);
|
||||||
@ -1041,7 +1029,7 @@ const validateItem = (listName, index, value) => {
|
|||||||
const checkDuplicates = (listName, newValue, currentIndex) => {
|
const checkDuplicates = (listName, newValue, currentIndex) => {
|
||||||
if (props.component.props.allowDuplicates) return true;
|
if (props.component.props.allowDuplicates) return true;
|
||||||
|
|
||||||
const currentItems = safeGetField(listName, formStore.previewFormData) || [];
|
const currentItems = safeGetField(listName, formStore.previewFormData, { warn: false }) || [];
|
||||||
const duplicateIndex = currentItems.findIndex((item, index) =>
|
const duplicateIndex = currentItems.findIndex((item, index) =>
|
||||||
index !== currentIndex && item.toLowerCase() === newValue.toLowerCase()
|
index !== currentIndex && item.toLowerCase() === newValue.toLowerCase()
|
||||||
);
|
);
|
||||||
@ -1080,7 +1068,7 @@ const getItemInputClasses = (listName, index, value) => {
|
|||||||
|
|
||||||
// Filter items based on search query
|
// Filter items based on search query
|
||||||
const getFilteredItems = (listName) => {
|
const getFilteredItems = (listName) => {
|
||||||
const items = safeGetField(listName, formStore.previewFormData) || [];
|
const items = safeGetField(listName, formStore.previewFormData, { warn: false }) || [];
|
||||||
const query = searchQuery.value[listName];
|
const query = searchQuery.value[listName];
|
||||||
|
|
||||||
if (!query || !props.component.props.enableSearch) {
|
if (!query || !props.component.props.enableSearch) {
|
||||||
@ -1113,13 +1101,13 @@ const toggleItemSelection = (listName, index) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isAllSelected = (listName) => {
|
const isAllSelected = (listName) => {
|
||||||
const items = safeGetField(listName, formStore.previewFormData) || [];
|
const items = safeGetField(listName, formStore.previewFormData, { warn: false }) || [];
|
||||||
const selected = selectedItems.value[listName] || [];
|
const selected = selectedItems.value[listName] || [];
|
||||||
return items.length > 0 && selected.length === items.length;
|
return items.length > 0 && selected.length === items.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleSelectAll = (listName) => {
|
const toggleSelectAll = (listName) => {
|
||||||
const items = safeGetField(listName, formStore.previewFormData) || [];
|
const items = safeGetField(listName, formStore.previewFormData, { warn: false }) || [];
|
||||||
|
|
||||||
if (isAllSelected(listName)) {
|
if (isAllSelected(listName)) {
|
||||||
selectedItems.value[listName] = [];
|
selectedItems.value[listName] = [];
|
||||||
@ -1130,7 +1118,7 @@ const toggleSelectAll = (listName) => {
|
|||||||
|
|
||||||
const deleteSelectedItems = (listName) => {
|
const deleteSelectedItems = (listName) => {
|
||||||
if (!props.component.props.confirmDelete || confirm('Are you sure you want to delete the selected items?')) {
|
if (!props.component.props.confirmDelete || confirm('Are you sure you want to delete the selected items?')) {
|
||||||
const currentItems = [...(safeGetField(listName, formStore.previewFormData) || [])];
|
const currentItems = [...(safeGetField(listName, formStore.previewFormData, { warn: false }) || [])];
|
||||||
const selected = selectedItems.value[listName] || [];
|
const selected = selectedItems.value[listName] || [];
|
||||||
|
|
||||||
// Sort indices in descending order to avoid index shifting issues
|
// Sort indices in descending order to avoid index shifting issues
|
||||||
@ -1152,7 +1140,7 @@ const deleteSelectedItems = (listName) => {
|
|||||||
|
|
||||||
// Import/Export functionality
|
// Import/Export functionality
|
||||||
const exportItems = (listName, format) => {
|
const exportItems = (listName, format) => {
|
||||||
const items = safeGetField(listName, formStore.previewFormData) || [];
|
const items = safeGetField(listName, formStore.previewFormData, { warn: false }) || [];
|
||||||
let content = '';
|
let content = '';
|
||||||
let filename = `${listName}_items`;
|
let filename = `${listName}_items`;
|
||||||
let mimeType = 'text/plain';
|
let mimeType = 'text/plain';
|
||||||
@ -1203,7 +1191,7 @@ const showImportModal = (listName) => {
|
|||||||
} else {
|
} else {
|
||||||
importedItems = content.split('\n').filter(Boolean);
|
importedItems = content.split('\n').filter(Boolean);
|
||||||
}
|
}
|
||||||
const currentItems = [...(safeGetField(listName, formStore.previewFormData) || [])];
|
const currentItems = [...(safeGetField(listName, formStore.previewFormData, { warn: false }) || [])];
|
||||||
const newItems = [...currentItems, ...importedItems];
|
const newItems = [...currentItems, ...importedItems];
|
||||||
const maxItems = props.component.props.maxItems || 20;
|
const maxItems = props.component.props.maxItems || 20;
|
||||||
if (newItems.length > maxItems) {
|
if (newItems.length > maxItems) {
|
||||||
@ -1344,41 +1332,170 @@ const updateListItems = (listName, newItems) => {
|
|||||||
const getTableData = (tableName) => {
|
const getTableData = (tableName) => {
|
||||||
if (!tableName) return [];
|
if (!tableName) return [];
|
||||||
|
|
||||||
// Directly check the form store without using safeGetField to avoid warnings
|
// Use safeGetField for consistent data access
|
||||||
const formData = formStore.previewFormData || {};
|
const currentData = safeGetField(tableName, formStore.previewFormData, { warn: false });
|
||||||
|
|
||||||
|
console.log('[ComponentPreview] getTableData:', tableName, 'currentData:', currentData);
|
||||||
|
|
||||||
// If field exists and is an array, return it
|
// If field exists and is an array, return it
|
||||||
if (formData.hasOwnProperty(tableName) && Array.isArray(formData[tableName])) {
|
if (Array.isArray(currentData)) {
|
||||||
return formData[tableName];
|
return currentData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If data doesn't exist, initialize it immediately (no nextTick needed for initial render)
|
// If data doesn't exist, initialize it immediately
|
||||||
const initialData = [];
|
const initialData = [];
|
||||||
const updatedFormData = { ...formData, [tableName]: initialData };
|
const updatedFormData = { ...formStore.previewFormData, [tableName]: initialData };
|
||||||
formStore.updatePreviewFormData(updatedFormData);
|
formStore.updatePreviewFormData(updatedFormData);
|
||||||
|
|
||||||
|
console.log('[ComponentPreview] getTableData: initialized new data for:', tableName);
|
||||||
|
|
||||||
return initialData;
|
return initialData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Simplified helper functions for regular fields only
|
||||||
|
const getFieldName = (component) => component.props.name;
|
||||||
|
const getFieldValue = (component) => {
|
||||||
|
const fieldName = component.props.name;
|
||||||
|
const formData = formStore.previewFormData;
|
||||||
|
|
||||||
|
// Silent check - don't trigger warnings for regular field access
|
||||||
|
if (formData && formData.hasOwnProperty(fieldName)) {
|
||||||
|
const value = formData[fieldName];
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return component default value if no form data exists
|
||||||
|
return component.props.value || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFieldInput = (component, event) => {
|
||||||
|
if (!props.isPreview) return;
|
||||||
|
|
||||||
|
const fieldName = component.props.name;
|
||||||
|
const newValue = event;
|
||||||
|
|
||||||
|
// Simple update for regular fields only
|
||||||
|
const updatedData = { ...formStore.previewFormData, [fieldName]: newValue };
|
||||||
|
formStore.updatePreviewFormData(updatedData);
|
||||||
|
emit('form-data-updated', updatedData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFieldChange = (component, event) => {
|
||||||
|
handleFieldInput(component, event);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to safely get field values for repeating groups without warnings
|
||||||
|
const getRepeatingGroupFieldValue = (group, fieldName, fieldType) => {
|
||||||
|
if (!group || typeof group !== 'object') {
|
||||||
|
return getDefaultValueForType(fieldType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.hasOwnProperty(fieldName)) {
|
||||||
|
const value = group[fieldName];
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return getDefaultValueForType(fieldType);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDefaultValueForType(fieldType);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to get default values based on field type
|
||||||
|
const getDefaultValueForType = (fieldType) => {
|
||||||
|
switch (fieldType) {
|
||||||
|
case 'number':
|
||||||
|
return 0;
|
||||||
|
case 'checkbox':
|
||||||
|
return [];
|
||||||
|
case 'select':
|
||||||
|
return '';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simple repeating group functions
|
||||||
|
const addRepeatingGroupItem = (groupName, children) => {
|
||||||
|
const currentData = safeGetField(groupName, formStore.previewFormData, { warn: false }) || [];
|
||||||
|
const newItem = {};
|
||||||
|
|
||||||
|
// Initialize fields from children
|
||||||
|
if (children) {
|
||||||
|
children.forEach(child => {
|
||||||
|
if (child.props && child.props.name) {
|
||||||
|
// Use the same default value logic as getDefaultValueForType
|
||||||
|
if (child.type === 'select' && child.props.options && child.props.options.length > 0) {
|
||||||
|
newItem[child.props.name] = child.props.options[0].value || '';
|
||||||
|
} else {
|
||||||
|
newItem[child.props.name] = getDefaultValueForType(child.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newData = [...currentData, newItem];
|
||||||
|
const updatedFormData = { ...formStore.previewFormData, [groupName]: newData };
|
||||||
|
|
||||||
|
formStore.updatePreviewFormData(updatedFormData);
|
||||||
|
emit('form-data-updated', updatedFormData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeRepeatingGroupItem = (groupName, index) => {
|
||||||
|
const currentData = safeGetField(groupName, formStore.previewFormData, { warn: false }) || [];
|
||||||
|
const newData = [...currentData];
|
||||||
|
newData.splice(index, 1);
|
||||||
|
|
||||||
|
const updatedFormData = { ...formStore.previewFormData, [groupName]: newData };
|
||||||
|
formStore.updatePreviewFormData(updatedFormData);
|
||||||
|
emit('form-data-updated', updatedFormData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateRepeatingGroupField = (groupName, groupIndex, fieldName, value) => {
|
||||||
|
const currentData = safeGetField(groupName, formStore.previewFormData, { warn: false }) || [];
|
||||||
|
const newData = [...currentData];
|
||||||
|
|
||||||
|
if (!newData[groupIndex]) {
|
||||||
|
newData[groupIndex] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
newData[groupIndex][fieldName] = value;
|
||||||
|
|
||||||
|
const updatedFormData = { ...formStore.previewFormData, [groupName]: newData };
|
||||||
|
formStore.updatePreviewFormData(updatedFormData);
|
||||||
|
emit('form-data-updated', updatedFormData);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Duplicate RepeatingGroupContainer definition removed
|
||||||
|
|
||||||
// Update table data for repeating-table component
|
// Update table data for repeating-table component
|
||||||
const updateTableData = (newData) => {
|
const updateTableData = (newData) => {
|
||||||
const tableName = props.component.props.name;
|
const tableName = props.component.props.name;
|
||||||
if (!tableName) return;
|
if (!tableName) return;
|
||||||
|
|
||||||
|
console.log('[ComponentPreview] updateTableData:', tableName, 'newData:', newData);
|
||||||
|
|
||||||
// Ensure newData is always an array
|
// Ensure newData is always an array
|
||||||
const safeNewData = Array.isArray(newData) ? newData : [];
|
const safeNewData = Array.isArray(newData) ? newData : [];
|
||||||
|
|
||||||
// Check if data actually changed to prevent unnecessary updates
|
// Check if data actually changed to prevent unnecessary updates
|
||||||
const currentData = safeGetField(tableName, formStore.previewFormData) || [];
|
const currentData = safeGetField(tableName, formStore.previewFormData, { warn: false }) || [];
|
||||||
const currentDataStr = JSON.stringify(currentData);
|
const currentDataStr = JSON.stringify(currentData);
|
||||||
const newDataStr = JSON.stringify(safeNewData);
|
const newDataStr = JSON.stringify(safeNewData);
|
||||||
|
|
||||||
if (currentDataStr === newDataStr) return;
|
if (currentDataStr === newDataStr) {
|
||||||
|
console.log('[ComponentPreview] updateTableData: no change detected, skipping update');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const updatedFormData = { ...formStore.previewFormData, [tableName]: safeNewData };
|
const updatedFormData = { ...formStore.previewFormData, [tableName]: safeNewData };
|
||||||
formStore.updatePreviewFormData(updatedFormData);
|
formStore.updatePreviewFormData(updatedFormData);
|
||||||
|
|
||||||
|
console.log('[ComponentPreview] updateTableData: updated form data for:', tableName, 'new data:', safeNewData);
|
||||||
|
|
||||||
// Emit the change for workflow page to sync with its local formData
|
// Emit the change for workflow page to sync with its local formData
|
||||||
emit('form-data-updated', updatedFormData);
|
emit('form-data-updated', updatedFormData);
|
||||||
});
|
});
|
||||||
@ -1386,8 +1503,8 @@ const updateTableData = (newData) => {
|
|||||||
|
|
||||||
// Form Section Component
|
// Form Section Component
|
||||||
const toggleSectionCollapse = (sectionId) => {
|
const toggleSectionCollapse = (sectionId) => {
|
||||||
// Find the section component and toggle its collapsed state
|
// Find the section component at any nesting level
|
||||||
const section = formStore.formComponents.find(comp => comp.id === sectionId);
|
const section = findContainerRecursively(sectionId);
|
||||||
if (section) {
|
if (section) {
|
||||||
section.props.collapsed = !section.props.collapsed;
|
section.props.collapsed = !section.props.collapsed;
|
||||||
formStore.updateComponent(section);
|
formStore.updateComponent(section);
|
||||||
@ -2066,6 +2183,15 @@ onMounted(() => {
|
|||||||
|
|
||||||
.repeating-table-wrapper{
|
.repeating-table-wrapper{
|
||||||
margin-bottom: 0.5rem !important;
|
margin-bottom: 0.5rem !important;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0; /* Allow shrinking */
|
||||||
|
overflow: hidden; /* Prevent table from breaking layout */
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0; /* Allow shrinking */
|
||||||
|
/* This is where the horizontal scrolling happens */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Form Section Component */
|
/* Form Section Component */
|
||||||
|
@ -56,22 +56,22 @@
|
|||||||
<!-- Data Table -->
|
<!-- Data Table -->
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<!-- Performance warning for too many columns -->
|
<!-- Performance warning for too many columns -->
|
||||||
<div v-if="config.columns.length > 20" class="performance-warning">
|
<div v-if="config.columns.length > 50" class="performance-warning">
|
||||||
<div class="warning-content">
|
<div class="warning-content">
|
||||||
<Icon name="heroicons:information-circle" class="warning-icon" />
|
<Icon name="heroicons:information-circle" class="warning-icon" />
|
||||||
<div class="warning-text">
|
<div class="warning-text">
|
||||||
<p class="warning-title">Performance Notice</p>
|
<p class="warning-title">Performance Notice</p>
|
||||||
<p class="warning-message">
|
<p class="warning-message">
|
||||||
This table has {{ config.columns.length }} columns. For optimal performance, only the first 20 columns are displayed.
|
This table has {{ config.columns.length }} columns. For optimal performance, only the first 50 columns are displayed.
|
||||||
Consider reducing the number of columns for better user experience.
|
The table is scrollable horizontally to view all data. Consider reducing the number of columns for better user experience.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="paginatedData.length > 0" class="table-wrapper">
|
<div v-if="paginatedData.length > 0" class="table-wrapper">
|
||||||
<div class="table-container" ref="tableContainer">
|
<div class="table-scroll-container" ref="tableContainer">
|
||||||
<table class="data-table">
|
<table class="data-table" :style="{ minWidth: calculateMinTableWidth }">
|
||||||
<thead class="table-header">
|
<thead class="table-header">
|
||||||
<tr>
|
<tr>
|
||||||
<th v-if="config.showRowNumbers" class="row-number-header">#</th>
|
<th v-if="config.showRowNumbers" class="row-number-header">#</th>
|
||||||
@ -79,7 +79,7 @@
|
|||||||
v-for="column in visibleColumns"
|
v-for="column in visibleColumns"
|
||||||
:key="column.name"
|
:key="column.name"
|
||||||
class="column-header"
|
class="column-header"
|
||||||
:style="{ width: getColumnWidth(column) }"
|
:style="{ width: getColumnWidth(column), minWidth: getColumnWidth(column) }"
|
||||||
>
|
>
|
||||||
<div class="column-header-content">
|
<div class="column-header-content">
|
||||||
<span class="header-text">{{ column.label }}</span>
|
<span class="header-text">{{ column.label }}</span>
|
||||||
@ -103,7 +103,7 @@
|
|||||||
v-for="column in visibleColumns"
|
v-for="column in visibleColumns"
|
||||||
:key="column.name"
|
:key="column.name"
|
||||||
class="data-cell"
|
class="data-cell"
|
||||||
:style="{ width: getColumnWidth(column) }"
|
:style="{ width: getColumnWidth(column), minWidth: getColumnWidth(column) }"
|
||||||
>
|
>
|
||||||
<div class="cell-content">
|
<div class="cell-content">
|
||||||
<SimpleCellValue
|
<SimpleCellValue
|
||||||
@ -415,33 +415,55 @@ const filteredData = computed(() => {
|
|||||||
// Column virtualization for large datasets
|
// Column virtualization for large datasets
|
||||||
const visibleColumns = computed(() => {
|
const visibleColumns = computed(() => {
|
||||||
const columns = props.config.columns
|
const columns = props.config.columns
|
||||||
const maxColumns = 20 // Limit visible columns for performance
|
const maxColumns = 50 // Increased limit for better user experience with scrolling
|
||||||
|
|
||||||
if (columns.length <= maxColumns) {
|
if (columns.length <= maxColumns) {
|
||||||
return columns
|
return columns
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return first 20 columns for performance
|
// Return first 50 columns for performance, but allow scrolling
|
||||||
return columns.slice(0, maxColumns)
|
return columns.slice(0, maxColumns)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Column width calculation
|
// Column width calculation
|
||||||
const getColumnWidth = (column) => {
|
const getColumnWidth = (column) => {
|
||||||
const baseWidth = 150
|
const baseWidth = 150 // Increased base width
|
||||||
const typeWidths = {
|
const typeWidths = {
|
||||||
text: 150,
|
text: 150,
|
||||||
number: 100,
|
number: 100,
|
||||||
email: 200,
|
email: 180,
|
||||||
tel: 120,
|
tel: 120,
|
||||||
date: 120,
|
date: 120,
|
||||||
time: 100,
|
time: 100,
|
||||||
url: 200,
|
url: 180,
|
||||||
select: 150,
|
select: 150,
|
||||||
checkbox: 80,
|
checkbox: 80,
|
||||||
textarea: 200
|
textarea: 200
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${typeWidths[column.type] || baseWidth}px`
|
// For tables with many columns, still use reasonable widths
|
||||||
|
const totalColumns = props.config.columns.length
|
||||||
|
if (totalColumns > 10) {
|
||||||
|
const adjustedWidths = {
|
||||||
|
text: 120,
|
||||||
|
number: 90,
|
||||||
|
email: 160,
|
||||||
|
tel: 110,
|
||||||
|
date: 110,
|
||||||
|
time: 90,
|
||||||
|
url: 160,
|
||||||
|
select: 120,
|
||||||
|
checkbox: 70,
|
||||||
|
textarea: 160
|
||||||
|
}
|
||||||
|
const width = `${adjustedWidths[column.type] || baseWidth}px`
|
||||||
|
console.log(`[RepeatingTable] Column ${column.name} width: ${width}`)
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = `${typeWidths[column.type] || baseWidth}px`
|
||||||
|
console.log(`[RepeatingTable] Column ${column.name} width: ${width}`)
|
||||||
|
return width
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record key generation for better Vue rendering
|
// Record key generation for better Vue rendering
|
||||||
@ -676,6 +698,51 @@ const goToPage = (page) => {
|
|||||||
currentPage.value = page
|
currentPage.value = page
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate minimum table width based on columns
|
||||||
|
const calculateMinTableWidth = computed(() => {
|
||||||
|
if (!props.config.columns) return 'auto'
|
||||||
|
|
||||||
|
let totalWidth = 0
|
||||||
|
|
||||||
|
// Add row number column width if enabled
|
||||||
|
if (props.config.showRowNumbers) {
|
||||||
|
totalWidth += 60 // Row number column width
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add column widths
|
||||||
|
visibleColumns.value.forEach(column => {
|
||||||
|
const width = parseInt(getColumnWidth(column))
|
||||||
|
totalWidth += width
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add actions column width if enabled
|
||||||
|
if (showActions.value) {
|
||||||
|
totalWidth += 100 // Actions column width
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add some padding for borders and spacing
|
||||||
|
totalWidth += 40
|
||||||
|
|
||||||
|
console.log('[RepeatingTable] Calculated minimum width:', totalWidth)
|
||||||
|
return `${totalWidth}px`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Debug table width on mount
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (tableContainer.value) {
|
||||||
|
const table = tableContainer.value.querySelector('.data-table')
|
||||||
|
if (table) {
|
||||||
|
console.log('[RepeatingTable] Table width:', table.offsetWidth)
|
||||||
|
console.log('[RepeatingTable] Container width:', tableContainer.value.offsetWidth)
|
||||||
|
console.log('[RepeatingTable] Scroll width:', table.scrollWidth)
|
||||||
|
console.log('[RepeatingTable] Has horizontal scroll:', table.scrollWidth > tableContainer.value.offsetWidth)
|
||||||
|
console.log('[RepeatingTable] Calculated min width:', calculateMinTableWidth.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// Cleanup on unmount
|
// Cleanup on unmount
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
columnCache.value.clear()
|
columnCache.value.clear()
|
||||||
@ -686,21 +753,33 @@ onUnmounted(() => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
/* Table Container with Sticky Header */
|
/* Table Container with Sticky Header */
|
||||||
.table-container {
|
.table-container {
|
||||||
overflow: hidden;
|
|
||||||
background: white;
|
background: white;
|
||||||
max-height: 500px;
|
|
||||||
overflow-y: auto;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: #cbd5e1 #f1f5f9;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-scroll-container {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 500px;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #cbd5e1 #f1f5f9;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
/* Ensure horizontal scrolling is always available */
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.data-table {
|
.data-table {
|
||||||
table-layout: fixed;
|
table-layout: auto; /* Changed from fixed to auto for better column sizing */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
/* Ensure table is wide enough to trigger scrolling */
|
||||||
|
white-space: nowrap;
|
||||||
|
/* Force minimum width to ensure scrolling */
|
||||||
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sticky Header */
|
/* Sticky Header */
|
||||||
@ -718,42 +797,47 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Custom scrollbar for webkit browsers */
|
/* Custom scrollbar for webkit browsers */
|
||||||
.table-container::-webkit-scrollbar {
|
.table-scroll-container::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-container::-webkit-scrollbar-track {
|
.table-scroll-container::-webkit-scrollbar-track {
|
||||||
background: #f1f5f9;
|
background: #f1f5f9;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-container::-webkit-scrollbar-thumb {
|
.table-scroll-container::-webkit-scrollbar-thumb {
|
||||||
background: #cbd5e1;
|
background: #cbd5e1;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-container::-webkit-scrollbar-thumb:hover {
|
.table-scroll-container::-webkit-scrollbar-thumb:hover {
|
||||||
background: #94a3b8;
|
background: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Horizontal scrollbar styling */
|
||||||
|
.table-scroll-container::-webkit-scrollbar:horizontal {
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.column-header,
|
.column-header,
|
||||||
.data-cell {
|
.data-cell {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
padding: 8px 12px;
|
||||||
text-overflow: ellipsis;
|
vertical-align: middle;
|
||||||
max-width: 0;
|
/* Remove text truncation to allow full content display */
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-content {
|
.cell-content {
|
||||||
overflow: hidden;
|
/* Allow content to expand naturally */
|
||||||
text-overflow: ellipsis;
|
min-width: max-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-value {
|
.cell-value {
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
/* Remove text truncation to show full content */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Virtual scrolling optimizations */
|
/* Virtual scrolling optimizations */
|
||||||
@ -801,7 +885,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
/* Responsive optimizations */
|
/* Responsive optimizations */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.table-content {
|
.table-scroll-container {
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -813,7 +897,7 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.table-content {
|
.table-scroll-container {
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -823,6 +907,35 @@ onUnmounted(() => {
|
|||||||
max-width: 100px;
|
max-width: 100px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Enhanced scrolling for wide tables */
|
||||||
|
.table-scroll-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll-container::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 20px;
|
||||||
|
background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.8));
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll-container::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 20px;
|
||||||
|
background: linear-gradient(to left, transparent, rgba(255, 255, 255, 0.8));
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
.repeating-table-container {
|
.repeating-table-container {
|
||||||
@apply border border-gray-200 rounded-lg overflow-hidden bg-white shadow-sm w-full;
|
@apply border border-gray-200 rounded-lg overflow-hidden bg-white shadow-sm w-full;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Utility to safely get a field value from a form data object
|
// Utility to safely get a field value from a form data object
|
||||||
export function safeGetField(field, formData) {
|
export function safeGetField(field, formData, options = {}) {
|
||||||
if (formData && Object.prototype.hasOwnProperty.call(formData, field)) {
|
if (formData && Object.prototype.hasOwnProperty.call(formData, field)) {
|
||||||
const value = formData[field];
|
const value = formData[field];
|
||||||
// If the value is undefined or null, return empty string for backward compatibility
|
// If the value is undefined or null, return empty string for backward compatibility
|
||||||
@ -8,9 +8,14 @@ export function safeGetField(field, formData) {
|
|||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
// Only warn in development
|
// Only warn for specific cases or when explicitly requested
|
||||||
console.warn(`Field '${field}' is missing or inaccessible.`);
|
if (process.env.NODE_ENV !== 'production' && options.warn !== false) {
|
||||||
|
// Don't warn for newly added components or fields that haven't been initialized yet
|
||||||
|
const isNewField = field && (field.includes('_') && /\d+$/.test(field));
|
||||||
|
if (!isNewField) {
|
||||||
|
console.warn(`Field '${field}' is missing or inaccessible.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
@ -286,7 +286,6 @@
|
|||||||
type="form"
|
type="form"
|
||||||
@submit="handlePreviewSubmit"
|
@submit="handlePreviewSubmit"
|
||||||
:actions="false"
|
:actions="false"
|
||||||
v-model="previewFormData"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="grid-preview-container"
|
class="grid-preview-container"
|
||||||
@ -2069,43 +2068,43 @@ const handleJsonImport = (event) => {
|
|||||||
// Ensure all required info display properties
|
// Ensure all required info display properties
|
||||||
processedProps = {
|
processedProps = {
|
||||||
title: processedProps.title || 'Information',
|
title: processedProps.title || 'Information',
|
||||||
name: processedDefaultProps.name || `info_display_${index + 1}`,
|
name: processedProps.name || `info_display_${index + 1}`,
|
||||||
help: processedDefaultProps.help || '',
|
help: processedProps.help || '',
|
||||||
layout: processedDefaultProps.layout || 'vertical',
|
layout: processedProps.layout || 'vertical',
|
||||||
showBorder: processedDefaultProps.showBorder !== undefined ? processedDefaultProps.showBorder : true,
|
showBorder: processedProps.showBorder !== undefined ? processedProps.showBorder : true,
|
||||||
backgroundColor: processedDefaultProps.backgroundColor || '#f8fafc',
|
backgroundColor: processedProps.backgroundColor || '#f8fafc',
|
||||||
fields: Array.isArray(processedDefaultProps.fields) ? processedDefaultProps.fields : [
|
fields: Array.isArray(processedProps.fields) ? processedProps.fields : [
|
||||||
{ label: 'Info Item', value: 'Value', key: 'item_1' }
|
{ label: 'Info Item', value: 'Value', key: 'item_1' }
|
||||||
],
|
],
|
||||||
...processedDefaultProps
|
...processedProps
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'file':
|
case 'file':
|
||||||
// Ensure all required file upload properties
|
// Ensure all required file upload properties
|
||||||
processedProps = {
|
processedProps = {
|
||||||
label: processedDefaultProps.label || 'File Upload',
|
label: processedProps.label || 'File Upload',
|
||||||
name: processedDefaultProps.name || `file_upload_${index + 1}`,
|
name: processedProps.name || `file_upload_${index + 1}`,
|
||||||
help: processedDefaultProps.help || 'Upload a file',
|
help: processedProps.help || 'Upload a file',
|
||||||
accept: processedDefaultProps.accept || '.pdf,.doc,.docx,.jpg,.jpeg,.png',
|
accept: processedProps.accept || '.pdf,.doc,.docx,.jpg,.jpeg,.png',
|
||||||
...processedDefaultProps
|
...processedProps
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'heading':
|
case 'heading':
|
||||||
// Ensure all required heading properties
|
// Ensure all required heading properties
|
||||||
processedProps = {
|
processedProps = {
|
||||||
value: processedDefaultProps.value || 'Heading',
|
value: processedProps.value || 'Heading',
|
||||||
level: processedDefaultProps.level || 2,
|
level: processedProps.level || 2,
|
||||||
...processedDefaultProps
|
...processedProps
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'paragraph':
|
case 'paragraph':
|
||||||
// Ensure all required paragraph properties
|
// Ensure all required paragraph properties
|
||||||
processedProps = {
|
processedProps = {
|
||||||
value: processedDefaultProps.value || 'Paragraph text',
|
value: processedProps.value || 'Paragraph text',
|
||||||
...processedDefaultProps
|
...processedProps
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -2113,7 +2112,7 @@ const handleJsonImport = (event) => {
|
|||||||
case 'radio':
|
case 'radio':
|
||||||
case 'checkbox':
|
case 'checkbox':
|
||||||
// Ensure options array exists
|
// Ensure options array exists
|
||||||
if (!Array.isArray(processedDefaultProps.options) || processedDefaultProps.options.length === 0) {
|
if (!Array.isArray(processedProps.options) || processedProps.options.length === 0) {
|
||||||
processedDefaultProps.options = [
|
processedDefaultProps.options = [
|
||||||
{ label: 'Option 1', value: 'option_1' },
|
{ label: 'Option 1', value: 'option_1' },
|
||||||
{ label: 'Option 2', value: 'option_2' }
|
{ label: 'Option 2', value: 'option_2' }
|
||||||
@ -2537,18 +2536,13 @@ const saveFormSettings = () => {
|
|||||||
// Preview form data for script interactions
|
// Preview form data for script interactions
|
||||||
const previewFormData = ref({});
|
const previewFormData = ref({});
|
||||||
|
|
||||||
// Initialize preview form data with default values
|
// Helper function to process nested components recursively
|
||||||
watchEffect(() => {
|
const processNestedComponents = (components, existingFormData, newDefaults, hasNewComponents) => {
|
||||||
console.log('[FormBuilder] watchEffect for previewFormData initialization. Current form components:', formStore.formComponents.length);
|
components.forEach(component => {
|
||||||
const existingFormData = { ...previewFormData.value }; // Preserve current user inputs
|
|
||||||
let newDefaults = {};
|
|
||||||
let hasNewComponents = false;
|
|
||||||
|
|
||||||
formStore.formComponents.forEach(component => {
|
|
||||||
if (component.props.name) {
|
if (component.props.name) {
|
||||||
// If field is not already in existingFormData, it's a new component or needs initialization
|
// If field is not already in existingFormData, it's a new component or needs initialization
|
||||||
if (!existingFormData.hasOwnProperty(component.props.name)) {
|
if (!existingFormData.hasOwnProperty(component.props.name)) {
|
||||||
hasNewComponents = true;
|
hasNewComponents.value = true;
|
||||||
// Set default values based on component type
|
// Set default values based on component type
|
||||||
switch (component.type) {
|
switch (component.type) {
|
||||||
case 'checkbox':
|
case 'checkbox':
|
||||||
@ -2620,25 +2614,48 @@ watchEffect(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process nested components if this component has children
|
||||||
|
if (component.props.children && Array.isArray(component.props.children)) {
|
||||||
|
processNestedComponents(component.props.children, existingFormData, newDefaults, hasNewComponents);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize preview form data with default values
|
||||||
|
watchEffect(() => {
|
||||||
|
const existingFormData = { ...previewFormData.value }; // Preserve current user inputs
|
||||||
|
let newDefaults = {};
|
||||||
|
let hasNewComponents = { value: false };
|
||||||
|
|
||||||
|
// Process all components including nested ones
|
||||||
|
processNestedComponents(formStore.formComponents, existingFormData, newDefaults, hasNewComponents);
|
||||||
|
|
||||||
// Only update previewFormData if it's the initial load (empty) or new components were added that need defaults
|
// Only update previewFormData if it's the initial load (empty) or new components were added that need defaults
|
||||||
// This prevents overwriting user input when existing components change their props (which also triggers this watchEffect)
|
// This prevents overwriting user input when existing components change their props (which also triggers this watchEffect)
|
||||||
const isInitialLoad = Object.keys(previewFormData.value).length === 0 && Object.keys(newDefaults).length > 0;
|
const isInitialLoad = Object.keys(previewFormData.value).length === 0 && Object.keys(newDefaults).length > 0;
|
||||||
|
|
||||||
if (isInitialLoad || hasNewComponents) {
|
// CRITICAL FIX: Don't reinitialize if we already have data for repeating groups/arrays
|
||||||
console.log('[FormBuilder] Initializing/merging preview form data. Initial load:', isInitialLoad, 'Has new components:', hasNewComponents);
|
// This prevents data loss when users are editing repeating group items
|
||||||
previewFormData.value = { ...existingFormData, ...newDefaults };
|
const hasExistingArrayData = Object.keys(existingFormData).some(key => Array.isArray(existingFormData[key]) && existingFormData[key].length > 0);
|
||||||
console.log('[FormBuilder] Preview form data after init/merge:', previewFormData.value);
|
|
||||||
} else {
|
if (isInitialLoad || (hasNewComponents.value && !hasExistingArrayData)) {
|
||||||
console.log('[FormBuilder] Skipping full previewFormData re-initialization to preserve user input.');
|
// Smart merge: preserve existing array data, only add truly new defaults
|
||||||
|
const smartMerge = { ...existingFormData };
|
||||||
|
|
||||||
|
// Only add defaults for fields that don't exist at all
|
||||||
|
Object.keys(newDefaults).forEach(key => {
|
||||||
|
if (!smartMerge.hasOwnProperty(key)) {
|
||||||
|
smartMerge[key] = newDefaults[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
previewFormData.value = smartMerge;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle script-driven field changes
|
// Handle script-driven field changes
|
||||||
const handleScriptFieldChange = ({ fieldName, value }) => {
|
const handleScriptFieldChange = ({ fieldName, value }) => {
|
||||||
console.log('[FormBuilder] Script field change:', fieldName, '=', value);
|
|
||||||
|
|
||||||
// Update the reactive form data
|
// Update the reactive form data
|
||||||
const newFormData = {
|
const newFormData = {
|
||||||
...previewFormData.value,
|
...previewFormData.value,
|
||||||
@ -2652,15 +2669,12 @@ const handleScriptFieldChange = ({ fieldName, value }) => {
|
|||||||
|
|
||||||
// Try to force FormKit form to update
|
// Try to force FormKit form to update
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
console.log('[FormBuilder] Updated form data:', newFormData);
|
|
||||||
|
|
||||||
// Try to access the FormKit form node and update it directly
|
// Try to access the FormKit form node and update it directly
|
||||||
if (previewForm.value && previewForm.value.node) {
|
if (previewForm.value && previewForm.value.node) {
|
||||||
try {
|
try {
|
||||||
previewForm.value.node.input(newFormData);
|
previewForm.value.node.input(newFormData);
|
||||||
console.log('[FormBuilder] Force updated FormKit form');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[FormBuilder] Could not force update FormKit form:', error);
|
// Silent fallback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -2669,26 +2683,16 @@ const handleScriptFieldChange = ({ fieldName, value }) => {
|
|||||||
// Handle script-driven field validation
|
// Handle script-driven field validation
|
||||||
const handleScriptFieldValidate = ({ fieldName }) => {
|
const handleScriptFieldValidate = ({ fieldName }) => {
|
||||||
// Could integrate with FormKit validation here
|
// Could integrate with FormKit validation here
|
||||||
console.log(`Validating field: ${fieldName}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle FormKit form input events
|
// Handle FormKit form input events
|
||||||
const handleFormKitInput = (formData, node) => {
|
const handleFormKitInput = (formData, node) => {
|
||||||
console.log('[FormBuilder] FormKit input event received!');
|
|
||||||
console.log('[FormBuilder] FormKit formData:', JSON.parse(JSON.stringify(formData)));
|
|
||||||
console.log('[FormBuilder] Current previewFormData before update:', JSON.parse(JSON.stringify(previewFormData.value)));
|
|
||||||
|
|
||||||
// Update our reactive form data - this should trigger the FormScriptEngine watcher
|
// Update our reactive form data - this should trigger the FormScriptEngine watcher
|
||||||
const oldPreviewData = { ...previewFormData.value };
|
const oldPreviewData = { ...previewFormData.value };
|
||||||
previewFormData.value = { ...formData };
|
previewFormData.value = { ...formData };
|
||||||
|
|
||||||
console.log('[FormBuilder] Updated previewFormData to:', JSON.parse(JSON.stringify(previewFormData.value)));
|
|
||||||
console.log('[FormBuilder] Did previewFormData actually change?', JSON.stringify(oldPreviewData) !== JSON.stringify(previewFormData.value));
|
|
||||||
|
|
||||||
// Make form data accessible to component previews
|
// Make form data accessible to component previews
|
||||||
formStore.updatePreviewFormData(previewFormData.value);
|
formStore.updatePreviewFormData(previewFormData.value);
|
||||||
|
|
||||||
console.log('[FormBuilder] FormStore preview data updated to:', JSON.parse(JSON.stringify(formStore.previewFormData)));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Watch for changes in previewFormData to trigger FormScriptEngine
|
// Watch for changes in previewFormData to trigger FormScriptEngine
|
||||||
@ -2702,9 +2706,13 @@ watch(() => previewFormData.value, (newData, oldData) => {
|
|||||||
return; // No actual change, skip update
|
return; // No actual change, skip update
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[FormBuilder] previewFormData watcher triggered!');
|
// CRITICAL FIX: Don't sync back to store if we have any array data at all
|
||||||
console.log('[FormBuilder] New data:', newDataStr);
|
// ComponentPreview.vue manages its own array data directly with the store
|
||||||
console.log('[FormBuilder] Old data:', oldDataStr);
|
const hasArrayData = Object.keys(newData).some(key => Array.isArray(newData[key]));
|
||||||
|
|
||||||
|
if (hasArrayData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Use nextTick to avoid synchronous updates that can cause recursion
|
// Use nextTick to avoid synchronous updates that can cause recursion
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@ -2712,7 +2720,6 @@ watch(() => previewFormData.value, (newData, oldData) => {
|
|||||||
const currentDataStr = JSON.stringify(previewFormData.value);
|
const currentDataStr = JSON.stringify(previewFormData.value);
|
||||||
if (currentDataStr === newDataStr) {
|
if (currentDataStr === newDataStr) {
|
||||||
formStore.updatePreviewFormData(newData);
|
formStore.updatePreviewFormData(newData);
|
||||||
console.log('[FormBuilder] FormStore preview data updated to:', JSON.parse(JSON.stringify(formStore.previewFormData)));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, { deep: true, immediate: false });
|
}, { deep: true, immediate: false });
|
||||||
@ -3379,6 +3386,8 @@ const handleFormRestored = (restoredForm) => {
|
|||||||
console.log('Form restored:', restoredForm);
|
console.log('Form restored:', restoredForm);
|
||||||
// You might want to update the form state or show a success message
|
// You might want to update the form state or show a success message
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user