@datametria/vue-components 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +25 -7
  2. package/dist/index.es.js +3378 -2148
  3. package/dist/index.umd.js +9 -9
  4. package/dist/src/components/DatametriaAutocomplete.vue.d.ts +14 -17
  5. package/dist/src/components/DatametriaBreadcrumb.vue.d.ts +39 -7
  6. package/dist/src/components/DatametriaCheckbox.vue.d.ts +35 -6
  7. package/dist/src/components/DatametriaCheckboxGroup.vue.d.ts +30 -0
  8. package/dist/src/components/DatametriaDataTable.vue.d.ts +64 -0
  9. package/dist/src/components/DatametriaDatePicker.vue.d.ts +15 -37
  10. package/dist/src/components/DatametriaDialog.vue.d.ts +71 -0
  11. package/dist/src/components/DatametriaEmpty.vue.d.ts +30 -0
  12. package/dist/src/components/DatametriaFloatingBar.vue.d.ts +2 -2
  13. package/dist/src/components/DatametriaForm.vue.d.ts +40 -0
  14. package/dist/src/components/DatametriaFormItem.vue.d.ts +28 -0
  15. package/dist/src/components/DatametriaGrid.vue.d.ts +1 -1
  16. package/dist/src/components/DatametriaInput.vue.d.ts +69 -10
  17. package/dist/src/components/DatametriaMenu.vue.d.ts +3 -3
  18. package/dist/src/components/DatametriaNavbar.vue.d.ts +2 -2
  19. package/dist/src/components/DatametriaPagination.vue.d.ts +29 -0
  20. package/dist/src/components/DatametriaPopconfirm.vue.d.ts +43 -0
  21. package/dist/src/components/DatametriaProgress.vue.d.ts +33 -8
  22. package/dist/src/components/DatametriaRadio.vue.d.ts +25 -6
  23. package/dist/src/components/DatametriaRadioGroup.vue.d.ts +29 -0
  24. package/dist/src/components/DatametriaResult.vue.d.ts +30 -0
  25. package/dist/src/components/DatametriaSelect.vue.d.ts +16 -11
  26. package/dist/src/components/DatametriaSidebar.vue.d.ts +3 -3
  27. package/dist/src/components/DatametriaSlider.vue.d.ts +3 -3
  28. package/dist/src/components/DatametriaSortableTable.vue.d.ts +1 -1
  29. package/dist/src/components/DatametriaSteps.vue.d.ts +45 -0
  30. package/dist/src/components/DatametriaSwitch.vue.d.ts +9 -4
  31. package/dist/src/components/DatametriaTabPane.vue.d.ts +28 -0
  32. package/dist/src/components/DatametriaTextarea.vue.d.ts +27 -8
  33. package/dist/src/components/DatametriaTimePicker.vue.d.ts +17 -25
  34. package/dist/src/components/DatametriaToast.vue.d.ts +1 -1
  35. package/dist/src/components/DatametriaTooltip.vue.d.ts +1 -1
  36. package/dist/src/components/DatametriaTree.vue.d.ts +31 -0
  37. package/dist/src/components/DatametriaTreeNode.vue.d.ts +17 -0
  38. package/dist/src/components/DatametriaUpload.vue.d.ts +64 -0
  39. package/dist/src/index.d.ts +14 -0
  40. package/dist/vue-components.css +1 -1
  41. package/package.json +4 -3
  42. package/src/components/DatametriaAutocomplete.vue +155 -260
  43. package/src/components/DatametriaBreadcrumb.vue +66 -80
  44. package/src/components/DatametriaCheckbox.vue +150 -37
  45. package/src/components/DatametriaCheckboxGroup.vue +43 -0
  46. package/src/components/DatametriaDataTable.vue +304 -0
  47. package/src/components/DatametriaDatePicker.vue +238 -614
  48. package/src/components/DatametriaDialog.vue +295 -0
  49. package/src/components/DatametriaDropdown.vue +352 -0
  50. package/src/components/DatametriaEmpty.vue +153 -0
  51. package/src/components/DatametriaForm.vue +160 -0
  52. package/src/components/DatametriaFormItem.vue +181 -0
  53. package/src/components/DatametriaInput.vue +226 -63
  54. package/src/components/DatametriaPagination.vue +373 -0
  55. package/src/components/DatametriaPopconfirm.vue +236 -0
  56. package/src/components/DatametriaProgress.vue +176 -63
  57. package/src/components/DatametriaRadio.vue +83 -72
  58. package/src/components/DatametriaRadioGroup.vue +42 -0
  59. package/src/components/DatametriaResult.vue +133 -0
  60. package/src/components/DatametriaSelect.vue +172 -67
  61. package/src/components/DatametriaSortableTable.vue +35 -4
  62. package/src/components/DatametriaSteps.vue +314 -0
  63. package/src/components/DatametriaSwitch.vue +86 -80
  64. package/src/components/DatametriaTabPane.vue +82 -0
  65. package/src/components/DatametriaTextarea.vue +140 -100
  66. package/src/components/DatametriaTimePicker.vue +231 -214
  67. package/src/components/DatametriaTree.vue +124 -0
  68. package/src/components/DatametriaTreeNode.vue +174 -0
  69. package/src/components/DatametriaUpload.vue +365 -0
  70. package/src/index.ts +25 -11
  71. package/src/components/__tests__/DatametriaAutocomplete.test.ts +0 -180
  72. package/src/components/__tests__/DatametriaBreadcrumb.test.ts +0 -75
  73. package/src/components/__tests__/DatametriaCheckbox.test.ts +0 -47
  74. package/src/components/__tests__/DatametriaDatePicker.test.ts +0 -234
  75. package/src/components/__tests__/DatametriaProgress.test.ts +0 -90
  76. package/src/components/__tests__/DatametriaRadio.test.ts +0 -77
  77. package/src/components/__tests__/DatametriaSwitch.test.ts +0 -64
  78. package/src/components/__tests__/DatametriaTextarea.test.ts +0 -66
