Drop Components Here
@@ -534,7 +587,7 @@ const props = defineProps({
}
});
-const emit = defineEmits(['select-nested-component']);
+const emit = defineEmits(['select-nested-component', 'form-data-updated']);
// Get access to the form builder store
const formStore = useFormBuilderStore();
@@ -593,6 +646,37 @@ onMounted(() => {
}
}
}
+
+ // Initialize repeating groups
+ if (props.component.type === 'repeating-group') {
+ const groupName = props.component.props.name;
+ if (groupName) {
+ // Get current groups or initialize empty array
+ const currentGroups = safeGetField(groupName, formStore.previewFormData);
+
+ // If no groups exist and minItems is specified, create initial groups
+ if ((!currentGroups || currentGroups.length === 0) && props.component.props.minItems > 0) {
+ const initialGroups = [];
+ const minItems = props.component.props.minItems || 1;
+
+ for (let i = 0; i < minItems; i++) {
+ const newGroup = {};
+
+ // Add fields from configuration
+ if (props.component.props.fields) {
+ props.component.props.fields.forEach(field => {
+ newGroup[field.name] = '';
+ });
+ }
+
+ initialGroups.push(newGroup);
+ }
+
+ const updatedData = { ...formStore.previewFormData, [groupName]: initialGroups };
+ formStore.updatePreviewFormData(updatedData);
+ }
+ }
+ }
});
// Watch for changes to component props, especially defaultItems
@@ -609,6 +693,39 @@ watch(() => props.component.props.defaultItems, (newDefaultItems, oldDefaultItem
}
}, { deep: true, immediate: true });
+// Watch for changes to repeating group props
+watch(() => props.component.props.minItems, (newMinItems, oldMinItems) => {
+ if (props.component.type === 'repeating-group') {
+ const groupName = props.component.props.name;
+ if (!groupName) return;
+
+ const currentGroups = safeGetField(groupName, formStore.previewFormData);
+ const minItems = newMinItems || 1;
+
+ // If current groups are fewer than minItems, add missing groups
+ if ((!currentGroups || currentGroups.length < minItems) && minItems > 0) {
+ const groupsToAdd = minItems - (currentGroups?.length || 0);
+ const updatedGroups = [...(currentGroups || [])];
+
+ for (let i = 0; i < groupsToAdd; i++) {
+ const newGroup = {};
+
+ // Add fields from configuration
+ if (props.component.props.fields) {
+ props.component.props.fields.forEach(field => {
+ newGroup[field.name] = '';
+ });
+ }
+
+ updatedGroups.push(newGroup);
+ }
+
+ const updatedData = { ...formStore.previewFormData, [groupName]: updatedGroups };
+ formStore.updatePreviewFormData(updatedData);
+ }
+ }
+}, { deep: true, immediate: true });
+
// Repeating group and dynamic list functionality
const addGroupItem = () => {
if (props.isPreview) return;
@@ -635,6 +752,9 @@ const addGroupItem = () => {
// 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) => {
@@ -646,12 +766,22 @@ const removeGroupItem = (index) => {
// 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 = () => {
@@ -1247,6 +1377,106 @@ const saveNestedComponentSettings = (updatedComponent) => {
// Close the modal
closeNestedSettingsModal();
};
+
+// Button link functionality
+const getButtonLink = () => {
+ if (!props.component || props.component.type !== 'button') return null;
+
+ const { linkType, linkUrl, linkProcessId, linkTarget, iframeDebug, iframeHideComplete, iframeTheme, iframeCustomParams } = props.component.props;
+
+ if (linkType === 'url' && linkUrl) {
+ return linkUrl;
+ }
+
+ if (linkType === 'process' && linkProcessId) {
+ // Generate the process workflow URL with iframe parameters
+ const baseUrl = `${window.location.origin}/workflow/${linkProcessId}`;
+ const params = new URLSearchParams();
+
+ // Add debug parameter (false = iframe mode, true = debug mode)
+ if (iframeDebug !== undefined) {
+ params.append('debug', iframeDebug ? 'true' : 'false');
+ }
+
+ // Add hideComplete parameter
+ if (iframeHideComplete) {
+ params.append('hideComplete', 'true');
+ }
+
+ // Add theme parameter
+ if (iframeTheme) {
+ params.append('theme', iframeTheme);
+ }
+
+ // Add custom parameters
+ if (iframeCustomParams) {
+ const customParams = new URLSearchParams(iframeCustomParams);
+ customParams.forEach((value, key) => {
+ params.append(key, value);
+ });
+ }
+
+ const queryString = params.toString();
+ return queryString ? `${baseUrl}?${queryString}` : baseUrl;
+ }
+
+ return null;
+};
+
+// Custom button styling functions
+const getCustomButtonStyles = (props) => {
+ if (!props || props.variant !== 'custom') return {};
+
+ const styles = {
+ backgroundColor: props.customBackgroundColor || '#3b82f6',
+ color: props.customTextColor || '#ffffff',
+ border: 'none',
+ cursor: 'pointer',
+ transition: 'all 0.2s ease-in-out'
+ };
+
+ // Add border if specified
+ if (props.customBorderColor && props.customBorderWidth) {
+ styles.border = `${props.customBorderWidth}px solid ${props.customBorderColor}`;
+ }
+
+ // Add border radius
+ if (props.customBorderRadius) {
+ styles.borderRadius = `${props.customBorderRadius}px`;
+ }
+
+ // Add hover effects
+ const hoverEffect = props.customHoverEffect;
+ if (hoverEffect && hoverEffect !== 'none') {
+ switch (hoverEffect) {
+ case 'darken':
+ styles[':hover'] = { filter: 'brightness(0.9)' };
+ break;
+ case 'lighten':
+ styles[':hover'] = { filter: 'brightness(1.1)' };
+ break;
+ case 'scale':
+ styles[':hover'] = { transform: 'scale(1.05)' };
+ break;
+ case 'glow':
+ styles[':hover'] = {
+ boxShadow: `0 0 10px ${props.customBackgroundColor || '#3b82f6'}`
+ };
+ break;
+ }
+ }
+
+ return styles;
+};
+
+const getButtonSizeClass = (size) => {
+ const sizeClasses = {
+ 'sm': 'px-3 py-1.5 text-sm',
+ 'md': 'px-4 py-2 text-sm',
+ 'lg': 'px-6 py-3 text-base'
+ };
+ return sizeClasses[size] || sizeClasses['md'];
+};
\ No newline at end of file
diff --git a/components/FormBuilderComponents.vue b/components/FormBuilderComponents.vue
index e8805a9..4faca4c 100644
--- a/components/FormBuilderComponents.vue
+++ b/components/FormBuilderComponents.vue
@@ -598,6 +598,10 @@ const availableComponents = [
variant: 'primary', // primary, secondary, success, warning, danger
size: 'md', // sm, md, lg
disabled: false,
+ showLabel: true, // Whether to show the label above the button
+ showButtonText: true, // Whether to show text on the button
+ buttonText: '', // Text to display on button (falls back to label if empty)
+ icon: '', // Optional icon to display on button
onClick: '' // Custom JavaScript code to execute
}
},
@@ -623,6 +627,7 @@ const availableComponents = [
spacing: 'normal', // compact, normal, relaxed
width: '100%',
gridColumn: 'span 12',
+ showPlaceholder: true, // Whether to show the placeholder in builder mode
children: [], // Array to hold nested components
// Conditional Logic Properties
conditionalLogic: {
diff --git a/components/FormBuilderFieldSettingsModal.vue b/components/FormBuilderFieldSettingsModal.vue
index 6399c15..0045192 100644
--- a/components/FormBuilderFieldSettingsModal.vue
+++ b/components/FormBuilderFieldSettingsModal.vue
@@ -218,6 +218,19 @@
rows="2"
/>
+
+
@@ -703,6 +1037,255 @@