From f86fe87fc5da668fe3b5c38650e2837b6435ee4f Mon Sep 17 00:00:00 2001 From: Afiq Date: Thu, 7 Aug 2025 10:05:53 +0800 Subject: [PATCH] Enhance Form Builder Components with Readonly State Support and New Modal Functionality - Updated ComponentPreview.vue to include support for the 'searchSelect' and 'switch' components in readonly states, ensuring consistent behavior across form fields. - Modified FormBuilderComponents.vue to set default readonly properties for various components, enhancing usability and preventing unintended edits. - Enhanced FormBuilderFieldSettingsModal.vue to reflect the updated readonly logic for component types, improving user awareness of field capabilities. - Introduced a new modal in manage.vue for copying workflow links, allowing users to select between direct and iframe link types with customizable options. - Improved link generation logic to support iframe parameters, enhancing the flexibility of sharing workflows. - Updated styles in SearchSelect.vue and Switch.vue to visually indicate readonly states, ensuring a consistent user experience across components. --- components/ComponentPreview.vue | 23 +- components/FormBuilderComponents.vue | 25 +- components/FormBuilderFieldSettingsModal.vue | 2 +- components/formkit/SearchSelect.vue | 28 +- components/formkit/Switch.vue | 27 +- pages/process-builder/manage.vue | 284 ++++++++++++++++++- 6 files changed, 360 insertions(+), 29 deletions(-) diff --git a/components/ComponentPreview.vue b/components/ComponentPreview.vue index 60a8293..10e220a 100644 --- a/components/ComponentPreview.vue +++ b/components/ComponentPreview.vue @@ -27,7 +27,7 @@ :label="component.props.label" :help="component.props.help" :placeholder="component.props.placeholder" :validation="component.props.validation" :validation-visibility="isPreview ? 'live' : 'blur'" :readonly="component.props.readonly || !isPreview" - :disabled="!isPreview || (component.props.readonly && ['select', 'checkbox', 'radio'].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" :accept="component.props.accept || undefined" :max="component.props.max || undefined" :mask="component.props.mask || undefined" :digits="component.props.digits || undefined" @@ -36,11 +36,12 @@ :classes="component.type === 'checkbox' ? { wrapper: 'mb-1', options: 'space-y-0.5' - } : {}" :class="{ + } : { }" :class="{ 'canvas-component': isPreview, - 'readonly-select': component.props.readonly && component.type === 'select', + 'readonly-select': component.props.readonly && (component.type === 'select' || component.type === 'searchSelect'), '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' }" /> @@ -1590,29 +1591,33 @@ const getButtonSizeClass = (size) => { pointer-events: none; } -/* Readonly styles for select, checkbox, and radio components */ +/* Readonly styles for select, checkbox, radio, and switch components */ :deep(.readonly-select), :deep(.readonly-checkbox), -:deep(.readonly-radio) { +:deep(.readonly-radio), +:deep(.readonly-switch) { cursor: default !important; } :deep(.readonly-select select), :deep(.readonly-select .formkit-inner), :deep(.readonly-checkbox input[type="checkbox"]), -:deep(.readonly-radio input[type="radio"]) { +:deep(.readonly-radio input[type="radio"]), +:deep(.readonly-switch input[type="checkbox"]) { pointer-events: none !important; opacity: 0.8 !important; background-color: #f3f4f6 !important; } :deep(.readonly-checkbox) .formkit-options, -:deep(.readonly-radio) .formkit-options { +:deep(.readonly-radio) .formkit-options, +:deep(.readonly-switch) .formkit-wrapper { pointer-events: none !important; } :deep(.readonly-checkbox) .formkit-wrapper, -:deep(.readonly-radio) .formkit-wrapper { +:deep(.readonly-radio) .formkit-wrapper, +:deep(.readonly-switch) .formkit-wrapper { cursor: not-allowed !important; } diff --git a/components/FormBuilderComponents.vue b/components/FormBuilderComponents.vue index 52b4d75..f19061f 100644 --- a/components/FormBuilderComponents.vue +++ b/components/FormBuilderComponents.vue @@ -136,6 +136,7 @@ const availableComponents = [ placeholder: 'Enter text...', help: '', validation: '', + readonly: false, // Conditional Logic Properties conditionalLogic: { enabled: false, @@ -156,6 +157,7 @@ const availableComponents = [ placeholder: 'Enter text...', help: '', validation: '', + readonly: false, // Conditional Logic Properties conditionalLogic: { enabled: false, @@ -176,6 +178,7 @@ const availableComponents = [ placeholder: '0', help: '', validation: '', + readonly: false, // Conditional Logic Properties conditionalLogic: { enabled: false, @@ -196,6 +199,7 @@ const availableComponents = [ placeholder: 'email@example.com', help: '', validation: 'email', + readonly: false, // Conditional Logic Properties conditionalLogic: { enabled: false, @@ -216,6 +220,7 @@ const availableComponents = [ placeholder: 'Enter password...', help: '', validation: '', + readonly: false, // Conditional Logic Properties conditionalLogic: { enabled: false, @@ -235,7 +240,8 @@ const availableComponents = [ type: 'url', placeholder: 'https://example.com', help: '', - validation: 'url' + validation: 'url', + readonly: false } }, { @@ -248,7 +254,8 @@ const availableComponents = [ type: 'tel', placeholder: '+1 (555) 123-4567', help: '', - validation: '' + validation: '', + readonly: false } }, { @@ -262,7 +269,8 @@ const availableComponents = [ placeholder: 'Enter value...', help: 'Input will be formatted according to the mask', mask: '###-###-####', - validation: '' + validation: '', + readonly: false } }, { @@ -295,6 +303,7 @@ const availableComponents = [ { label: 'Option 3', value: 'option_3' } ], validation: '', + readonly: false, // Conditional Logic Properties conditionalLogic: { enabled: false, @@ -320,6 +329,7 @@ const availableComponents = [ { label: 'Option 3', value: 'option_3' } ], validation: '', + readonly: false, // Conditional Logic Properties conditionalLogic: { enabled: false, @@ -345,7 +355,8 @@ const availableComponents = [ { label: 'Option 2', value: 'option_2' }, { label: 'Option 3', value: 'option_3' } ], - validation: '' + validation: '', + readonly: false } }, { @@ -362,7 +373,8 @@ const availableComponents = [ { label: 'Option 2', value: 'option_2' }, { label: 'Option 3', value: 'option_3' } ], - validation: '' + validation: '', + readonly: false } }, { @@ -377,7 +389,8 @@ const availableComponents = [ name: 'switch_field', help: 'Toggle this option on or off', value: false, - validation: '' + validation: '', + readonly: false } }, diff --git a/components/FormBuilderFieldSettingsModal.vue b/components/FormBuilderFieldSettingsModal.vue index 763c193..5bbc81d 100644 --- a/components/FormBuilderFieldSettingsModal.vue +++ b/components/FormBuilderFieldSettingsModal.vue @@ -2223,7 +2223,7 @@ const showField = (fieldName) => { rows: ['textarea'], options: ['select', 'searchSelect', 'checkbox', 'radio'], conditionalLogic: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group'], - readonly: ['text', 'number', 'email', 'textarea', 'mask', 'url', 'tel'] + readonly: ['text', 'number', 'email', 'password', 'textarea', 'mask', 'url', 'tel', 'select', 'searchSelect', 'checkbox', 'radio', 'switch'] } return fieldConfig[fieldName]?.includes(props.component.type) || false diff --git a/components/formkit/SearchSelect.vue b/components/formkit/SearchSelect.vue index bfe3b3e..b080312 100644 --- a/components/formkit/SearchSelect.vue +++ b/components/formkit/SearchSelect.vue @@ -5,14 +5,14 @@ @input="handleChange" :options="context.options || []" :placeholder="context.placeholder || 'Search and select an option'" - :disabled="context.disabled" - :searchable="true" + :disabled="context.disabled || context.readonly" + :searchable="!context.readonly" :clearable="false" label="label" :reduce="option => option.value" :class="[ 'vue-select-wrapper', - { 'vue-select-disabled': context.disabled } + { 'vue-select-disabled': context.disabled || context.readonly } ]" /> @@ -21,7 +21,7 @@ :value="_value" :name="context.node.name" :id="context.id" - :disabled="context.disabled" + :disabled="context.disabled || context.readonly" :required="context.attrs.required" class="hidden-select" tabindex="-1" @@ -60,6 +60,10 @@ const _value = computed({ // Handle value changes from vue3-select-component const handleChange = (value) => { + // Don't allow changes if readonly + if (props.context.readonly) { + return + } props.context.node.input(value) } @@ -81,4 +85,20 @@ const handleChange = (value) => { white-space: nowrap; border-width: 0; } + +/* Readonly styles for SearchSelect */ +.vue-select-disabled { + opacity: 0.8; + background-color: #f3f4f6; + cursor: not-allowed; +} + +.vue-select-disabled .vs__dropdown-toggle { + background-color: #f3f4f6 !important; + cursor: not-allowed !important; +} + +.vue-select-disabled .vs__actions { + pointer-events: none !important; +} \ No newline at end of file diff --git a/components/formkit/Switch.vue b/components/formkit/Switch.vue index 04d32c3..5dae240 100644 --- a/components/formkit/Switch.vue +++ b/components/formkit/Switch.vue @@ -4,6 +4,10 @@ const props = defineProps({ }); function handleChange(event) { + // Don't allow changes if readonly + if (props.context.readonly) { + return; + } props.context.node.input(event.target.checked); } @@ -16,11 +20,11 @@ function handleChange(event) { :name="context.name" type="checkbox" :checked="context.value" - :disabled="context.disabled" + :disabled="context.disabled || context.readonly" @change="handleChange" class="switch-input" /> -