@@ -1,758 +1,382 @@
1
1
  <template>
2
- <div class="dm-datepicker" ref="datepickerRef">
3
- <label v-if="label" :for="inputId" class="dm-datepicker__label">
4
- {{ label }}
5
- <span v-if="required" class="dm-datepicker__required">*</span>
6
- </label>
7
-
8
- <div class="dm-datepicker__wrapper">
9
- <input
10
- :id="inputId"
11
- v-model="displayValue"
12
- type="text"
13
- class="dm-datepicker__input"
14
- :class="{ 'dm-datepicker__input--error': errorMessage }"
15
- :placeholder="placeholder"
16
- :disabled="disabled"
17
- :readonly="readonly"
18
- :required="required"
19
- :aria-label="label || 'Date picker'"
20
- :aria-invalid="!!errorMessage"
21
- :aria-describedby="errorMessage ? `${inputId}-error` : undefined"
22
- @click="toggleCalendar"
23
- @focus="handleFocus"
24
- @keydown="handleKeydown"
25
- />
26
-
27
- <button
28
- v-if="clearable && hasValue"
29
- type="button"
30
- class="dm-datepicker__clear"
31
- @click="clearDate"
32
- aria-label="Clear date"
2
+ <div class="datametria-datepicker" :class="{ 'datametria-datepicker--disabled': disabled }">
3
+ <input
4
+ ref="inputRef"
5
+ v-model="displayValue"
6
+ type="text"
7
+ class="datametria-datepicker__input"
8
+ :placeholder="placeholder"
9
+ :disabled="disabled"
10
+ :readonly="readonly"
11
+ @focus="handleFocus"
12
+ @blur="handleBlur"
13
+ />
14
+ <Teleport to="body">
15
+ <div
16
+ v-if="isOpen"
17
+ ref="dropdownRef"
18
+ class="datametria-datepicker__dropdown"
19
+ :style="dropdownStyle"
33
20
  >
34
- ×
35
- </button>
36
- </div>
37
-
38
- <div
39
- v-if="isOpen"
40
- class="dm-datepicker__calendar"
41
- role="dialog"
42
- aria-label="Calendar"
43
- @click.stop
44
- >
45
- <div class="dm-datepicker__header">
46
- <button
47
- type="button"
48
- class="dm-datepicker__nav dm-datepicker__nav-prev"
49
- @click="previousMonth"
50
- aria-label="Previous month"
51
- >
52
-
53
- </button>
54
-
55
- <button
56
- type="button"
57
- class="dm-datepicker__month-year"
58
- @click="toggleYearPicker"
59
- :aria-label="`${currentMonthYear}, click to select year`"
60
- >
61
- {{ currentMonthYear }}
62
- </button>
63
-
64
- <button
65
- type="button"
66
- class="dm-datepicker__nav dm-datepicker__nav-next"
67
- @click="nextMonth"
68
- aria-label="Next month"
69
- >
70
-
71
- </button>
72
- </div>
73
-
74
- <div v-if="showShortcuts && shortcuts.length > 0" class="dm-datepicker__shortcuts">
75
- <button
76
- v-for="shortcut in shortcuts"
77
- :key="shortcut.label"
78
- type="button"
79
- class="dm-datepicker__shortcut"
80
- @click="applyShortcut(shortcut)"
81
- >
82
- {{ shortcut.label }}
83
- </button>
84
- </div>
85
-
86
- <div v-if="showYearPicker" class="dm-datepicker__year-picker">
87
- <div
88
- v-for="year in availableYears"
89
- :key="year"
90
- class="dm-datepicker__year-option"
91
- :class="{ 'dm-datepicker__year-option--current': year === currentDate.getFullYear() }"
92
- @click="selectYear(year)"
93
- >
94
- {{ year }}
95
- </div>
96
- </div>
97
-
98
- <div v-else class="dm-datepicker__grid">
99
- <div class="dm-datepicker__weekdays">
100
- <div
101
- v-for="day in weekdays"
102
- :key="day"
103
- class="dm-datepicker__weekday"
104
- >
105
- {{ day }}
106
- </div>
107
- </div>
108
-
109
- <div class="dm-datepicker__days">
21
+ <div v-if="shortcuts.length" class="datametria-datepicker__shortcuts">
110
22
  <button
