PluginProbe
The Innovative Form Builder – IvyForms / 0.4
The Innovative Form Builder – IvyForms v0.4
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / views / public / fields / NameFormField.vue

NameFormField.vue in The Innovative Form Builder – IvyForms 0.4, at frontend/src/views/public/fields/NameFormField.vue

198 lines 6.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div
3 :class="[
4 'ivyforms-field',
5 'ivyforms-field__name',
6 'ivyforms-field__name_' + field.id,
7 field.cssClasses,
8 {
9 'main-label-visible': !field.hideLabel,
10 'ivyforms-name-field--is-readonly': !!field.readOnly,
11 },
12 ]"
13 >
14 <div
15 v-if="!field.hideLabel"
16 class="ivyforms-name-field__label ivyforms-gap-4 ivyforms-align-items-center ivyforms-mb-8 regular-16"
17 >
18 {{ field.label }}
19 </div>
20 <div class="ivyforms-name-field__layout ivyforms-width-100 ivyforms-gap-16" :style="gridStyle">
21 <div
22 v-for="(fieldData, idx) in nameFields"
23 :key="fieldData.fieldID"
24 class="ivyforms-name-field__layout__field-wrapper"
25 >
26 <IvyFormItem
27 :required="isSubRequired(idx)"
28 :prop="`${field.type}_${field.fieldIndex}.${getTypeByIndex(idx)}`"
29 :error="getSubfieldError(getTypeByIndex(idx))"
30 :show-info="!!getSubDescription(idx)"
31 :info-text="getSubDescription(idx) || ''"
32 :show-info-icon="false"
33 >
34 <template #label>
35 <div
36 class="ivyforms-name-field__layout__field-wrapper__sublabel ivyforms-align-items-center ivyforms-gap-4 medium-14"
37 >
38 <span v-if="!fieldData.hide">{{ fieldData.label }}</span>
39 <span
40 v-if="isSubRequired(idx)"
41 class="ivyforms-name-field__layout__field-wrapper__sublabel__asterisk"
42 >*</span
43 >
44 </div>
45 </template>
46 <IvyTextInput
47 :id="fieldData.fieldID"
48 :model-value="getFieldValue(getTypeByIndex(idx))"
49 class="ivyforms-name-field__name-input"
50 type="text"
51 :placeholder="fieldData.placeholder"
52 :disabled="isDisabled"
53 :required="isSubRequired(idx)"
54 :aria-required="isSubRequired(idx) ? 'true' : 'false'"
55 @input="updateFieldValue(getTypeByIndex(idx), $event)"
56 />
57 </IvyFormItem>
58 </div>
59 </div>
60 </div>
61 </template>
62
63 <script setup lang="ts">
64 import { computed, inject } from 'vue'
65 import type { Field, NameFieldValue } from '@/types/field'
66
67 interface FieldProps {
68 modelValue: NameFieldValue
69 field: Field
70 disabled?: boolean
71 error?: string
72 fieldErrors?: Record<string, string>
73 }
74
75 const props = withDefaults(defineProps<FieldProps>(), {
76 modelValue: () => ({}),
77 disabled: false,
78 error: '',
79 fieldErrors: () => ({}),
80 })
81 const emit = defineEmits(['update:modelValue'])
82
83 // Inject error clearer from parent form
84 const clearFieldError = inject<(fieldKey: string) => void>('clearFieldError', () => {})
85
86 // Disable input if component disabled OR field.readOnly is set
87 const isDisabled = computed(() => !!props.disabled || !!props.field.readOnly)
88
89 // Get specific error for a subfield
90 const getSubfieldError = (fieldType: string): string => {
91 const subKey = `${props.field.type}_${props.field.fieldIndex}.${fieldType}`
92 return props.fieldErrors?.[subKey] || ''
93 }
94
95 const nameFieldTypes = computed(() => props.field.nameFieldTypes || ['nameField1', 'nameField2'])
96
97 const nameFields = computed(() => {
98 return nameFieldTypes.value.map((fieldType) => {
99 // Raw label without i18n fallback, so cleared sublabels are treated as hidden
100 const rawLabel = ((props.field as Record<string, string>)[`${fieldType}Label`] ?? '') as string
101 const isCleared = rawLabel.trim() === ''
102 const explicitHide = (props.field as Record<string, boolean>)[`${fieldType}HideLabel`] || false
103 const hide = explicitHide || isCleared
104 return {
105 label: rawLabel, // no fallback; empty means hidden via `hide`
106 placeholder: (props.field as Record<string, string>)[`${fieldType}Placeholder`] || '',
107 fieldID: `ivyforms-field__name-${fieldType}_${(props.field.formId || '').toString()}_${(props.field.fieldIndex || '').toString()}`,
108 hide,
109 }
110 })
111 })
112
113 const getTypeByIndex = (idx: number): string => nameFieldTypes.value[idx] || ''
114 const isSubRequired = (idx: number): boolean => {
115 const t = getTypeByIndex(idx)
116 return !!(props.field as Record<string, unknown>)[`${t}Required`]
117 }
118
119 // Subfield description helper (per sub)
120 const getSubDescription = (idx: number): string => {
121 const t = getTypeByIndex(idx)
122 const val = (props.field as Record<string, unknown>)[`${t}Description`]
123 return typeof val === 'string' ? val : ''
124 }
125
126 // Single row grid with equal columns for 1–5 subfields
127 const gridStyle = computed(() => {
128 const count = Math.max(1, Math.min(5, nameFields.value.length || 0))
129 return { gridTemplateColumns: `repeat(${count}, minmax(0, 1fr))` }
130 })
131
132 const getFieldValue = (fieldType: string) => props.modelValue?.[fieldType] || ''
133
134 const updateFieldValue = (fieldType: string, value: string) => {
135 const newModelValue: NameFieldValue = { ...props.modelValue, [fieldType]: value }
136 emit('update:modelValue', newModelValue)
137
138 // Clear specific subfield error immediately on input (e.g., after submit validation)
139 const subKey = `${props.field.type}_${props.field.fieldIndex}.${fieldType}`
140 clearFieldError(subKey)
141 }
142 </script>
143
144 <style lang="scss" scoped>
145 .ivyforms-name-field {
146 :deep(.el-form-item__label) {
147 justify-content: flex-start;
148 }
149
150 &--is-readonly {
151 opacity: 0.7;
152 // Prevent hover/focus border changes when read-only
153 :deep(.el-input__wrapper),
154 :deep(.el-input__wrapper:hover),
155 :deep(.el-input__wrapper.is-focus) {
156 border-color: var(--map-base-dusk-stroke--2) !important;
157 box-shadow: none !important;
158 }
159 :deep(input[disabled]]) {
160 cursor: not-allowed;
161 }
162 }
163
164 &__label {
165 color: var(--map-base-text-0);
166 display: inline-flex;
167 }
168
169 &__layout {
170 display: grid;
171
172 &__field-wrapper {
173 min-width: 0;
174
175 &__sublabel {
176 display: inline-flex;
177 color: var(--map-base-text-0);
178 height: 20px;
179 span {
180 white-space: nowrap;
181 }
182
183 &__asterisk {
184 color: var(--map-status-error-fill-0);
185 }
186 }
187 }
188 }
189 }
190
191 /* Always hide default required asterisk for sublabels within Name field; rely on custom one */
192 .ivyforms-field__name :deep(.el-form-item.is-required .ivyforms-form-item__left-label::after),
193 .ivyforms-field__name :deep(.el-form-item.is-required .el-form-item__label::after) {
194 display: none !important;
195 content: '' !important;
196 }
197 </style>
198