PluginProbe
The Innovative Form Builder – IvyForms / 0.8.2
The Innovative Form Builder – IvyForms v0.8.2
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 / admin / form-builder / fields / time / TimeField.vue

TimeField.vue in The Innovative Form Builder – IvyForms 0.8.2, at frontend/src/views/admin/form-builder/fields/time/TimeField.vue

342 lines 10.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div class="ivyforms-field-time" :class="{ 'ivyforms-field-time--readonly': readOnly }">
3 <!-- Time Picker or Input types (existing behavior) -->
4 <template v-if="!isDropdownType">
5 <!-- Text input mode -->
6 <template v-if="timeFieldType === 'input'">
7 <IvyFormItem
8 :label="hideLabel ? '' : label"
9 :required="required"
10 :label-position="labelPosition"
11 >
12 <div class="ivyforms-flex ivyforms-gap-12 ivyforms-width-100">
13 <div class="ivyforms-flex ivyforms-flex-1 ivyforms-flex-direction-column">
14 <IvyTextInput
15 :id="'time_' + index"
16 :aria-label="label"
17 readonly
18 :placeholder="placeholder"
19 :model-value="inputDisplayValue"
20 type="text"
21 />
22 </div>
23 <div
24 v-if="field.timeFormat === 'ampm'"
25 class="ivyforms-flex ivyforms-flex-1 ivyforms-flex-direction-column"
26 >
27 <IvySelectInput
28 :model-value="amPmValue"
29 :aria-label="getLabel('am_pm')"
30 readonly
31 :placeholder="getLabel('am_pm')"
32 @mousedown.prevent
33 @click.prevent
34 />
35 </div>
36 </div>
37 <div
38 v-if="description"
39 class="ivyforms-field-time__description regular-14 ivyforms-width-100"
40 >
41 {{ description }}
42 </div>
43 </IvyFormItem>
44 </template>
45 <!-- Time picker mode -->
46 <template v-else>
47 <IvyFormItem
48 :label="hideLabel ? '' : label"
49 :required="required"
50 :label-position="labelPosition"
51 >
52 <IvyTimePicker
53 :id="'time_' + index"
54 :aria-label="label"
55 :editable="false"
56 :format="timeFormat === 'ampm' ? 'hh:mm A' : 'HH:mm'"
57 :model-value="modelValue24h"
58 :clearable="false"
59 :time-format="timeFormat"
60 :placeholder="placeholder"
61 @click.stop.prevent
62 />
63 <div
64 v-if="description"
65 class="ivyforms-field-time__description regular-14 ivyforms-width-100"
66 >
67 {{ description }}
68 </div>
69 </IvyFormItem>
70 </template>
71 </template>
72
73 <!-- Dropdown type (use select components) -->
74 <template v-else>
75 <IvyFormItem
76 :label="hideLabel ? '' : label"
77 :required="required"
78 :label-position="labelPosition"
79 >
80 <div class="ivyforms-flex ivyforms-width-100 ivyforms-gap-12">
81 <div class="ivyforms-flex ivyforms-flex-1 ivyforms-flex-direction-column">
82 <div class="medium-14 ivyforms-mb-6 ivyforms-field-time__sublabel">
83 {{ getLabel('hours') }}
84 </div>
85 <IvySelectInput
86 :model-value="dropdownHoursValue"
87 placeholder="--"
88 readonly
89 @mousedown.prevent
90 @click.prevent
91 />
92 </div>
93 <div class="ivyforms-flex ivyforms-flex-1 ivyforms-flex-direction-column">
94 <div class="medium-14 ivyforms-mb-6 ivyforms-field-time__sublabel">
95 {{ getLabel('minutes') }}
96 </div>
97 <IvySelectInput
98 :model-value="dropdownMinutesValue"
99 placeholder="--"
100 readonly
101 @mousedown.prevent
102 @click.prevent
103 />
104 </div>
105 <div
106 v-if="field.timeFormat === 'ampm'"
107 class="ivyforms-flex ivyforms-flex-1 ivyforms-flex-direction-column ivyforms-justify-content-end"
108 >
109 <IvySelectInput
110 :model-value="dropdownAmPmValue"
111 :aria-label="getLabel('am_pm')"
112 :placeholder="getLabel('am_pm')"
113 readonly
114 @mousedown.prevent
115 @click.prevent
116 />
117 </div>
118 </div>
119 <div
120 v-if="description"
121 class="ivyforms-field-time__description regular-14 ivyforms-width-100"
122 >
123 {{ description }}
124 </div>
125 </IvyFormItem>
126 </template>
127 </div>
128 </template>
129
130 <script setup lang="ts">
131 import { computed, ref, watch } from 'vue'
132 import { useFormBuilder } from '@/stores/useFormBuilder'
133 import { useLabels } from '@/composables/useLabels'
134 import { useTimeField } from '@/composables/useTimeField'
135 import IvySelectInput from '@/views/_components/input/IvySelectInput.vue'
136 import IvyTimePicker from '@/views/_components/timepicker/IvyTimePicker.vue'
137 import IvyTextInput from '@/views/_components/input/IvyTextInput.vue'
138 import IvyFormItem from '@/views/_components/form/IvyFormItem.vue'
139
140 const { getLabel } = useLabels()
141 const props = defineProps<{ fieldIndex: number }>()
142 const formBuilderStore = useFormBuilder()
143 const field = computed(() => formBuilderStore.fields.find((f) => f.fieldIndex === props.fieldIndex))
144 const label = computed(() => field.value?.label || '')
145 const placeholder = computed(() => field.value?.placeholder || '')
146 const required = computed(() => field.value?.required || false)
147 const hideLabel = computed(() => field.value?.hideLabel || false)
148 const readOnly = computed(() => field.value?.readOnly || false)
149 const description = computed(() => field.value?.description || '')
150 const index = computed(() => field.value?.fieldIndex ?? props.fieldIndex)
151 const modelValue = computed(() => field.value?.defaultValue || '')
152 const timeFieldType = computed(() => field.value?.timeFieldType || 'time-picker')
153 const timeFormat = computed(() => field.value?.timeFormat)
154 const labelPosition = computed(() =>
155 field.value?.labelPosition === 'default' ? 'top' : field.value?.labelPosition || 'top',
156 )
157
158 const isDropdownType = computed(() => timeFieldType.value === 'dropdown')
159
160 const { parseTimeString, to24hFormat } = useTimeField()
161
162 const modelValue24h = computed(() => to24hFormat(modelValue.value))
163
164 // Parse default value to extract hours, minutes, and AM/PM
165 const parsedTime = computed(() => parseTimeString(modelValue.value))
166
167 const inputValue = computed(() => {
168 if (!modelValue.value) return ''
169 if (/^(AM|PM)$/i.test(modelValue.value.trim())) return ''
170 const match = modelValue.value.match(/^(.*?)(?:\s*(AM|PM))?$/)
171 return match ? match[1].trim() : modelValue.value
172 })
173
174 // For input mode - display value in 12h format when AM/PM is enabled
175 const inputDisplayValue = computed(() => {
176 return inputValue.value
177 })
178
179 const previousAmPm = ref('')
180
181 const amPmValue = computed(() => {
182 if (!modelValue.value) {
183 return ''
184 }
185 const match = modelValue.value.match(/\b(AM|PM)\b/i)
186 if (match && match[1]) {
187 return match[1].toUpperCase()
188 }
189 return ''
190 })
191
192 watch(amPmValue, (newVal) => {
193 previousAmPm.value = newVal
194 })
195
196 // For dropdown mode - extract hours based on format, including only-hours case
197 const dropdownHoursValue = computed(() => {
198 if (!modelValue.value) return ''
199 const hoursMatch = modelValue.value.match(/^(\d{2})$/)
200 if (hoursMatch) {
201 return hoursMatch[1]
202 }
203 const timeMatch = modelValue.value.match(/^(\d{2}):/)
204 if (timeMatch) {
205 return timeMatch[1]
206 }
207 return ''
208 })
209
210 const dropdownMinutesValue = computed(() => {
211 if (!modelValue.value) return ''
212 if (/^:([0-9]{2})$/.test(modelValue.value)) {
213 const match = modelValue.value.match(/^:([0-9]{2})$/)
214 return match ? match[1] : ''
215 }
216 return parsedTime.value.minutes
217 })
218
219 const dropdownAmPmValue = computed(() => {
220 if (!modelValue.value) return ''
221 if (/^(AM|PM)$/.test(modelValue.value)) {
222 return modelValue.value
223 }
224 if (parsedTime.value.amPm) {
225 return parsedTime.value.amPm
226 }
227 return ''
228 })
229 </script>
230
231 <style lang="scss" scoped>
232 .ivyforms-field-time {
233 cursor: default;
234
235 &--readonly {
236 opacity: 0.6;
237 }
238
239 &__sublabel {
240 color: var(--map-base-text-0);
241 }
242
243 &__description {
244 color: var(--map-base-text-0);
245 display: block;
246 white-space: normal;
247 overflow-wrap: anywhere;
248 word-break: break-word;
249 margin-top: 6px;
250 }
251
252 .ivyforms-form-item {
253 cursor: default;
254 margin-bottom: 0;
255 :deep(.ivyforms-form-item__label) {
256 display: flex;
257 align-items: center;
258 color: var(--map-base-text-0);
259 font-size: 14px;
260 font-style: normal;
261 font-weight: 500;
262 line-height: 20px;
263 cursor: default !important;
264 }
265 :deep(.el-form-item__label) {
266 cursor: default;
267 }
268 .ivyforms-input.el-input {
269 :deep(.el-input__wrapper),
270 :deep(.el-input__wrapper:hover),
271 :deep(.el-input__wrapper.is-focus) {
272 border-radius: var(--Radius-radius-md, 8px);
273 border: 1px solid var(--map-base-dusk-stroke--2);
274 background: transparent;
275 box-shadow: none;
276 padding: 0 12px;
277 transition: none;
278 cursor: default;
279 }
280 :deep(input) {
281 border: none;
282 background: transparent;
283 cursor: default;
284 box-shadow: none;
285 &:focus {
286 outline: none;
287 border: 1px solid transparent;
288 background: none;
289 box-shadow: none;
290 }
291 }
292 }
293 :deep(.ivyforms-time-picker) {
294 pointer-events: none;
295 }
296 :deep(.el-date-editor.el-input) {
297 width: 100%;
298 pointer-events: none;
299 .el-input__prefix {
300 display: none !important;
301 }
302 .el-input__wrapper {
303 border-radius: var(--Radius-radius-md, 8px);
304 border: 1px solid var(--map-base-dusk-stroke--2);
305 background: transparent;
306 box-shadow: none;
307 padding: 0 12px;
308 transition: none;
309 cursor: default;
310 height: 40px;
311 }
312 input.el-input__inner {
313 border: none;
314 background: transparent;
315 cursor: default;
316 box-shadow: none;
317 padding: 0;
318 font-size: 16px;
319 line-height: 22px;
320 pointer-events: none;
321 &:focus {
322 outline: none;
323 border: 1px solid transparent;
324 background: none;
325 box-shadow: none;
326 }
327 }
328 }
329 }
330
331 :deep(.ivyforms-form-item-select .el-select__wrapper),
332 :deep(.ivyforms-form-item-select .el-select__wrapper:hover),
333 :deep(.ivyforms-form-item-select .el-select__wrapper.is-hovering),
334 :deep(.ivyforms-form-item-select .el-select__wrapper:focus),
335 :deep(.ivyforms-form-item-select .el-select__wrapper.is-focused),
336 :deep(.ivyforms-form-item-select .el-select__wrapper:active),
337 :deep(.ivyforms-form-item-select .el-select__wrapper.is-active) {
338 border: 1px solid var(--map-base-dusk-stroke--2);
339 }
340 }
341 </style>
342