111
- v-for="day in calendarDays"
112
- :key="`${day.date}-${day.month}`"
113
- type="button"
114
- class="dm-datepicker__day"
115
- :class="{
116
- 'dm-datepicker__day--other-month': day.otherMonth,
117
- 'dm-datepicker__day--disabled': day.disabled,
118
- 'dm-datepicker__day--selected': day.selected,
119
- 'dm-datepicker__day--today': day.today,
120
- 'dm-datepicker__day--in-range': day.inRange,
121
- 'dm-datepicker__day--range-start': day.rangeStart,
122
- 'dm-datepicker__day--range-end': day.rangeEnd
123
- }"
124
- :data-date="day.dateString"
125
- :disabled="day.disabled"
126
- :aria-label="getDateAriaLabel(day)"
127
- :aria-current="day.today ? 'date' : undefined"
128
- @click="selectDate(day)"
23
+ v-for="shortcut in shortcuts"
24
+ :key="shortcut.text"
25
+ class="datametria-datepicker__shortcut"
26
+ @click="handleShortcut(shortcut)"
129
27
  >
130
- {{ day.date }}
28
+ {{ shortcut.text }}
131
29
  </button>
132
30
  </div>
31
+ <div class="datametria-datepicker__calendar">
32
+ <div class="datametria-datepicker__header">
33
+ <button @click="prevMonth">&lt;</button>
34
+ <span>{{ monthYear }}</span>
35
+ <button @click="nextMonth">&gt;</button>
36
+ </div>
37
+ <div class="datametria-datepicker__weekdays">
38
+ <span v-for="day in weekdays" :key="day">{{ day }}</span>
39
+ </div>
40
+ <div class="datametria-datepicker__days">
41
+ <button
42
+ v-for="day in calendarDays"
43
+ :key="day.date.getTime()"
44
+ class="datametria-datepicker__day"
45
+ :class="{
46
+ 'datametria-datepicker__day--disabled': day.disabled || isDateDisabled(day.date),
47
+ 'datametria-datepicker__day--selected': isSelected(day.date),
48
+ 'datametria-datepicker__day--in-range': isInRange(day.date),
49
+ 'datametria-datepicker__day--other-month': day.otherMonth
50
+ }"
51
+ :disabled="day.disabled || isDateDisabled(day.date)"
52
+ @click="selectDate(day.date)"
53
+ >
54
+ {{ day.date.getDate() }}
55
+ </button>
56
+ </div>
57
+ </div>
133
58
  </div>
134
-
135
- <div v-if="showToday" class="dm-datepicker__footer">
136
- <button
137
- type="button"
138
- class="dm-datepicker__today"
139
- @click="selectToday"
140
- >
141
- Today
142
- </button>
143
- </div>
144
- </div>
145
-
146
- <div v-if="errorMessage" :id="`${inputId}-error`" class="dm-datepicker__error">
147
- {{ errorMessage }}
148
- </div>
59
+ </Teleport>
149
60
  </div>
150
61
  </template>
151
62
 
152
63
  <script setup lang="ts">
153
- import { ref, computed, onMounted, onUnmounted } from 'vue'
154
-
155
- type DateValue = string | { start: string; end: string } | string[]
64
+ import { ref, computed, watch } from 'vue'
156
65
 
157
66
  interface Shortcut {
158
- label: string
159
- value: string | { start: string; end: string } | string[]
67
+ text: string
68
+ value: Date | [Date, Date]
160
69
  }
161
70
 
162
71
  interface Props {
163
- modelValue?: DateValue
164
- mode?: 'single' | 'range' | 'multiple'
72
+ modelValue?: Date | [Date, Date] | null
73
+ type?: 'date' | 'datetime' | 'range'
165
74
  format?: string
166
75
  placeholder?: string
167
- label?: string
168
- errorMessage?: string
169
76
  disabled?: boolean
170
77
  readonly?: boolean
171
- required?: boolean
172
- min?: string
173
- max?: string
174
- disabledDates?: string[]
175
- disabledWeekdays?: number[]
176
- enabledDates?: string[]
177
- showToday?: boolean
178
- clearable?: boolean
179
- closeOnSelect?: boolean
180
- showShortcuts?: boolean
78
+ disabledDate?: (date: Date) => boolean
181
79
  shortcuts?: Shortcut[]
182
80
  }
183
81
 
184
82
  const props = withDefaults(defineProps<Props>(), {
185
- mode: 'single',
83
+ modelValue: null,
84
+ type: 'date',
186
85
  format: 'DD/MM/YYYY',
187
- placeholder: 'Select date...',
188
- showToday: false,
189
- clearable: false,
190
- closeOnSelect: true,
191
- showShortcuts: false,
192
- shortcuts: () => [
193
- { label: 'Today', value: new Date().toISOString().split('T')[0] },
194
- { label: 'Tomorrow', value: new Date(Date.now() + 86400000).toISOString().split('T')[0] },
195
- { label: 'Next Week', value: new Date(Date.now() + 7 * 86400000).toISOString().split('T')[0] }
196
- ]
86
+ placeholder: 'Selecione uma data',
87
+ disabled: false,
88
+ readonly: false,
89
+ shortcuts: () => []
197
90
  })
198
91
 
199
92
  const emit = defineEmits<{
200
- 'update:modelValue': [value: DateValue]
201
- open: []
202
- close: []
203
- clear: []
93
+ 'update:modelValue': [value: Date | [Date, Date] | null]
94
+ change: [value: Date | [Date, Date] | null]
204
95
  }>()
205
96
 
206
- const datepickerRef = ref<HTMLElement>()
97
+ const inputRef = ref<HTMLInputElement>()
98
+ const dropdownRef = ref<HTMLDivElement>()
207
99
  const isOpen = ref(false)
