Enhance ComponentPreview and FormBuilder Components with Drag-and-Drop Functionality
- Updated ComponentPreview.vue to implement a draggable container for nested components, allowing users to reorder fields within repeating groups. - Enhanced the user interface with visual feedback during drag-and-drop actions, improving usability and interaction. - Modified FormBuilderComponents.vue to include default properties for nested components, facilitating better organization and management of form fields. - Improved FormBuilderFieldSettingsModal.vue to provide clearer information about repeating group containers and their functionalities. - Refactored event handling for component updates and deletions to support nested components within sections and repeating groups, ensuring consistent data management. - Updated styles across components to enhance the visual experience during drag-and-drop operations and improve overall aesthetics.
This commit is contained in:
parent
f86fe87fc5
commit
415ac5a0d1
@ -144,24 +144,81 @@
|
||||
{{ component.props.help }}
|
||||
</div>
|
||||
|
||||
<!-- Default group preview (in edit mode) -->
|
||||
<!-- Builder mode - show draggable container -->
|
||||
<div v-if="!isPreview" class="repeating-groups space-y-4">
|
||||
<div class="group-item border border-gray-200 rounded-md p-3 bg-gray-50">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<h4 class="text-sm font-medium text-gray-700">Item 1</h4>
|
||||
<button type="button" class="text-red-500 hover:text-red-700 text-sm">
|
||||
{{ component.props.removeText || 'Remove' }}
|
||||
<!-- Container for draggable fields -->
|
||||
<div
|
||||
class="repeating-group-container border-2 border-dashed border-gray-300 rounded-md p-4 min-h-[100px] bg-gray-50"
|
||||
:class="{
|
||||
'border-blue-400 bg-blue-50': sectionDropStates[component.id]?.isDraggingOver
|
||||
}"
|
||||
@dragover.prevent="handleSectionDragOver($event, component.id)"
|
||||
@dragleave="handleSectionDragLeave($event, component.id)"
|
||||
@drop="handleSectionDrop($event, component.id)"
|
||||
@dragenter.prevent="handleSectionDragEnter($event, component.id)"
|
||||
>
|
||||
<div v-if="component.props.children && component.props.children.length > 0" class="nested-components">
|
||||
<draggable
|
||||
v-model="component.props.children"
|
||||
group="form-components"
|
||||
item-key="id"
|
||||
handle=".drag-handle"
|
||||
ghost-class="ghost"
|
||||
animation="300"
|
||||
class="grid grid-cols-12 gap-2"
|
||||
@end="onNestedDragEnd"
|
||||
@add="onNestedComponentAdd"
|
||||
>
|
||||
<template #item="{ element: childElement, index: childIndex }">
|
||||
<div
|
||||
class="form-component relative border rounded-md overflow-hidden transition-all duration-200 bg-white"
|
||||
:class="'border-gray-200 hover:border-blue-300 hover:shadow-md'"
|
||||
:style="{
|
||||
gridColumn: childElement.props.gridColumn || 'span 6'
|
||||
}"
|
||||
>
|
||||
<!-- Component actions -->
|
||||
<div class="component-actions absolute right-1 top-1 flex space-x-1 z-10">
|
||||
<button
|
||||
class="p-1 text-gray-400 hover:text-blue-600 rounded"
|
||||
title="Component settings"
|
||||
@click.stop="openNestedComponentSettings(childElement)"
|
||||
>
|
||||
<Icon name="heroicons:cog-6-tooth" class="w-3 h-3" />
|
||||
</button>
|
||||
<button
|
||||
class="p-1 text-gray-400 hover:text-gray-600 rounded"
|
||||
title="Drag to reorder"
|
||||
>
|
||||
<span class="drag-handle cursor-move">
|
||||
<Icon name="material-symbols:drag-indicator" class="w-3 h-3" />
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="p-1 text-gray-400 hover:text-red-500 rounded"
|
||||
title="Remove from group"
|
||||
@click.stop="removeFromSection(component.id, childIndex)"
|
||||
>
|
||||
<Icon name="material-symbols:close" class="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<template v-for="(field, fieldIndex) in component.props.fields" :key="fieldIndex">
|
||||
<FormKit :type="field.type" :label="field.label" :placeholder="field.placeholder"
|
||||
:name="`${field.name}_1`" :options="field.options" disabled />
|
||||
<div class="p-2">
|
||||
<component-preview :component="childElement" :is-preview="false" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div v-else class="text-center py-8">
|
||||
<Icon name="material-symbols:add-circle-outline" class="w-8 h-8 text-gray-400 mx-auto mb-2" />
|
||||
<p class="text-sm text-gray-500">Drag fields here to add them to this repeating group</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Item button for preview -->
|
||||
<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">
|
||||
<Icon name="material-symbols:add-circle-outline" class="w-4 h-4 mr-1" />
|
||||
@ -169,7 +226,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Functional groups (in preview mode) -->
|
||||
<!-- Preview mode - show functional repeating groups -->
|
||||
<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">
|
||||
@ -181,12 +238,17 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<template v-for="(field, fieldIndex) in component.props.fields" :key="fieldIndex">
|
||||
<FormKit :type="field.type" :label="field.label" :placeholder="field.placeholder"
|
||||
:name="`${component.props.name}.${groupIndex}.${field.name}`" :options="field.options"
|
||||
:value="group[field.name]"
|
||||
@input="updateGroupField(component.props.name, groupIndex, field.name, $event)" />
|
||||
<div class="grid grid-cols-12 gap-2">
|
||||
<!-- Render children components for each group item -->
|
||||
<template v-for="(child, childIndex) in component.props.children" :key="childIndex">
|
||||
<div
|
||||
class="form-component"
|
||||
:style="{
|
||||
gridColumn: child.props.gridColumn || 'span 6'
|
||||
}"
|
||||
>
|
||||
<component-preview :component="child" :is-preview="true" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@ -462,9 +524,16 @@
|
||||
<!-- In preview mode, show only the nested components (no placeholder) -->
|
||||
<div v-if="isPreview" class="section-fields">
|
||||
<!-- Render nested components if they exist -->
|
||||
<div v-if="component.props.children && component.props.children.length > 0" class="space-y-3">
|
||||
<div v-if="component.props.children && component.props.children.length > 0" class="grid grid-cols-12 gap-2">
|
||||
<template v-for="(childComponent, childIndex) in component.props.children" :key="childIndex">
|
||||
<div
|
||||
class="form-component"
|
||||
:style="{
|
||||
gridColumn: childComponent.props.gridColumn || 'span 6'
|
||||
}"
|
||||
>
|
||||
<component-preview :component="childComponent" :is-preview="true" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Show subtle indication for empty sections in preview mode (optional) -->
|
||||
@ -579,6 +648,7 @@ import { useFormBuilderStore } from '~/stores/formBuilder';
|
||||
import FormBuilderFieldSettingsModal from '~/components/FormBuilderFieldSettingsModal.vue';
|
||||
import { safeGetField } from '~/composables/safeGetField';
|
||||
import { onMounted, onUnmounted, watch, computed } from 'vue';
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
const props = defineProps({
|
||||
component: {
|
||||
@ -613,6 +683,9 @@ const sectionDropStates = ref({});
|
||||
const showNestedSettingsModal = ref(false);
|
||||
const selectedNestedComponent = ref(null);
|
||||
|
||||
// Track selected component ID for highlighting
|
||||
const selectedComponentId = ref(null);
|
||||
|
||||
// Track timers and DOM elements for cleanup
|
||||
let lightbox = null;
|
||||
let importInput = null;
|
||||
@ -1323,18 +1396,18 @@ const removeFromSection = (sectionId, childIndex) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSectionDragOver = (event, sectionId) => {
|
||||
const handleSectionDragOver = (event, containerId) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// Initialize section drop state if it doesn't exist
|
||||
if (!sectionDropStates.value[sectionId]) {
|
||||
sectionDropStates.value[sectionId] = { isDraggingOver: false };
|
||||
// Initialize container drop state if it doesn't exist
|
||||
if (!sectionDropStates.value[containerId]) {
|
||||
sectionDropStates.value[containerId] = { isDraggingOver: false };
|
||||
}
|
||||
sectionDropStates.value[sectionId].isDraggingOver = true;
|
||||
sectionDropStates.value[containerId].isDraggingOver = true;
|
||||
};
|
||||
|
||||
const handleSectionDragLeave = (event, sectionId) => {
|
||||
const handleSectionDragLeave = (event, containerId) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
@ -1347,18 +1420,70 @@ const handleSectionDragLeave = (event, sectionId) => {
|
||||
event.clientY > rect.bottom
|
||||
);
|
||||
|
||||
if (isOutside && sectionDropStates.value[sectionId]) {
|
||||
sectionDropStates.value[sectionId].isDraggingOver = false;
|
||||
if (isOutside && sectionDropStates.value[containerId]) {
|
||||
sectionDropStates.value[containerId].isDraggingOver = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSectionDrop = (event, sectionId) => {
|
||||
// Container drag handling functions
|
||||
const handleContainerDragStart = (event) => {
|
||||
console.log('Container drag started:', event);
|
||||
};
|
||||
|
||||
const handleContainerDragEnd = (event) => {
|
||||
console.log('Container drag ended:', event);
|
||||
// The draggable component automatically updates the array order
|
||||
// We just need to update the parent container to trigger reactivity
|
||||
if (props.component) {
|
||||
formStore.updateComponent(props.component);
|
||||
}
|
||||
};
|
||||
|
||||
// Nested component selection
|
||||
const selectNestedComponent = (nestedComponent) => {
|
||||
if (!nestedComponent || !nestedComponent.id) return;
|
||||
|
||||
selectedComponentId.value = nestedComponent.id;
|
||||
selectedNestedComponent.value = nestedComponent;
|
||||
|
||||
// Emit the selection event to parent
|
||||
emit('select-nested-component', nestedComponent);
|
||||
|
||||
console.log('Selected nested component:', nestedComponent);
|
||||
};
|
||||
|
||||
// Delete nested component
|
||||
const deleteNestedComponent = (componentId) => {
|
||||
if (!componentId || !props.component) return;
|
||||
|
||||
// Find the component in the children array
|
||||
const childIndex = props.component.props.children.findIndex(child => child.id === componentId);
|
||||
if (childIndex !== -1) {
|
||||
// Remove the component
|
||||
const deletedComponent = props.component.props.children.splice(childIndex, 1)[0];
|
||||
|
||||
// Update the container to trigger reactivity
|
||||
formStore.updateComponent(props.component);
|
||||
|
||||
// Clear selection if the deleted component was selected
|
||||
if (selectedComponentId.value === componentId) {
|
||||
selectedComponentId.value = null;
|
||||
selectedNestedComponent.value = null;
|
||||
}
|
||||
|
||||
console.log('Deleted nested component:', deletedComponent);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
const handleSectionDrop = (event, containerId) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// Reset drag state
|
||||
if (sectionDropStates.value[sectionId]) {
|
||||
sectionDropStates.value[sectionId].isDraggingOver = false;
|
||||
if (sectionDropStates.value[containerId]) {
|
||||
sectionDropStates.value[containerId].isDraggingOver = false;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -1384,7 +1509,7 @@ const handleSectionDrop = (event, sectionId) => {
|
||||
name: componentData.name,
|
||||
props: {
|
||||
...componentData.defaultProps,
|
||||
gridColumn: 'span 6', // Default to half width in sections
|
||||
gridColumn: 'span 6', // Default to half width in containers
|
||||
width: '50%',
|
||||
// Ensure the component has a proper label
|
||||
label: componentData.defaultProps.label || componentData.name || `${componentData.type.charAt(0).toUpperCase() + componentData.type.slice(1)} Field`,
|
||||
@ -1392,41 +1517,42 @@ const handleSectionDrop = (event, sectionId) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Find the target section
|
||||
const section = formStore.formComponents.find(comp => comp.id === sectionId);
|
||||
if (section) {
|
||||
// Find the target container (section or repeating-group)
|
||||
const container = formStore.formComponents.find(comp => comp.id === containerId);
|
||||
if (container && (container.type === 'form-section' || container.type === 'repeating-group')) {
|
||||
// Initialize children array if it doesn't exist
|
||||
if (!section.props.children) {
|
||||
section.props.children = [];
|
||||
if (!container.props.children) {
|
||||
container.props.children = [];
|
||||
}
|
||||
|
||||
// Add the component to the section
|
||||
section.props.children.push(newComponent);
|
||||
// Add the component to the container
|
||||
container.props.children.push(newComponent);
|
||||
|
||||
// Update the section in the form store
|
||||
formStore.updateComponent(section);
|
||||
// Update the container in the form store
|
||||
formStore.updateComponent(container);
|
||||
|
||||
// Record the action in history
|
||||
formStore.recordHistory('add_component_to_section', {
|
||||
formStore.recordHistory('add_component_to_container', {
|
||||
componentType: newComponent.type,
|
||||
componentName: newComponent.name,
|
||||
sectionId: sectionId
|
||||
containerId: containerId,
|
||||
containerType: container.type
|
||||
});
|
||||
|
||||
console.log('Component added to section:', newComponent);
|
||||
console.log('Component added to container:', newComponent);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error dropping component into section:', error);
|
||||
console.error('Error dropping component into container:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSectionDragEnter = (event, sectionId) => {
|
||||
const handleSectionDragEnter = (event, containerId) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// Initialize section drop state if it doesn't exist
|
||||
if (!sectionDropStates.value[sectionId]) {
|
||||
sectionDropStates.value[sectionId] = { isDraggingOver: false };
|
||||
// Initialize container drop state if it doesn't exist
|
||||
if (!sectionDropStates.value[containerId]) {
|
||||
sectionDropStates.value[containerId] = { isDraggingOver: false };
|
||||
}
|
||||
};
|
||||
|
||||
@ -1445,20 +1571,20 @@ const closeNestedSettingsModal = () => {
|
||||
const saveNestedComponentSettings = (updatedComponent) => {
|
||||
if (!updatedComponent || !selectedNestedComponent.value) return;
|
||||
|
||||
// Find the parent section and update the nested component
|
||||
const parentSection = formStore.formComponents.find(comp =>
|
||||
comp.type === 'form-section' &&
|
||||
// Find the parent container (section or repeating-group) and update the nested component
|
||||
const parentContainer = formStore.formComponents.find(comp =>
|
||||
(comp.type === 'form-section' || comp.type === 'repeating-group') &&
|
||||
comp.props.children &&
|
||||
comp.props.children.some(child => child.id === updatedComponent.id)
|
||||
);
|
||||
|
||||
if (parentSection) {
|
||||
const childIndex = parentSection.props.children.findIndex(child => child.id === updatedComponent.id);
|
||||
if (parentContainer) {
|
||||
const childIndex = parentContainer.props.children.findIndex(child => child.id === updatedComponent.id);
|
||||
if (childIndex !== -1) {
|
||||
// Update the nested component
|
||||
parentSection.props.children[childIndex] = { ...updatedComponent };
|
||||
// Update the section to trigger reactivity
|
||||
formStore.updateComponent(parentSection);
|
||||
parentContainer.props.children[childIndex] = { ...updatedComponent };
|
||||
// Update the container to trigger reactivity
|
||||
formStore.updateComponent(parentContainer);
|
||||
|
||||
console.log('Updated nested component:', updatedComponent);
|
||||
}
|
||||
@ -1658,6 +1784,65 @@ const getButtonSizeClass = (size) => {
|
||||
border-color: #93c5fd;
|
||||
}
|
||||
|
||||
.repeating-group-container {
|
||||
transition: all 0.2s ease-in-out;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.repeating-group-container:hover {
|
||||
border-color: #93c5fd;
|
||||
background-color: #f0f9ff;
|
||||
}
|
||||
|
||||
/* Nested Component Styles */
|
||||
.nested-component {
|
||||
transition: all 0.2s ease-in-out;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.nested-component:hover {
|
||||
border-color: #93c5fd;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.nested-component-actions {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.nested-component:hover .nested-component-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.draggable-children-container {
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
/* Ghost class for drag preview */
|
||||
.ghost {
|
||||
opacity: 0.5;
|
||||
background: #c1d5db;
|
||||
}
|
||||
|
||||
/* Form component styles for nested components */
|
||||
.form-component {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.form-component:hover {
|
||||
border-color: #93c5fd;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.component-actions {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.form-component:hover .component-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Dynamic List Component */
|
||||
.dynamic-list-container {
|
||||
width: 100%;
|
||||
@ -1773,6 +1958,19 @@ const getButtonSizeClass = (size) => {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Preview mode grid styles */
|
||||
.repeating-groups .grid,
|
||||
.section-fields .grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.repeating-groups .form-component,
|
||||
.section-fields .form-component {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.component-actions:hover {
|
||||
background-color: white;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
@ -508,11 +508,17 @@ const availableComponents = [
|
||||
maxItems: 10,
|
||||
buttonText: 'Add Person',
|
||||
removeText: 'Remove',
|
||||
fields: [
|
||||
{ type: 'text', name: 'name', label: 'Name', placeholder: 'Enter name' },
|
||||
{ type: 'number', name: 'age', label: 'Age', placeholder: 'Enter age' },
|
||||
{ type: 'email', name: 'email', label: 'Email', placeholder: 'Enter email' }
|
||||
]
|
||||
width: '100%',
|
||||
gridColumn: 'span 12',
|
||||
showPlaceholder: true, // Whether to show the placeholder in builder mode
|
||||
children: [], // Array to hold nested components (draggable fields)
|
||||
// Conditional Logic Properties
|
||||
conditionalLogic: {
|
||||
enabled: false,
|
||||
conditions: [],
|
||||
action: 'show',
|
||||
operator: 'and'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1337,117 +1337,32 @@ if (name && email) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Group Fields Management -->
|
||||
<div class="space-y-4">
|
||||
<h5 class="text-sm font-medium text-gray-700 border-b pb-2">Group Fields</h5>
|
||||
|
||||
<div class="border rounded-md p-3 bg-gray-50 space-y-3">
|
||||
<div v-for="(field, index) in (configModel.fields || [])" :key="index" class="border p-3 rounded bg-white">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<h4 class="font-medium text-sm text-gray-800">Field {{ index + 1 }}</h4>
|
||||
<button
|
||||
@click="removeGroupField(index)"
|
||||
class="text-red-500 hover:text-red-700 p-1"
|
||||
type="button"
|
||||
title="Remove field"
|
||||
>
|
||||
<Icon name="material-symbols:delete-outline" class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3 mb-3">
|
||||
<FormKit
|
||||
type="select"
|
||||
label="Field Type"
|
||||
:options="[
|
||||
{ label: 'Text', value: 'text' },
|
||||
{ label: 'Number', value: 'number' },
|
||||
{ label: 'Email', value: 'email' },
|
||||
{ label: 'Textarea', value: 'textarea' },
|
||||
{ label: 'Select', value: 'select' },
|
||||
{ label: 'Date', value: 'date' },
|
||||
{ label: 'Time', value: 'time' },
|
||||
{ label: 'Checkbox', value: 'checkbox' },
|
||||
{ label: 'Radio', value: 'radio' }
|
||||
]"
|
||||
v-model="field.type"
|
||||
:classes="{ outer: 'field-wrapper' }"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Field Name"
|
||||
v-model="field.name"
|
||||
:classes="{ outer: 'field-wrapper' }"
|
||||
placeholder="e.g., name, age, email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Field Label"
|
||||
v-model="field.label"
|
||||
:classes="{ outer: 'field-wrapper' }"
|
||||
placeholder="e.g., Full Name, Age, Email"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Placeholder"
|
||||
v-model="field.placeholder"
|
||||
:classes="{ outer: 'field-wrapper' }"
|
||||
placeholder="e.g., Enter your name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Options for select/radio/checkbox fields -->
|
||||
<div v-if="['select', 'radio', 'checkbox'].includes(field.type)" class="mt-3">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700">Options</label>
|
||||
<div class="border rounded-md p-2 bg-gray-50 space-y-2">
|
||||
<div v-for="(option, optionIndex) in (field.options || [])" :key="optionIndex" class="flex items-center space-x-2">
|
||||
<input
|
||||
type="text"
|
||||
v-model="field.options[optionIndex].label"
|
||||
class="flex-1 border border-gray-300 rounded px-2 py-1 text-sm"
|
||||
placeholder="Option label"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
v-model="field.options[optionIndex].value"
|
||||
class="flex-1 border border-gray-300 rounded px-2 py-1 text-sm"
|
||||
placeholder="Option value"
|
||||
/>
|
||||
<button
|
||||
@click="removeFieldOption(field, optionIndex)"
|
||||
class="text-red-500 hover:text-red-700 p-1"
|
||||
type="button"
|
||||
>
|
||||
<Icon name="material-symbols:delete-outline" class="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
@click="addFieldOption(field)"
|
||||
class="text-sm text-blue-600 hover:text-blue-800 flex items-center"
|
||||
type="button"
|
||||
>
|
||||
<Icon name="material-symbols:add-circle-outline" class="w-3 h-3 mr-1" />
|
||||
Add Option
|
||||
</button>
|
||||
</div>
|
||||
<!-- Container Information -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-md p-4">
|
||||
<div class="flex items-start">
|
||||
<Icon name="material-symbols:info" class="w-5 h-5 text-blue-600 mr-2 mt-0.5" />
|
||||
<div>
|
||||
<h4 class="font-medium text-sm text-blue-800 mb-1">Repeating Group Container</h4>
|
||||
<p class="text-xs text-blue-700">
|
||||
This is a container component. Drag and drop fields into it in the form builder to create the repeating group structure.
|
||||
Each field you add will be repeated for every group item.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="addGroupField"
|
||||
class="w-full text-sm text-blue-600 hover:text-blue-800 flex items-center justify-center py-2 border border-dashed border-blue-300 rounded-md hover:bg-blue-50"
|
||||
type="button"
|
||||
>
|
||||
<Icon name="material-symbols:add-circle-outline" class="w-4 h-4 mr-1" />
|
||||
Add Field to Group
|
||||
</button>
|
||||
<!-- Children Count -->
|
||||
<div class="border rounded-md p-3 bg-gray-50">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h5 class="text-sm font-medium text-gray-700">Group Fields</h5>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
{{ configModel.children?.length || 0 }} field(s) in this repeating group
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
<Icon name="material-symbols:drag-indicator" class="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -59,11 +59,8 @@ function handleChange(event) {
|
||||
}
|
||||
|
||||
.switch-input:disabled + .switch-label .switch-track {
|
||||
@apply opacity-50 cursor-not-allowed;
|
||||
}
|
||||
|
||||
.switch-input:disabled + .switch-label {
|
||||
@apply cursor-not-allowed;
|
||||
@apply opacity-50;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.switch-thumb {
|
||||
@ -98,20 +95,9 @@ function handleChange(event) {
|
||||
|
||||
/* Readonly styles for Switch */
|
||||
.switch-input:disabled + .switch-label .switch-track {
|
||||
@apply opacity-50 cursor-not-allowed;
|
||||
@apply opacity-50;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.switch-input:disabled + .switch-label {
|
||||
@apply cursor-not-allowed;
|
||||
}
|
||||
|
||||
/* Additional readonly styling */
|
||||
.switch-label.cursor-not-allowed {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.switch-label.cursor-not-allowed .switch-track {
|
||||
opacity: 0.8;
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
</style>
|
@ -2334,17 +2334,17 @@ const handleUpdateComponent = (updatedComponent) => {
|
||||
// Check if this is a nested component inside a section
|
||||
let foundInSection = false;
|
||||
|
||||
// Look for the component in section children
|
||||
// Look for the component in section or repeating-group children
|
||||
formStore.formComponents.forEach(component => {
|
||||
if (component.type === 'form-section' && component.props.children) {
|
||||
if ((component.type === 'form-section' || component.type === 'repeating-group') && component.props.children) {
|
||||
const nestedIndex = component.props.children.findIndex(child => child.id === updatedComponent.id);
|
||||
if (nestedIndex !== -1) {
|
||||
// Update the nested component
|
||||
component.props.children[nestedIndex] = updatedComponent;
|
||||
// Update the entire section to trigger reactivity
|
||||
// Update the entire container to trigger reactivity
|
||||
formStore.updateComponent(component);
|
||||
foundInSection = true;
|
||||
console.log('Updated nested component in section:', updatedComponent);
|
||||
console.log('Updated nested component in container:', updatedComponent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -2366,14 +2366,14 @@ const handleDeleteComponent = (id) => {
|
||||
// Check if this is a nested component inside a section
|
||||
let foundInSection = false;
|
||||
|
||||
// Look for the component in section children
|
||||
// Look for the component in section or repeating-group children
|
||||
formStore.formComponents.forEach(component => {
|
||||
if (component.type === 'form-section' && component.props.children) {
|
||||
if ((component.type === 'form-section' || component.type === 'repeating-group') && component.props.children) {
|
||||
const nestedIndex = component.props.children.findIndex(child => child.id === id);
|
||||
if (nestedIndex !== -1) {
|
||||
// Remove the nested component
|
||||
const deletedComponent = component.props.children.splice(nestedIndex, 1)[0];
|
||||
// Update the entire section to trigger reactivity
|
||||
// Update the entire container to trigger reactivity
|
||||
formStore.updateComponent(component);
|
||||
foundInSection = true;
|
||||
|
||||
@ -2383,7 +2383,7 @@ const handleDeleteComponent = (id) => {
|
||||
formStore.selectedComponentId = null;
|
||||
}
|
||||
|
||||
console.log('Deleted nested component from section:', deletedComponent);
|
||||
console.log('Deleted nested component from container:', deletedComponent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user