PluginProbe
The Innovative Form Builder – IvyForms / 0.8
The Innovative Form Builder – IvyForms v0.8
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 / DateFormField.vue

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

754 lines 24.2 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__date',
6 'ivyforms-field__date_' + field.id,
7 field.cssClasses,
8 {
9 'ivyforms-field__date--is-readonly': !!field.readOnly,
10 },
11 ]"
12 >
13 <!-- Input Mode (manual entry) -->
14 <IvyFormItem
15 v-if="field.dateFieldType === 'input'"
16 :label="field.hideLabel ? '' : field.label"
17 :required="field.required"
18 :error="error"
19 :prop="field.type + '_' + field.fieldIndex"
20 :show-info="!!field.description"
21 :info-text="field.description || ''"
22 :show-info-icon="false"
23 :label-position="field.labelPosition"
24 >
25 <IvyTextInput
26 :id="fieldID"
27 v-model="textInputValue"
28 :aria-label="field.label"
29 class="ivyforms-field__date-input"
30 type="text"
31 :placeholder="displayFormat"
32 :disabled="disabled || field.readOnly"
33 :readonly="field.readOnly"
34 @blur="onTextInputBlur"
35 @update:model-value="onTextInputChange"
36 />
37 </IvyFormItem>
38
39 <!-- Dropdown Mode -->
40 <IvyFormItem
41 v-else-if="field.dateFieldType === 'dropdown'"
42 :label="field.label"
43 :required="field.required"
44 :error="error"
45 :prop="field.type + '_' + field.fieldIndex"
46 :show-info="!!field.description"
47 :info-text="field.description || ''"
48 :show-info-icon="false"
49 :label-position="field.labelPosition"
50 :hidden-label="field.hideLabel"
51 >
52 <div class="ivyforms-flex ivyforms-gap-12 ivyforms-width-100">
53 <div
54 v-for="fieldConfig in dateFieldsConfig"
55 :key="fieldConfig.type"
56 class="ivyforms-flex ivyforms-flex-1 ivyforms-flex-direction-column"
57 >
58 <label
59 v-if="fieldConfig.type === 'day'"
60 :for="`day-select-${field.fieldIndex}`"
61 class="medium-14 ivyforms-mb-6 ivyforms-field__date__sublabel"
62 >
63 {{ getLabel('day') }}
64 </label>
65 <label
66 v-else-if="fieldConfig.type === 'month'"
67 :for="`month-select-${field.fieldIndex}`"
68 class="medium-14 ivyforms-mb-6 ivyforms-field__date__sublabel"
69 >
70 {{ getLabel('month') }}
71 </label>
72 <label
73 v-else-if="fieldConfig.type === 'year'"
74 :for="`year-select-${field.fieldIndex}`"
75 class="medium-14 ivyforms-mb-6 ivyforms-field__date__sublabel"
76 >
77 {{ getLabel('year') }}
78 </label>
79
80 <IvySelectInput
81 v-if="fieldConfig.type === 'day'"
82 :id="`day-select-${field.fieldIndex}`"
83 v-model="selectedDay"
84 class="ivyforms-field__date-day"
85 :error="!!(error && !selectedDay)"
86 placeholder="&#45;&#45;"
87 :disabled="disabled || field.readOnly"
88 :clearable="!field.readOnly"
89 field-type="date"
90 :aria-label="`${fieldConfig.type}-select-${field.fieldIndex}`"
91 @update:model-value="onPartChange"
92 >
93 <IvySelectOption
94 v-for="d in dayOptions"
95 :key="d.value"
96 :label="d.label"
97 :value="d.value"
98 field-type="date"
99 />
100 </IvySelectInput>
101 <IvySelectInput
102 v-else-if="fieldConfig.type === 'month'"
103 :id="`month-select-${field.fieldIndex}`"
104 v-model="selectedMonth"
105 class="ivyforms-field__date-month"
106 :error="!!(error && !selectedMonth)"
107 placeholder="&#45;&#45;"
108 :disabled="disabled || field.readOnly"
109 :clearable="!field.readOnly"
110 field-type="date"
111 :aria-label="`${fieldConfig.type}-select-${field.fieldIndex}`"
112 @update:model-value="onPartChange"
113 >
114 <IvySelectOption
115 v-for="m in monthOptions"
116 :key="m.value"
117 :label="m.label"
118 :value="m.value"
119 field-type="date"
120 />
121 </IvySelectInput>
122 <IvySelectInput
123 v-else-if="fieldConfig.type === 'year'"
124 :id="`year-select-${field.fieldIndex}`"
125 v-model="selectedYear"
126 class="ivyforms-field__date-year"
127 :error="!!(error && !selectedYear)"
128 placeholder="&#45;&#45;"
129 :disabled="disabled || field.readOnly"
130 :clearable="!field.readOnly"
131 field-type="date"
132 :aria-label="`${fieldConfig.type}-select-${field.fieldIndex}`"
133 @update:model-value="onPartChange"
134 >
135 <IvySelectOption
136 v-for="y in yearOptions"
137 :key="y.value"
138 :label="y.label"
139 :value="y.value"
140 field-type="date"
141 />
142 </IvySelectInput>
143 </div>
144 </div>
145 </IvyFormItem>
146
147 <!-- Date Picker Mode -->
148 <IvyFormItem
149 v-else
150 :label="field.hideLabel ? '' : field.label"
151 :required="field.required"
152 :error="error"
153 :prop="field.type + '_' + field.fieldIndex"
154 :show-info="!!field.description"
155 :info-text="field.description || ''"
156 :show-info-icon="false"
157 :label-position="field.labelPosition"
158 >
159 <IvyDatePicker
160 :id="fieldID"
161 v-model="localModelValue"
162 :aria-label="field.label"
163 class="ivyforms-field__date-picker"
164 :format="displayFormat"
165 value-format="YYYY-MM-DD"
166 :placeholder="field.placeholder"
167 :disabled="disabled || field.readOnly"
168 :clearable="true"
169 :disabled-date="disabledDatePickerDates"
170 />
171 </IvyFormItem>
172 </div>
173 </template>
174
175 <script setup lang="ts">
176 import { computed, inject, ref, watch, nextTick, onMounted } from 'vue'
177 import { useDateField } from '@/composables/useDateField'
178 import { useLabels } from '@/composables/useLabels'
179 import type { Field } from '@/types/field'
180 import IvyFormItem from '@/views/_components/form/IvyFormItem.vue'
181 import IvyDatePicker from '@/views/_components/datepicker/IvyDatePicker.vue'
182 import IvySelectInput from '@/views/_components/input/IvySelectInput.vue'
183 import IvySelectOption from '@/views/_components/input/IvySelectOption.vue'
184 import IvyTextInput from '@/views/_components/input/IvyTextInput.vue'
185
186 const { getLabel } = useLabels()
187
188 interface FieldProps {
189 modelValue: string | null | undefined
190 field: Field
191 disabled?: boolean
192 error?: string
193 }
194
195 const props = withDefaults(defineProps<FieldProps>(), {
196 modelValue: '',
197 disabled: false,
198 error: '',
199 })
200
201 const emit = defineEmits(['update:modelValue'])
202 const clearFieldError = inject<(fieldKey: string) => void>('clearFieldError', () => {})
203 const fieldKey = computed(() => props.field.type + '_' + props.field.fieldIndex)
204
205 const {
206 parseDateString,
207 formatDateString,
208 getDaysArray,
209 getMonthsArray,
210 getYearsArray,
211 toISOFormat,
212 fromISOFormat,
213 } = useDateField()
214
215 // Input mode ref - single text input for date
216 const textInputValue = ref<string>('')
217
218 // Dropdown mode refs - strictly typed as strings to prevent numeric values
219 const selectedDay = ref<string>('')
220 const selectedMonth = ref<string>('')
221 const selectedYear = ref<string>('')
222
223 // Create watchers to ensure dropdown values are always strings and never become numbers
224 watch(selectedDay, (newVal) => {
225 if (typeof newVal !== 'string') {
226 selectedDay.value = ''
227 }
228 })
229 watch(selectedMonth, (newVal) => {
230 if (typeof newVal !== 'string') {
231 selectedMonth.value = ''
232 }
233 })
234 watch(selectedYear, (newVal) => {
235 if (typeof newVal !== 'string') {
236 selectedYear.value = ''
237 }
238 })
239
240 // Watch for year changes to clear invalid month/day selections
241 watch(selectedYear, () => {
242 if (selectedYear.value && (minDate.value || maxDate.value)) {
243 // Check if current month is still valid
244 if (selectedMonth.value) {
245 const isMonthValid = monthOptions.value.some((m) => m.value === selectedMonth.value)
246 if (!isMonthValid) {
247 selectedMonth.value = ''
248 selectedDay.value = ''
249 }
250 }
251 }
252 })
253
254 // Watch for month changes to clear invalid day selections
255 watch(selectedMonth, () => {
256 if (selectedMonth.value && selectedYear.value && (minDate.value || maxDate.value)) {
257 // Check if current day is still valid
258 if (selectedDay.value) {
259 const isDayValid = dayOptions.value.some((d) => d.value === selectedDay.value)
260 if (!isDayValid) {
261 selectedDay.value = ''
262 }
263 }
264 }
265 })
266
267 // Flag to prevent watch from overwriting values during internal updates
268 const isInternalUpdate = ref(false)
269
270 // Determine field order and placeholders based on date format
271 const dateFieldsConfig = computed(() => {
272 const format = ((props.field.dateFormat as string) || 'MM/DD/YYYY').toUpperCase()
273 const fields: Array<{ type: 'day' | 'month' | 'year'; order: number; placeholder: string }> = []
274
275 // Find position of each component in the format
276 const dayIndex = format.indexOf('D')
277 const monthIndex = format.indexOf('M')
278 const yearIndex = format.indexOf('Y')
279
280 if (dayIndex !== -1) {
281 fields.push({ type: 'day', order: dayIndex, placeholder: format.includes('DD') ? 'DD' : 'D' })
282 }
283 if (monthIndex !== -1) {
284 fields.push({
285 type: 'month',
286 order: monthIndex,
287 placeholder: format.includes('MM') ? 'MM' : 'M',
288 })
289 }
290 if (yearIndex !== -1) {
291 fields.push({
292 type: 'year',
293 order: yearIndex,
294 placeholder: format.includes('YYYY') ? 'YYYY' : 'YY',
295 })
296 }
297
298 // Sort by position in format string
299 return fields.sort((a, b) => a.order - b.order)
300 })
301
302 const handleInput = (value: string | null | undefined, shouldClearError: boolean = true) => {
303 isInternalUpdate.value = true
304 emit('update:modelValue', value ?? '')
305 if (shouldClearError) {
306 clearFieldError(fieldKey.value)
307 }
308 nextTick(() => {
309 isInternalUpdate.value = false
310 })
311 }
312
313 // Computed for date picker mode - convert to/from ISO string expected by DatePicker
314 const localModelValue = computed({
315 get() {
316 const val = props.modelValue != null ? String(props.modelValue) : ''
317 return toISOFormat(val, (props.field.dateFormat as string) || 'MM/DD/YYYY')
318 },
319 set(dateValue: string) {
320 const formatted = fromISOFormat(
321 dateValue || '',
322 (props.field.dateFormat as string) || 'MM/DD/YYYY',
323 )
324 handleInput(formatted, true)
325 },
326 })
327
328 // Display format as string for props expecting string
329 const displayFormat = computed(() => String((props.field.dateFormat as string) || 'MM/DD/YYYY'))
330
331 // Parse minDateValue and maxDateValue to Date objects for validation
332 const minDate = computed(() => {
333 if (!props.field.minDateValue) return null
334 const parsed = parseDateString(
335 String(props.field.minDateValue),
336 (props.field.dateFormat as string) || 'MM/DD/YYYY',
337 )
338 if (parsed.day && parsed.month && parsed.year) {
339 return new Date(Number(parsed.year), Number(parsed.month) - 1, Number(parsed.day))
340 }
341 return null
342 })
343
344 const maxDate = computed(() => {
345 if (!props.field.maxDateValue) return null
346 const parsed = parseDateString(
347 String(props.field.maxDateValue),
348 (props.field.dateFormat as string) || 'MM/DD/YYYY',
349 )
350 if (parsed.day && parsed.month && parsed.year) {
351 return new Date(Number(parsed.year), Number(parsed.month) - 1, Number(parsed.day))
352 }
353 return null
354 })
355
356 // Function to disable dates outside the min/max range for date picker
357 const disabledDatePickerDates = (time: Date) => {
358 if (minDate.value && time < minDate.value) {
359 return true
360 }
361 if (maxDate.value && time > maxDate.value) {
362 return true
363 }
364 return false
365 }
366
367 // Handle text input mode changes
368 function onTextInputChange(value: string | number | null | undefined) {
369 // Just update the local value as user types
370 textInputValue.value = value != null ? String(value) : ''
371 emit('update:modelValue', value === undefined || value === null ? '' : value)
372 }
373
374 function onTextInputBlur() {
375 // Validate and format the date when user leaves the input
376 const value = textInputValue.value.trim()
377
378 if (!value) {
379 handleInput('', true)
380 return
381 }
382
383 // Try to parse the date according to the format
384 const parsed = parseDateString(value, (props.field.dateFormat as string) || 'MM/DD/YYYY')
385
386 if (parsed.day && parsed.month && parsed.year) {
387 // Validate the date values
388 const dayNum = Number(parsed.day)
389 const monthNum = Number(parsed.month)
390 const yearNum = Number(parsed.year)
391
392 // Check if date is valid
393 const isValidDate =
394 dayNum >= 1 &&
395 dayNum <= 31 &&
396 monthNum >= 1 &&
397 monthNum <= 12 &&
398 yearNum >= 1900 &&
399 yearNum <= 2100
400
401 if (isValidDate) {
402 const enteredDate = new Date(yearNum, monthNum - 1, dayNum)
403
404 // Validate against min/max date if set
405 if (minDate.value && enteredDate < minDate.value) {
406 // Date is before minimum - keep the input as is to show error
407 handleInput(value, false)
408 return
409 }
410 if (maxDate.value && enteredDate > maxDate.value) {
411 // Date is after maximum - keep the input as is to show error
412 handleInput(value, false)
413 return
414 }
415
416 // Format the date properly
417 const formatted = formatDateString(
418 String(dayNum),
419 String(monthNum),
420 String(yearNum),
421 (props.field.dateFormat as string) || 'MM/DD/YYYY',
422 )
423
424 textInputValue.value = formatted
425 handleInput(formatted, true)
426 } else {
427 // Invalid date - keep the input as is to show error
428 handleInput(value, false)
429 }
430 } else {
431 // Could not parse - keep the input as is to show error
432 handleInput(value, false)
433 }
434 }
435
436 // Handle dropdown mode changes
437 function onPartChange() {
438 nextTick(() => {
439 // Ensure values are strings and validate they're not invalid numeric strings
440 const dayStr = selectedDay.value == null ? '' : String(selectedDay.value)
441 const monthStr = selectedMonth.value == null ? '' : String(selectedMonth.value)
442 const yearStr = selectedYear.value == null ? '' : String(selectedYear.value)
443
444 // Check for invalid values that might contain MIN_SAFE_INTEGER or other invalid numbers
445 const isValidDay = dayStr && !dayStr.includes('-') && dayStr !== '0' && !isNaN(Number(dayStr))
446 const isValidMonth =
447 monthStr && !monthStr.includes('-') && monthStr !== '0' && !isNaN(Number(monthStr))
448 const isValidYear =
449 yearStr && !yearStr.includes('-') && yearStr.length === 4 && !isNaN(Number(yearStr))
450
451 // Only emit when all three values are valid or all are empty
452 const hasAllValues = isValidDay && isValidMonth && isValidYear
453 const hasNoValues = !dayStr && !monthStr && !yearStr
454
455 if (hasAllValues) {
456 const formatted = formatDateString(
457 dayStr,
458 monthStr,
459 yearStr,
460 (props.field.dateFormat as string) || 'MM/DD/YYYY',
461 )
462
463 // Validate against min/max date if set
464 if (minDate.value || maxDate.value) {
465 const selectedDate = new Date(Number(yearStr), Number(monthStr) - 1, Number(dayStr))
466 if (minDate.value && selectedDate < minDate.value) {
467 // Date is before minimum - don't update
468 return
469 }
470 if (maxDate.value && selectedDate > maxDate.value) {
471 // Date is after maximum - don't update
472 return
473 }
474 }
475
476 handleInput(formatted, true)
477 } else if (hasNoValues) {
478 handleInput('', true)
479 }
480 // Don't emit anything for partial or invalid states
481 })
482 }
483
484 // Options for dropdowns
485 const dayOptions = computed(() => {
486 const allDays = getDaysArray()
487
488 // If min/max dates are set, filter days accordingly
489 if (minDate.value || maxDate.value) {
490 // If we have a selected month and year, filter days for that specific month/year
491 if (selectedMonth.value && selectedYear.value) {
492 const month = Number(selectedMonth.value)
493 const year = Number(selectedYear.value)
494
495 return allDays.filter((day) => {
496 if (!day.value) return true // Keep the empty option
497 const dayNum = Number(day.value)
498 const testDate = new Date(year, month - 1, dayNum)
499
500 if (minDate.value && testDate < minDate.value) return false
501 if (maxDate.value && testDate > maxDate.value) return false
502 return true
503 })
504 }
505
506 // If only year is selected, show days that could be valid in any month of that year
507 if (selectedYear.value) {
508 const year = Number(selectedYear.value)
509
510 return allDays.filter((day) => {
511 if (!day.value) return true // Keep the empty option
512 const dayNum = Number(day.value)
513
514 // Check if this day is valid in any month of the selected year
515 for (let month = 1; month <= 12; month++) {
516 const testDate = new Date(year, month - 1, dayNum)
517
518 // Skip invalid dates (e.g., Feb 30)
519 if (testDate.getDate() !== dayNum) continue
520
521 const isValid =
522 (!minDate.value || testDate >= minDate.value) &&
523 (!maxDate.value || testDate <= maxDate.value)
524 if (isValid) return true
525 }
526 return false
527 })
528 }
529
530 // No year/month selected yet - show days that could be valid in the allowed date range
531 return allDays.filter((day) => {
532 if (!day.value) return true // Keep the empty option
533 const dayNum = Number(day.value)
534
535 // Get the range of years to check
536 const minYear = minDate.value ? minDate.value.getFullYear() : new Date().getFullYear() - 100
537 const maxYear = maxDate.value ? maxDate.value.getFullYear() : new Date().getFullYear() + 100
538
539 // Check if this day number appears in any valid month/year combination
540 for (let year = minYear; year <= maxYear; year++) {
541 for (let month = 1; month <= 12; month++) {
542 const testDate = new Date(year, month - 1, dayNum)
543
544 // Skip invalid dates (e.g., Feb 30)
545 if (testDate.getDate() !== dayNum) continue
546
547 const isValid =
548 (!minDate.value || testDate >= minDate.value) &&
549 (!maxDate.value || testDate <= maxDate.value)
550 if (isValid) return true
551 }
552 }
553 return false
554 })
555 }
556
557 return allDays
558 })
559
560 const monthOptions = computed(() => {
561 const allMonths = getMonthsArray()
562
563 // If min/max dates are set, filter months accordingly
564 if (minDate.value || maxDate.value) {
565 // If we have a selected year, filter months for that specific year
566 if (selectedYear.value) {
567 const year = Number(selectedYear.value)
568
569 return allMonths.filter((month) => {
570 if (!month.value) return true // Keep the empty option
571 const monthNum = Number(month.value)
572
573 // Check first and last day of the month
574 const firstDay = new Date(year, monthNum - 1, 1)
575 const lastDay = new Date(year, monthNum, 0)
576
577 // Include month if any day in it falls within the valid range
578 if (minDate.value && lastDay < minDate.value) return false
579 if (maxDate.value && firstDay > maxDate.value) return false
580 return true
581 })
582 }
583
584 // No year selected yet - show months that could be valid in the allowed date range
585 return allMonths.filter((month) => {
586 if (!month.value) return true // Keep the empty option
587 const monthNum = Number(month.value)
588
589 // Get the range of years to check
590 const minYear = minDate.value ? minDate.value.getFullYear() : new Date().getFullYear() - 100
591 const maxYear = maxDate.value ? maxDate.value.getFullYear() : new Date().getFullYear() + 100
592
593 // Check if this month appears in any valid year
594 for (let year = minYear; year <= maxYear; year++) {
595 const firstDay = new Date(year, monthNum - 1, 1)
596 const lastDay = new Date(year, monthNum, 0)
597
598 // Include month if any day in it falls within the valid range
599 const isValid =
600 (!minDate.value || lastDay >= minDate.value) &&
601 (!maxDate.value || firstDay <= maxDate.value)
602 if (isValid) return true
603 }
604 return false
605 })
606 }
607
608 return allMonths
609 })
610
611 const yearOptions = computed(() => {
612 let minYear = new Date().getFullYear() - 100
613 let maxYear = new Date().getFullYear() + 100
614
615 // If minDateValue is set, use its year as the minimum
616 if (minDate.value) {
617 minYear = minDate.value.getFullYear()
618 }
619
620 // If maxDateValue is set, use its year as the maximum
621 if (maxDate.value) {
622 maxYear = maxDate.value.getFullYear()
623 }
624
625 return getYearsArray(minYear, maxYear)
626 })
627
628 // Watch for modelValue changes to update inputs
629 watch(
630 () => [props.modelValue, props.field.dateFormat],
631 () => {
632 // Skip if this is an internal update (from user input)
633 if (isInternalUpdate.value) {
634 return
635 }
636
637 // Use modelValue if available, otherwise use defaultValue
638 let val = props.modelValue != null ? String(props.modelValue) : ''
639
640 // If no modelValue, check for defaultValue
641 if (!val && props.field.defaultValue) {
642 val = String(props.field.defaultValue)
643 }
644
645 // Sanitize: reject any invalid values containing MIN_SAFE_INTEGER or other invalid data
646 if (val && (val.includes('-9007199254740991') || val.includes('MIN_SAFE_INTEGER'))) {
647 val = ''
648 // Emit empty value to clean up the corrupted data
649 handleInput('', false)
650 }
651
652 if (val && val.trim() !== '') {
653 if (props.field.dateFieldType === 'input') {
654 // For text input mode, just set the formatted value
655 textInputValue.value = val
656 } else if (props.field.dateFieldType === 'dropdown') {
657 const parsed = parseDateString(val, (props.field.dateFormat as string) || 'MM/DD/YYYY')
658 // Only set if parsed values are valid strings (not empty and not invalid)
659 selectedDay.value = parsed.day && parsed.day !== '' && parsed.day !== '0' ? parsed.day : ''
660 selectedMonth.value =
661 parsed.month && parsed.month !== '' && parsed.month !== '0' ? parsed.month : ''
662 selectedYear.value =
663 parsed.year && parsed.year !== '' && !parsed.year.includes('-') ? parsed.year : ''
664 }
665 } else {
666 if (props.field.dateFieldType === 'input') {
667 textInputValue.value = ''
668 } else if (props.field.dateFieldType === 'dropdown') {
669 selectedDay.value = ''
670 selectedMonth.value = ''
671 selectedYear.value = ''
672 }
673 }
674 },
675 { immediate: true },
676 )
677
678 // Watch for default value changes to update inputs when modelValue is empty
679 watch(
680 () => props.field.defaultValue,
681 (newDefaultValue) => {
682 // Only apply default value if modelValue is empty or undefined
683 const currentValue = props.modelValue != null ? String(props.modelValue) : ''
684 if (currentValue) {
685 return // Don't override user's current value
686 }
687
688 if (newDefaultValue && typeof newDefaultValue === 'string') {
689 if (props.field.dateFieldType === 'input') {
690 // For text input mode, just set the formatted value
691 textInputValue.value = newDefaultValue
692 } else if (props.field.dateFieldType === 'dropdown') {
693 const parsed = parseDateString(
694 newDefaultValue,
695 (props.field.dateFormat as string) || 'MM/DD/YYYY',
696 )
697 selectedDay.value = parsed.day || ''
698 selectedMonth.value = parsed.month || ''
699 selectedYear.value = parsed.year || ''
700 }
701 } else {
702 // Clear values if no default value
703 if (props.field.dateFieldType === 'input') {
704 textInputValue.value = ''
705 } else if (props.field.dateFieldType === 'dropdown') {
706 selectedDay.value = ''
707 selectedMonth.value = ''
708 selectedYear.value = ''
709 }
710 }
711 },
712 { immediate: false },
713 )
714
715 const fieldID = computed(() => {
716 return `ivyforms-field__date-input_${props.field.formId || ''}_${props.field.fieldIndex || ''}`
717 })
718
719 // Ensure fields start with empty value if no modelValue or defaultValue
720 onMounted(() => {
721 const hasModelValue = props.modelValue != null && String(props.modelValue) !== ''
722 const hasDefaultValue =
723 props.field.defaultValue != null && String(props.field.defaultValue) !== ''
724
725 // If no value exists, emit empty string to ensure form doesn't try to validate placeholders or undefined values
726 if (!hasModelValue && !hasDefaultValue) {
727 if (props.field.dateFieldType === 'input' || props.field.dateFieldType === 'dropdown') {
728 handleInput('', false)
729 }
730 }
731 })
732 </script>
733
734 <style lang="scss">
735 .ivyforms-field__date {
736 &__sublabel {
737 color: var(--map-base-text-0);
738 }
739
740 &--is-readonly {
741 opacity: 0.6;
742 pointer-events: none;
743 }
744
745 .ivyforms-field__date-picker,
746 .ivyforms-field__date-input,
747 .ivyforms-field__date-day,
748 .ivyforms-field__date-month,
749 .ivyforms-field__date-year {
750 width: 100%;
751 }
752 }
753 </style>
754