208
- const showYearPicker = ref(false)
209
- const currentDate = ref(new Date())
210
- const isMobile = ref(false)
211
-
212
- const inputId = computed(() => `datepicker-${Math.random().toString(36).substr(2, 9)}`)
213
- const weekdays = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
214
-
215
- const hasValue = computed(() => {
216
- if (props.mode === 'single') return !!props.modelValue
217
- if (props.mode === 'range') return !!(props.modelValue as any)?.start && !!(props.modelValue as any)?.end
218
- if (props.mode === 'multiple') return Array.isArray(props.modelValue) && props.modelValue.length > 0
219
- return false
220
- })
100
+ const currentMonth = ref(new Date())
101
+ const rangeStart = ref<Date | null>(null)
221
102
 
222
- const displayValue = computed(() => {
223
- if (!props.modelValue) return ''
224
-
225
- if (props.mode === 'single') {
226
- const date = new Date(props.modelValue as string)
227
- return formatDate(date, props.format)
228
- }
229
-
230
- if (props.mode === 'range') {
231
- const range = props.modelValue as { start: string; end: string }
232
- if (!range.start || !range.end) return ''
233
- const startDate = formatDate(new Date(range.start), props.format)
234
- const endDate = formatDate(new Date(range.end), props.format)
235
- return `${startDate} - ${endDate}`
236
- }
237
-
238
- if (props.mode === 'multiple') {
239
- const dates = props.modelValue as string[]
240
- if (dates.length === 0) return ''
241
- if (dates.length === 1) return formatDate(new Date(dates[0]), props.format)
242
- return `${dates.length} dates selected`
243
- }
244
-
245
- return ''
246
- })
103
+ const weekdays = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb']
247
104
 
248
- const currentMonthYear = computed(() => {
249
- const month = currentDate.value.toLocaleString('default', { month: 'long' })
250
- const year = currentDate.value.getFullYear()
251
- return `${month} ${year}`
105
+ const monthYear = computed(() => {
106
+ const months = ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho',
107
+ 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro']
108
+ return `${months[currentMonth.value.getMonth()]} ${currentMonth.value.getFullYear()}`
252
109
  })
253
110
 
254
- const availableYears = computed(() => {
255
- const currentYear = new Date().getFullYear()
256
- const years = []
257
- for (let i = currentYear - 50; i <= currentYear + 10; i++) {
258
- years.push(i)
111
+ const displayValue = computed(() => {
112
+ if (!props.modelValue) return ''
113
+ if (props.type === 'range' && Array.isArray(props.modelValue)) {
114
+ return `${formatDate(props.modelValue[0])} - ${formatDate(props.modelValue[1])}`
259
115
  }
260
- return years
116
+ return formatDate(props.modelValue as Date)
261
117
  })
262
118
 
263
119
  const calendarDays = computed(() => {
264
- const year = currentDate.value.getFullYear()
265
- const month = currentDate.value.getMonth()
266
-
120
+ const year = currentMonth.value.getFullYear()
121
+ const month = currentMonth.value.getMonth()
267
122
  const firstDay = new Date(year, month, 1)
268
- const startDate = new Date(firstDay)
269
- startDate.setDate(startDate.getDate() - firstDay.getDay())
123
+ const lastDay = new Date(year, month + 1, 0)
124
+ const prevLastDay = new Date(year, month, 0)
125
+
126
+ const days: Array<{ date: Date; disabled: boolean; otherMonth: boolean }> = []
127
+
128
+ // Previous month days
129
+ const firstDayOfWeek = firstDay.getDay()
130
+ for (let i = firstDayOfWeek - 1; i >= 0; i--) {
131
+ const date = new Date(year, month, -i)
132
+ days.push({ date, disabled: false, otherMonth: true })
133
+ }
270
134
 
271
- const days = []
272
- const today = new Date()
135
+ // Current month days
136
+ for (let i = 1; i <= lastDay.getDate(); i++) {
137
+ const date = new Date(year, month, i)
138
+ days.push({ date, disabled: false, otherMonth: false })
139
+ }
273
140
 
274
- for (let i = 0; i < 42; i++) {
275
- const date = new Date(startDate)
276
- date.setDate(startDate.getDate() + i)
277
-
278
- const dateString = date.toISOString().split('T')[0]
279
- const isOtherMonth = date.getMonth() !== month
280
- const isDisabled = isDateDisabled(date)
281
- const isSelected = isDateSelected(dateString)
282
- const isToday = isSameDate(date, today)
283
-
284
- days.push({
285
- date: date.getDate(),
286
- month: date.getMonth(),
287
- year: date.getFullYear(),
288
- dateString,
289
- otherMonth: isOtherMonth,
290
- disabled: isDisabled,
291
- selected: isSelected,
292
- today: isToday,
293
- inRange: false,
294
- rangeStart: false,
295
- rangeEnd: false
296
- })
141
+ // Next month days
142
+ const remainingDays = 42 - days.length
143
+ for (let i = 1; i <= remainingDays; i++) {
144
+ const date = new Date(year, month + 1, i)
145
+ days.push({ date, disabled: false, otherMonth: true })
297
146
  }
298
147
 
299
148
  return days
300
149
  })
301
150
 
302
- const formatDate = (date: Date, format: string): string => {
303
- const day = date.getDate().toString().padStart(2, '0')
304
- const month = (date.getMonth() + 1).toString().padStart(2, '0')
305
- const year = date.getFullYear().toString()
306
-
307
- return format
308
- .replace('DD', day)
309
- .replace('MM', month)
310
- .replace('YYYY', year)
311
- }
151
+ const dropdownStyle = computed(() => {
152
+ if (!inputRef.value) return {}
153
+ const rect = inputRef.value.getBoundingClientRect()
154
+ return {
155
+ position: 'fixed',
156
+ top: `${rect.bottom + 4}px`,
157
+ left: `${rect.left}px`,
158
+ zIndex: 9999
159
+ }
160
+ })
312
161
 
313
- const isSameDate = (date1: Date, date2: Date): boolean => {
314
- return date1.getFullYear() === date2.getFullYear() &&
315
- date1.getMonth() === date2.getMonth() &&
316
- date1.getDate() === date2.getDate()
162
+ const formatDate = (date: Date): string => {
163
+ const day = String(date.getDate()).padStart(2, '0')
164
+ const month = String(date.getMonth() + 1).padStart(2, '0')
165
+ const year = date.getFullYear()
166
+ return `${day}/${month}/${year}`
317
167
  }
318
168
 
319
169
  const isDateDisabled = (date: Date): boolean => {
320
- const dateString = date.toISOString().split('T')[0]
321
-
322
- if (props.min && date < new Date(props.min)) return true
323
- if (props.max && date > new Date(props.max)) return true
324
- if (props.disabledDates?.includes(dateString)) return true
325
- if (props.disabledWeekdays?.includes(date.getDay())) return true
326
- if (props.enabledDates && !props.enabledDates.includes(dateString)) return true
327
-
328
- return false
170
+ return props.disabledDate ? props.disabledDate(date) : false
329
171
  }
330
172
 
331
- const isDateSelected = (dateString: string): boolean => {
332
- if (props.mode === 'single') {
333
- return props.modelValue === dateString
334
- }
335
-
336
- if (props.mode === 'range') {
337
- const range = props.modelValue as { start: string; end: string }
338
- return range?.start === dateString || range?.end === dateString
339
- }
340
-
341
- if (props.mode === 'multiple') {
342
- const dates = props.modelValue as string[]
343
- return dates?.includes(dateString) || false
173
+ const isSelected = (date: Date): boolean => {
174
+ if (!props.modelValue) return false
175
+ if (props.type === 'range' && Array.isArray(props.modelValue)) {
176
+ return isSameDay(date, props.modelValue[0]) || isSameDay(date, props.modelValue[1])
344
177
  }
345
-
346
- return false
178
+ return isSameDay(date, props.modelValue as Date)
347
179
  }
348
180
 
349
- const getDateAriaLabel = (day: any): string => {
350
- const date = new Date(day.year, day.month, day.date)
351
- const dateString = date.toLocaleDateString('en-US', {
352
- weekday: 'long',
353
- year: 'numeric',
354
- month: 'long',
355
- day: 'numeric'
356
- })
357
-
358
- let label = dateString
359
- if (day.today) label += ', today'
360
- if (day.selected) label += ', selected'
361
- if (day.disabled) label += ', disabled'
362
-
363
- return label
181
+ const isInRange = (date: Date): boolean => {
182
+ if (props.type !== 'range' || !Array.isArray(props.modelValue)) return false
183
+ const [start, end] = props.modelValue
184
+ return date > start && date < end
364
185
  }
365
186
 
366
- const toggleCalendar = () => {
367
- if (!props.disabled && !props.readonly) {
368
- isOpen.value = !isOpen.value
369
- if (isOpen.value) {
370
- emit('open')
187
+ const isSameDay = (date1: Date, date2: Date): boolean => {
188
+ return date1.getDate() === date2.getDate() &&
189
+ date1.getMonth() === date2.getMonth() &&
190
+ date1.getFullYear() === date2.getFullYear()
191
+ }
192
+
193
+ const selectDate = (date: Date) => {
194
+ if (props.type === 'range') {
195
+ if (!rangeStart.value) {
196
+ rangeStart.value = date
371
197
  } else {
372
- emit('close')
198
+ const start = rangeStart.value < date ? rangeStart.value : date
199
+ const end = rangeStart.value < date ? date : rangeStart.value
200
+ emit('update:modelValue', [start, end])
201
+ emit('change', [start, end])
202
+ rangeStart.value = null
203
+ isOpen.value = false
373
204
  }
205
+ } else {
206
+ emit('update:modelValue', date)
207
+ emit('change', date)
208
+ isOpen.value = false
374
209
  }
375
210
  }
376
211
 
377
212
  const handleFocus = () => {
378
213
  if (!props.disabled && !props.readonly) {
379
214
  isOpen.value = true
380
- emit('open')
381
- }
382
- }
383
-
384
- const handleKeydown = (event: KeyboardEvent) => {
385
- if (event.key === 'Enter' || event.key === ' ') {
386
- event.preventDefault()
387
- toggleCalendar()
388
- } else if (event.key === 'Escape') {
389
- isOpen.value = false
390
- emit('close')
391
215
  }
392
216
  }
393
217
 
394
- const previousMonth = () => {
395
- currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() - 1, 1)
396
- }
397
-
398
- const nextMonth = () => {
399
- currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() + 1, 1)
400
- }
401
-
402
- const toggleYearPicker = () => {
403
- showYearPicker.value = !showYearPicker.value
404
- }
405
-
406
- const selectYear = (year: number) => {
407
- currentDate.value = new Date(year, currentDate.value.getMonth(), 1)
408
- showYearPicker.value = false
409
- }
410
-
411
- const selectDate = (day: any) => {
412
- if (day.disabled) return
413
-
414
- const dateString = day.dateString
415
-
416
- if (props.mode === 'single') {
417
- emit('update:modelValue', dateString)
418
- if (props.closeOnSelect) {
218
+ const handleBlur = (event: FocusEvent) => {
219
+ setTimeout(() => {
220
+ if (!dropdownRef.value?.contains(event.relatedTarget as Node)) {
419
221
  isOpen.value = false
420
- emit('close')
421
- }
422
- }
423
-
424
- if (props.mode === 'range') {
425
- const currentRange = (props.modelValue as { start: string; end: string }) || { start: '', end: '' }
426
-
427
- if (!currentRange.start || (currentRange.start && currentRange.end)) {
428
- emit('update:modelValue', { start: dateString, end: '' })
429
- } else {
430
- const start = new Date(currentRange.start)
431
- const selected = new Date(dateString)
432
-
433
- if (selected < start) {
434
- emit('update:modelValue', { start: dateString, end: currentRange.start })
435
- } else {
436
- emit('update:modelValue', { start: currentRange.start, end: dateString })
437
- }
438
-
439
- if (props.closeOnSelect) {
440
- isOpen.value = false
441
- emit('close')
442
- }
443
- }
444
- }
445
-
446
- if (props.mode === 'multiple') {
447
- const currentDates = (props.modelValue as string[]) || []
448
- const index = currentDates.indexOf(dateString)
449
-
450
- if (index >= 0) {
451
- const newDates = [...currentDates]
452
- newDates.splice(index, 1)
453
- emit('update:modelValue', newDates)
454
- } else {
455
- emit('update:modelValue', [...currentDates, dateString])
456
222
  }
457
- }
223
+ }, 200)
458
224
  }
459
225
 
460
- const selectToday = () => {
461
- const today = new Date().toISOString().split('T')[0]
462
-
463
- if (props.mode === 'single') {
464
- emit('update:modelValue', today)
465
- } else if (props.mode === 'range') {
466
- emit('update:modelValue', { start: today, end: today })
467
- } else if (props.mode === 'multiple') {
468
- const currentDates = (props.modelValue as string[]) || []
469
- if (!currentDates.includes(today)) {
470
- emit('update:modelValue', [...currentDates, today])
471
- }
472
- }
473
-
226
+ const handleShortcut = (shortcut: Shortcut) => {
227
+ emit('update:modelValue', shortcut.value)
228
+ emit('change', shortcut.value)
474
229
  isOpen.value = false
475
- emit('close')
476
230
  }
477
231
 
478
- const applyShortcut = (shortcut: Shortcut) => {
479
- emit('update:modelValue', shortcut.value as DateValue)
480
- isOpen.value = false
481
- emit('close')
232
+ const prevMonth = () => {
233
+ currentMonth.value = new Date(currentMonth.value.getFullYear(), currentMonth.value.getMonth() - 1)
482
234
  }
483
235
 
484
- const clearDate = () => {
485
- if (props.mode === 'single') {
486
- emit('update:modelValue', '')
487
- } else if (props.mode === 'range') {
488
- emit('update:modelValue', { start: '', end: '' })
489
- } else if (props.mode === 'multiple') {
490
- emit('update:modelValue', [])
491
- }
492
- emit('clear')
236
+ const nextMonth = () => {
237
+ currentMonth.value = new Date(currentMonth.value.getFullYear(), currentMonth.value.getMonth() + 1)
493
238
  }
494
239
 
495
- const handleClickOutside = (event: Event) => {
496
- if (datepickerRef.value && !datepickerRef.value.contains(event.target as Node)) {
497
- isOpen.value = false
498
- showYearPicker.value = false
499
- emit('close')
240
+ watch(() => props.modelValue, (newValue) => {
241
+ if (newValue && !Array.isArray(newValue)) {
242
+ currentMonth.value = new Date(newValue)
500
243
  }
501
- }
502
-
503
- const checkMobile = () => {
504
- isMobile.value = window.innerWidth <= 640
505
- }
506
-
507
- onMounted(() => {
508
- document.addEventListener('click', handleClickOutside)
509
- window.addEventListener('resize', checkMobile)
510
- checkMobile()
511
- })
512
-
513
- onUnmounted(() => {
514
- document.removeEventListener('click', handleClickOutside)
515
- window.removeEventListener('resize', checkMobile)
516
244
  })
517
245
  </script>
518
246
 
519
247
  <style scoped>
520
- .dm-datepicker {
248
+ .datametria-datepicker {
521
249
  position: relative;
250
+ display: inline-block;
522
251
  width: 100%;
523
252
  }
524
253
 
525
- .dm-datepicker__label {
526
- display: block;
527
- margin-bottom: 0.5rem;
528
- font-weight: 500;
529
- color: var(--dm-neutral-700, #374151);
530
- }
531
-
532
- .dm-datepicker__required {
533
- color: var(--dm-error, #ef4444);
534
- }
535
-
536
- .dm-datepicker__wrapper {
537
- position: relative;
538
- }
539
-
540
- .dm-datepicker__input {
254
+ .datametria-datepicker__input {
541
255
  width: 100%;
542
- padding: 0.75rem;
543
- border: 1px solid var(--dm-neutral-300, #d1d5db);
544
- border-radius: var(--dm-radius-md, 0.375rem);
545
- font-size: 1rem;
256
+ padding: 8px 12px;
257
+ border: 1px solid var(--datametria-border-color, #dcdfe6);
258
+ border-radius: 4px;
259
+ font-size: 14px;
260
+ color: var(--datametria-text-color, #303133);
261
+ background-color: var(--datametria-bg-color, #ffffff);
546
262
  cursor: pointer;
547
263
  transition: border-color 0.2s;
548
264
  }
549
265
 
550
- .dm-datepicker__input:focus {
551
- outline: none;
552
- border-color: var(--dm-primary, #0072ce);
553
- box-shadow: 0 0 0 3px rgba(0, 114, 206, 0.1);
266
+ .datametria-datepicker__input:hover {
267
+ border-color: var(--datametria-border-color-hover, #c0c4cc);
554
268
  }
555
269
 
556
- .dm-datepicker__input--error {
557
- border-color: var(--dm-error, #ef4444);
270
+ .datametria-datepicker__input:focus {
271
+ outline: none;
272
+ border-color: var(--datametria-primary-color, #0072ce);
558
273
  }
559
274
 
560
- .dm-datepicker__clear {
561
- position: absolute;
562
- right: 0.75rem;
563
- top: 50%;
564
- transform: translateY(-50%);
565
- background: none;
566
- border: none;
567
- font-size: 1.25rem;
568
- cursor: pointer;
569
- color: var(--dm-neutral-500, #6b7280);
570
- }
571
-
572
- .dm-datepicker__calendar {
573
- position: absolute;
574
- top: 100%;
575
- left: 0;
576
- z-index: 1000;
577
- background: white;
578
- border: 1px solid var(--dm-neutral-300, #d1d5db);
579
- border-radius: var(--dm-radius-md, 0.375rem);
580
- box-shadow: var(--dm-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1));
581
- padding: 1rem;
582
- min-width: 280px;
275
+ .datametria-datepicker--disabled .datametria-datepicker__input {
276
+ background-color: var(--datametria-disabled-bg-color, #f5f7fa);
277
+ cursor: not-allowed;
583
278
  }
584
279
 
585
- @media (max-width: 640px) {
586
- .dm-datepicker__calendar {
587
- position: fixed;
588
- top: 50%;
589
- left: 50%;
590
- transform: translate(-50%, -50%);
591
- width: 90vw;
592
- max-width: 320px;
593
- }
280
+ .datametria-datepicker__dropdown {
281
+ display: flex;
282
+ background: var(--datametria-bg-color, #ffffff);
283
+ border: 1px solid var(--datametria-border-color, #dcdfe6);
284
+ border-radius: 4px;
285
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
286
+ padding: 12px;
594
287
  }
595
288
 
596
- .dm-datepicker__header {
289
+ .datametria-datepicker__shortcuts {
597
290
  display: flex;
598
- align-items: center;
599
- justify-content: space-between;
600
- margin-bottom: 1rem;
291
+ flex-direction: column;
292
+ gap: 4px;
293
+ padding-right: 12px;
294
+ border-right: 1px solid var(--datametria-border-color, #dcdfe6);
295
+ margin-right: 12px;
601
296
  }
602
297
 
603
- .dm-datepicker__nav {
604
- background: none;
298
+ .datametria-datepicker__shortcut {
299
+ padding: 8px 12px;
605
300
  border: none;
606
- font-size: 1.25rem;
301
+ background: none;
607
302
  cursor: pointer;
608
- padding: 0.25rem;
609
- border-radius: var(--dm-radius-sm, 0.25rem);
303
+ text-align: left;
304
+ white-space: nowrap;
305
+ color: var(--datametria-text-color, #303133);
306
+ border-radius: 4px;
610
307
  transition: background-color 0.2s;
611
308
  }
612
309
 
613
- .dm-datepicker__nav:hover {
614
- background-color: var(--dm-neutral-100, #f3f4f6);
615
- }
616
-
617
- .dm-datepicker__month-year {
618
- background: none;
619
- border: none;
620
- font-weight: 600;
621
- cursor: pointer;
622
- padding: 0.5rem;
623
- border-radius: var(--dm-radius-sm, 0.25rem);
624
- transition: background-color 0.2s;
310
+ .datametria-datepicker__shortcut:hover {
311
+ background-color: var(--datametria-hover-bg-color, #f5f7fa);
625
312
  }
626
313
 
627
- .dm-datepicker__month-year:hover {
628
- background-color: var(--dm-neutral-100, #f3f4f6);
314
+ .datametria-datepicker__calendar {
315
+ min-width: 280px;
629
316
  }
630
317
 
631
- .dm-datepicker__shortcuts {
318
+ .datametria-datepicker__header {
632
319
  display: flex;
633
- gap: 0.5rem;
634
- margin-bottom: 1rem;
635
- flex-wrap: wrap;
320
+ justify-content: space-between;
321
+ align-items: center;
322
+ margin-bottom: 12px;
636
323
  }
637
324
 
638
- .dm-datepicker__shortcut {
325
+ .datametria-datepicker__header button {
326
+ padding: 4px 8px;
327
+ border: none;
639
328
  background: none;
640
- border: 1px solid var(--dm-neutral-300, #d1d5db);
641
- padding: 0.25rem 0.5rem;
642
- border-radius: var(--dm-radius-sm, 0.25rem);
643
- cursor: pointer;
644
- font-size: 0.875rem;
645
- transition: all 0.2s;
646
- }
647
-
648
- .dm-datepicker__shortcut:hover {
649
- background-color: var(--dm-neutral-100, #f3f4f6);
650
- }
651
-
652
- .dm-datepicker__year-picker {
653
- display: grid;
654
- grid-template-columns: repeat(4, 1fr);
655
- gap: 0.5rem;
656
- max-height: 200px;
657
- overflow-y: auto;
658
- }
659
-
660
- .dm-datepicker__year-option {
661
- padding: 0.5rem;
662
- text-align: center;
663
329
  cursor: pointer;
664
- border-radius: var(--dm-radius-sm, 0.25rem);
665
- transition: background-color 0.2s;
666
- }
667
-
668
- .dm-datepicker__year-option:hover {
669
- background-color: var(--dm-neutral-100, #f3f4f6);
330
+ font-size: 16px;
331
+ color: var(--datametria-text-color, #303133);
670
332
  }
671
333
 
672
- .dm-datepicker__year-option--current {
673
- background-color: var(--dm-primary, #0072ce);
674
- color: white;
675
- }
676
-
677
- .dm-datepicker__weekdays {
334
+ .datametria-datepicker__weekdays {
678
335
  display: grid;
679
336
  grid-template-columns: repeat(7, 1fr);
680
- gap: 0.25rem;
681
- margin-bottom: 0.5rem;
682
- }
683
-
684
- .dm-datepicker__weekday {
685
- padding: 0.5rem;
337
+ gap: 4px;
338
+ margin-bottom: 8px;
686
339
  text-align: center;
687
- font-size: 0.875rem;
688
- font-weight: 600;
689
- color: var(--dm-neutral-600, #4b5563);
340
+ font-size: 12px;
341
+ color: var(--datametria-info-color, #909399);
690
342
  }
691
343
 
692
- .dm-datepicker__days {
344
+ .datametria-datepicker__days {
693
345
  display: grid;
694
346
  grid-template-columns: repeat(7, 1fr);
695
- gap: 0.25rem;
347
+ gap: 4px;
696
348
  }
697
349
 
698
- .dm-datepicker__day {
699
- padding: 0.5rem;
700
- text-align: center;
701
- cursor: pointer;
350
+ .datametria-datepicker__day {
351
+ padding: 8px;
702
352
  border: none;
703
353
  background: none;
704
- border-radius: var(--dm-radius-sm, 0.25rem);
705
- transition: background-color 0.2s;
706
- }
707
-
708
- .dm-datepicker__day:hover:not(.dm-datepicker__day--disabled) {
709
- background-color: var(--dm-neutral-100, #f3f4f6);
710
- }
711
-
712
- .dm-datepicker__day--other-month {
713
- color: var(--dm-neutral-400, #9ca3af);
354
+ cursor: pointer;
355
+ border-radius: 4px;
356
+ font-size: 14px;
357
+ color: var(--datametria-text-color, #303133);
358
+ transition: all 0.2s;
714
359
  }
715
360
 
716
- .dm-datepicker__day--disabled {
717
- color: var(--dm-neutral-300, #d1d5db);
718
- cursor: not-allowed;
361
+ .datametria-datepicker__day:hover:not(:disabled) {
362
+ background-color: var(--datametria-hover-bg-color, #f5f7fa);
719
363
  }
720
364
 
721
- .dm-datepicker__day--selected {
722
- background-color: var(--dm-primary, #0072ce);
365
+ .datametria-datepicker__day--selected {
366
+ background-color: var(--datametria-primary-color, #0072ce);
723
367
  color: white;
724
368
  }
725
369
 
726
- .dm-datepicker__day--today {
727
- font-weight: 600;
728
- color: var(--dm-primary, #0072ce);
370
+ .datametria-datepicker__day--in-range {
371
+ background-color: var(--datametria-primary-light-color, #ecf5ff);
729
372
  }
730
373
 
731
- .dm-datepicker__footer {
732
- margin-top: 1rem;
733
- padding-top: 1rem;
734
- border-top: 1px solid var(--dm-neutral-200, #e5e7eb);
735
- text-align: center;
736
- }
737
-
738
- .dm-datepicker__today {
739
- background: none;
740
- border: 1px solid var(--dm-primary, #0072ce);
741
- color: var(--dm-primary, #0072ce);
742
- padding: 0.5rem 1rem;
743
- border-radius: var(--dm-radius-sm, 0.25rem);
744
- cursor: pointer;
745
- transition: all 0.2s;
374
+ .datametria-datepicker__day--other-month {
375
+ color: var(--datametria-disabled-text-color, #c0c4cc);
746
376
  }
747
377
 
748
- .dm-datepicker__today:hover {
749
- background-color: var(--dm-primary, #0072ce);
750
- color: white;
751
- }
752
-
753
- .dm-datepicker__error {
754
- margin-top: 0.25rem;
755
- color: var(--dm-error, #ef4444);
756
- font-size: 0.875rem;
378
+ .datametria-datepicker__day--disabled {
379
+ color: var(--datametria-disabled-text-color, #c0c4cc);
380
+ cursor: not-allowed;
757
381
  }
758
- </style>
382
+ </style>