v-calendar-3 1.0.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 (147) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +142 -0
  3. package/dist/cjs/index.css +1133 -0
  4. package/dist/cjs/index.js +10699 -0
  5. package/dist/cjs/index.js.map +1 -0
  6. package/dist/cjs/package.json +1 -0
  7. package/dist/es/index.js +10678 -0
  8. package/dist/es/index.js.map +1 -0
  9. package/dist/es/style.css +1134 -0
  10. package/dist/iife/index.js +2 -0
  11. package/dist/iife/index.js.map +1 -0
  12. package/dist/mjs/index.mjs +10678 -0
  13. package/dist/mjs/index.mjs.map +1 -0
  14. package/dist/mjs/style.css +1134 -0
  15. package/dist/style.css +1134 -0
  16. package/dist/types/components/BaseIcon/BaseIcon.vue.d.ts +41 -0
  17. package/dist/types/components/BaseIcon/icons/IconChevronDown.vue.d.ts +3 -0
  18. package/dist/types/components/BaseIcon/icons/IconChevronLeft.vue.d.ts +3 -0
  19. package/dist/types/components/BaseIcon/icons/IconChevronRight.vue.d.ts +3 -0
  20. package/dist/types/components/BaseIcon/icons/IconClock.vue.d.ts +3 -0
  21. package/dist/types/components/BaseIcon/icons/index.d.ts +4 -0
  22. package/dist/types/components/BaseSelect/BaseSelect.vue.d.ts +26 -0
  23. package/dist/types/components/Calendar/Calendar.vue.d.ts +2526 -0
  24. package/dist/types/components/Calendar/CalendarDay.vue.d.ts +83 -0
  25. package/dist/types/components/Calendar/CalendarDayPopover.vue.d.ts +3 -0
  26. package/dist/types/components/Calendar/CalendarHeader.vue.d.ts +13 -0
  27. package/dist/types/components/Calendar/CalendarNav.vue.d.ts +4 -0
  28. package/dist/types/components/Calendar/CalendarNavPopover.vue.d.ts +3 -0
  29. package/dist/types/components/Calendar/CalendarPage.vue.d.ts +3 -0
  30. package/dist/types/components/Calendar/CalendarPageProvider.vue.d.ts +17 -0
  31. package/dist/types/components/Calendar/CalendarSlot.vue.d.ts +15 -0
  32. package/dist/types/components/Calendar/CalendarTitleSelect.vue.d.ts +15 -0
  33. package/dist/types/components/CalendarGrid/CalendarCell.vue.d.ts +9 -0
  34. package/dist/types/components/CalendarGrid/CalendarCellPopover.vue.d.ts +209 -0
  35. package/dist/types/components/CalendarGrid/CalendarDayCell.vue.d.ts +8 -0
  36. package/dist/types/components/CalendarGrid/CalendarEventDetails.vue.d.ts +15 -0
  37. package/dist/types/components/CalendarGrid/CalendarEventEdit.vue.d.ts +26 -0
  38. package/dist/types/components/CalendarGrid/CalendarGrid.vue.d.ts +134 -0
  39. package/dist/types/components/CalendarGrid/CalendarGridWeek.vue.d.ts +8 -0
  40. package/dist/types/components/CalendarGrid/CalendarViewSelect.vue.d.ts +3 -0
  41. package/dist/types/components/CalendarGrid/CalendarWeekCell.vue.d.ts +10 -0
  42. package/dist/types/components/CalendarGrid/Constraints.d.ts +16 -0
  43. package/dist/types/components/DatePicker/DatePicker.vue.d.ts +15251 -0
  44. package/dist/types/components/DatePicker/DatePickerBase.vue.d.ts +3 -0
  45. package/dist/types/components/DatePicker/DatePickerPopover.vue.d.ts +3 -0
  46. package/dist/types/components/DatePicker/TimePicker.vue.d.ts +10089 -0
  47. package/dist/types/components/Popover/Popover.vue.d.ts +72 -0
  48. package/dist/types/components/Popover/PopoverRow.vue.d.ts +17 -0
  49. package/dist/types/components/index.d.ts +4 -0
  50. package/dist/types/index.d.cts +40981 -0
  51. package/dist/types/index.d.mts +40981 -0
  52. package/dist/types/index.d.ts +40981 -0
  53. package/dist/types/use/base.d.ts +67 -0
  54. package/dist/types/use/calendar.d.ts +2422 -0
  55. package/dist/types/use/calendarGrid.d.ts +9370 -0
  56. package/dist/types/use/datePicker.d.ts +20210 -0
  57. package/dist/types/use/page.d.ts +20 -0
  58. package/dist/types/use/slots.d.ts +5 -0
  59. package/dist/types/use/timePicker.d.ts +10089 -0
  60. package/dist/types/utils/attribute.d.ts +49 -0
  61. package/dist/types/utils/cache.d.ts +10 -0
  62. package/dist/types/utils/calendar/event.d.ts +111 -0
  63. package/dist/types/utils/config/index.d.ts +5 -0
  64. package/dist/types/utils/date/helpers.d.ts +161 -0
  65. package/dist/types/utils/date/range.d.ts +74 -0
  66. package/dist/types/utils/date/repeat.d.ts +34 -0
  67. package/dist/types/utils/date/rules.d.ts +82 -0
  68. package/dist/types/utils/defaults/index.d.ts +30 -0
  69. package/dist/types/utils/defaults/locales.d.ts +9 -0
  70. package/dist/types/utils/glyph.d.ts +58 -0
  71. package/dist/types/utils/helpers.d.ts +49 -0
  72. package/dist/types/utils/locale.d.ts +48 -0
  73. package/dist/types/utils/page.d.ts +95 -0
  74. package/dist/types/utils/plugins/index.d.ts +4 -0
  75. package/dist/types/utils/popovers.d.ts +47 -0
  76. package/dist/types/utils/theme.d.ts +17 -0
  77. package/dist/types/utils/touch.d.ts +8 -0
  78. package/dist/types/utils/useDisplayMode.d.ts +12 -0
  79. package/dist/types/utils/watchers.d.ts +6 -0
  80. package/package.json +115 -0
  81. package/src/components/BaseIcon/BaseIcon.vue +29 -0
  82. package/src/components/BaseIcon/icons/IconChevronDown.vue +5 -0
  83. package/src/components/BaseIcon/icons/IconChevronLeft.vue +5 -0
  84. package/src/components/BaseIcon/icons/IconChevronRight.vue +5 -0
  85. package/src/components/BaseIcon/icons/IconClock.vue +11 -0
  86. package/src/components/BaseIcon/icons/index.ts +4 -0
  87. package/src/components/BaseSelect/BaseSelect.vue +136 -0
  88. package/src/components/Calendar/Calendar.vue +116 -0
  89. package/src/components/Calendar/CalendarDay.vue +422 -0
  90. package/src/components/Calendar/CalendarDayPopover.vue +44 -0
  91. package/src/components/Calendar/CalendarHeader.vue +203 -0
  92. package/src/components/Calendar/CalendarNav.vue +325 -0
  93. package/src/components/Calendar/CalendarNavPopover.vue +24 -0
  94. package/src/components/Calendar/CalendarPage.vue +140 -0
  95. package/src/components/Calendar/CalendarPageProvider.vue +15 -0
  96. package/src/components/Calendar/CalendarSlot.vue +36 -0
  97. package/src/components/Calendar/CalendarTitleSelect.vue +72 -0
  98. package/src/components/CalendarGrid/CalendarCell.vue +112 -0
  99. package/src/components/CalendarGrid/CalendarCellPopover.vue +93 -0
  100. package/src/components/CalendarGrid/CalendarDayCell.vue +57 -0
  101. package/src/components/CalendarGrid/CalendarEventDetails.vue +71 -0
  102. package/src/components/CalendarGrid/CalendarEventEdit.vue +195 -0
  103. package/src/components/CalendarGrid/CalendarGrid.vue +715 -0
  104. package/src/components/CalendarGrid/CalendarGridWeek.vue +60 -0
  105. package/src/components/CalendarGrid/CalendarViewSelect.vue +34 -0
  106. package/src/components/CalendarGrid/CalendarWeekCell.vue +52 -0
  107. package/src/components/CalendarGrid/Constraints.ts +40 -0
  108. package/src/components/DatePicker/DatePicker.vue +27 -0
  109. package/src/components/DatePicker/DatePickerBase.vue +53 -0
  110. package/src/components/DatePicker/DatePickerPopover.vue +37 -0
  111. package/src/components/DatePicker/TimePicker.vue +167 -0
  112. package/src/components/Popover/Popover.vue +572 -0
  113. package/src/components/Popover/PopoverRow.vue +94 -0
  114. package/src/components/index.ts +4 -0
  115. package/src/index.ts +24 -0
  116. package/src/styles/index.css +48 -0
  117. package/src/styles/theme.css +322 -0
  118. package/src/styles/transitions.css +77 -0
  119. package/src/use/base.ts +144 -0
  120. package/src/use/calendar.ts +796 -0
  121. package/src/use/calendarGrid.ts +954 -0
  122. package/src/use/datePicker.ts +909 -0
  123. package/src/use/page.ts +78 -0
  124. package/src/use/slots.ts +17 -0
  125. package/src/use/timePicker.ts +194 -0
  126. package/src/utils/attribute.ts +107 -0
  127. package/src/utils/cache.ts +30 -0
  128. package/src/utils/calendar/event.ts +335 -0
  129. package/src/utils/config/index.ts +11 -0
  130. package/src/utils/date/helpers.ts +1039 -0
  131. package/src/utils/date/range.ts +274 -0
  132. package/src/utils/date/repeat.ts +135 -0
  133. package/src/utils/date/rules.ts +290 -0
  134. package/src/utils/defaults/index.ts +77 -0
  135. package/src/utils/defaults/locales.ts +123 -0
  136. package/src/utils/defaults/masks.json +15 -0
  137. package/src/utils/defaults/touch.json +5 -0
  138. package/src/utils/glyph.ts +305 -0
  139. package/src/utils/helpers.ts +181 -0
  140. package/src/utils/locale.ts +275 -0
  141. package/src/utils/page.ts +608 -0
  142. package/src/utils/plugins/index.ts +24 -0
  143. package/src/utils/popovers.ts +216 -0
  144. package/src/utils/theme.ts +58 -0
  145. package/src/utils/touch.ts +72 -0
  146. package/src/utils/useDisplayMode.ts +198 -0
  147. package/src/utils/watchers.ts +26 -0
@@ -0,0 +1,909 @@
1
+ import {
2
+ type SetupContext,
3
+ type ExtractPropTypes,
4
+ type PropType,
5
+ ref,
6
+ computed,
7
+ watch,
8
+ onMounted,
9
+ onUnmounted,
10
+ nextTick,
11
+ toRef,
12
+ inject,
13
+ provide,
14
+ } from 'vue';
15
+ import Calendar from '../components/Calendar/Calendar.vue';
16
+ import Popover from '../components/Popover/Popover.vue';
17
+ import { getDefault } from '../utils/defaults';
18
+ import type { AttributeConfig } from '../utils/attribute';
19
+ import { type CalendarDay, getPageAddressForDate } from '../utils/page';
20
+ import {
21
+ defaultsDeep,
22
+ isArray,
23
+ isDate,
24
+ isNumber,
25
+ isObject,
26
+ isString,
27
+ } from '../utils/helpers';
28
+ import {
29
+ type DatePatch,
30
+ type DateParts,
31
+ type DatePartsRules,
32
+ type DateSource,
33
+ type SimpleDateParts,
34
+ isDateParts,
35
+ } from '../utils/date/helpers';
36
+ import type { SimpleDateRange } from '../utils/date/range';
37
+ import {
38
+ type PopoverOptions,
39
+ showPopover as sp,
40
+ hidePopover as hp,
41
+ togglePopover as tp,
42
+ getPopoverEventHandlers,
43
+ } from '../utils/popovers';
44
+ import { propsDef as basePropsDef, createBase } from './base';
45
+ import type { MoveTarget, MoveOptions } from './calendar';
46
+ import { provideSlots } from './slots';
47
+
48
+ export type DateType = 'date' | 'string' | 'number';
49
+
50
+ export interface DateConfig {
51
+ type: DateType;
52
+ rules: DatePartsRules;
53
+ mask?: string;
54
+ }
55
+
56
+ const contextKey = Symbol('__vc_date_picker_context__');
57
+
58
+ export type DateModes = 'date' | 'datetime' | 'time';
59
+
60
+ export type ValueTarget = 'start' | 'end';
61
+
62
+ export interface UpdateOptions {
63
+ config: any;
64
+ patch: DatePatch;
65
+ debounce: number;
66
+ clearIfEqual: boolean;
67
+ formatInput: boolean;
68
+ hidePopover: boolean;
69
+ dragging: boolean;
70
+ targetPriority: ValueTarget;
71
+ moveToValue: boolean;
72
+ }
73
+
74
+ export interface ModelModifiers {
75
+ number?: boolean;
76
+ string?: boolean;
77
+ range?: boolean;
78
+ }
79
+
80
+ export type DatePickerDate = DateSource | Partial<SimpleDateParts> | null;
81
+ export type DatePickerRangeArray = [DatePickerDate, DatePickerDate];
82
+ export type DatePickerRangeObject = {
83
+ start: Exclude<DatePickerDate, null>;
84
+ end: Exclude<DatePickerDate, null>;
85
+ };
86
+ export type DatePickerModel = DatePickerDate | DatePickerRangeObject;
87
+
88
+ export type DatePickerContext = ReturnType<typeof createDatePicker>;
89
+
90
+ export type DatePickerProps = Readonly<ExtractPropTypes<typeof propsDef>>;
91
+
92
+ export const propsDef = {
93
+ ...basePropsDef,
94
+ mode: { type: String, default: 'date' },
95
+ modelValue: {
96
+ type: [Number, String, Date, Object] as PropType<DatePickerModel>,
97
+ },
98
+ modelModifiers: {
99
+ type: Object as PropType<ModelModifiers>,
100
+ default: () => ({}),
101
+ },
102
+ rules: [String, Object] as PropType<'auto' | DatePartsRules>,
103
+ is24hr: Boolean,
104
+ hideTimeHeader: Boolean,
105
+ timeAccuracy: { type: Number, default: 2 },
106
+ isRequired: Boolean,
107
+ isRange: Boolean,
108
+ updateOnInput: {
109
+ type: Boolean,
110
+ default: () => getDefault('datePicker.updateOnInput'),
111
+ },
112
+ inputDebounce: {
113
+ type: Number,
114
+ default: () => getDefault('datePicker.inputDebounce'),
115
+ },
116
+ popover: {
117
+ type: [Boolean, Object] as PropType<boolean | Partial<PopoverOptions>>,
118
+ default: true,
119
+ },
120
+ dragAttribute: Object as PropType<AttributeConfig>,
121
+ selectAttribute: Object as PropType<AttributeConfig>,
122
+ attributes: [Object, Array],
123
+ };
124
+
125
+ export const emits = [
126
+ 'update:modelValue',
127
+ 'drag',
128
+ 'dayclick',
129
+ 'daykeydown',
130
+ 'popover-will-show',
131
+ 'popover-did-show',
132
+ 'popover-will-hide',
133
+ 'popover-did-hide',
134
+ ];
135
+
136
+ export function createDatePicker(
137
+ props: DatePickerProps,
138
+ { emit, slots }: SetupContext<string[]>,
139
+ ) {
140
+ provideSlots(slots, { footer: 'dp-footer' });
141
+
142
+ const baseCtx = createBase(props);
143
+ const { locale, masks, disabledAttribute } = baseCtx;
144
+
145
+ const showCalendar = ref(false);
146
+ const datePickerPopoverId = ref(Symbol());
147
+ const dateValue = ref<null | Date | SimpleDateRange>(null);
148
+ const dragValue = ref<null | SimpleDateRange>(null);
149
+ const inputValues = ref<string[]>(['', '']);
150
+ const popoverRef = ref<InstanceType<typeof Popover> | null>(null);
151
+ const calendarRef = ref<InstanceType<typeof Calendar> | null>(null);
152
+
153
+ let updateTimeout: ReturnType<typeof setTimeout> | undefined;
154
+ let dragTrackingValue: null | SimpleDateRange;
155
+ let watchValue = true;
156
+
157
+ // #region Computed
158
+
159
+ const isRange = computed(() => {
160
+ return props.isRange || props.modelModifiers.range === true;
161
+ });
162
+
163
+ const valueStart = computed(() =>
164
+ isRange.value && dateValue.value != null
165
+ ? (dateValue.value as SimpleDateRange).start
166
+ : null,
167
+ );
168
+
169
+ const valueEnd = computed(() =>
170
+ isRange.value && dateValue.value != null
171
+ ? (dateValue.value as SimpleDateRange).end
172
+ : null,
173
+ );
174
+
175
+ const isDateMode = computed(() => props.mode.toLowerCase() === 'date');
176
+ const isDateTimeMode = computed(
177
+ () => props.mode.toLowerCase() === 'datetime',
178
+ );
179
+ const isTimeMode = computed(() => props.mode.toLowerCase() === 'time');
180
+
181
+ const isDragging = computed(() => !!dragValue.value);
182
+
183
+ const modelConfig = computed(() => {
184
+ let type: DateType = 'date';
185
+ if (props.modelModifiers.number) type = 'number';
186
+ if (props.modelModifiers.string) type = 'string';
187
+ const mask = masks.value.modelValue || 'iso';
188
+ return normalizeConfig(<DateConfig>{ type, mask });
189
+ });
190
+
191
+ const dateParts = computed(() =>
192
+ getDateParts(dragValue.value ?? dateValue.value),
193
+ );
194
+
195
+ const inputMask = computed(() => {
196
+ if (isTimeMode.value) {
197
+ return props.is24hr ? masks.value.inputTime24hr : masks.value.inputTime;
198
+ }
199
+ if (isDateTimeMode.value) {
200
+ return props.is24hr
201
+ ? masks.value.inputDateTime24hr
202
+ : masks.value.inputDateTime;
203
+ }
204
+ return masks.value.input;
205
+ });
206
+
207
+ const inputMaskHasTime = computed(() => /[Hh]/g.test(inputMask.value));
208
+
209
+ const inputMaskHasDate = computed(() =>
210
+ /[dD]{1,2}|Do|W{1,4}|M{1,4}|YY(?:YY)?/g.test(inputMask.value),
211
+ );
212
+
213
+ const inputMaskPatch = computed(() => {
214
+ if (inputMaskHasTime.value && inputMaskHasDate.value) {
215
+ return 'dateTime';
216
+ }
217
+ if (inputMaskHasDate.value) return 'date';
218
+ if (inputMaskHasTime.value) return 'time';
219
+ return undefined;
220
+ });
221
+
222
+ const popover = computed(() => {
223
+ const target = popoverRef.value?.$el.previousElementSibling ?? undefined;
224
+ return defaultsDeep({}, props.popover, getDefault('datePicker.popover'), {
225
+ target,
226
+ }) as Partial<PopoverOptions>;
227
+ });
228
+
229
+ const popoverEvents = computed(() =>
230
+ getPopoverEventHandlers({
231
+ ...popover.value,
232
+ id: datePickerPopoverId.value,
233
+ }),
234
+ );
235
+
236
+ const inputValue = computed(() => {
237
+ return isRange.value
238
+ ? {
239
+ start: inputValues.value[0],
240
+ end: inputValues.value[1],
241
+ }
242
+ : inputValues.value[0];
243
+ });
244
+
245
+ const inputEvents = computed(() => {
246
+ const events = (['start', 'end'] as const).map(target => ({
247
+ input: onInputInput(target),
248
+ change: onInputChange(target),
249
+ keyup: onInputKeyup,
250
+ ...(props.popover && popoverEvents.value),
251
+ }));
252
+ return isRange.value
253
+ ? {
254
+ start: events[0],
255
+ end: events[1],
256
+ }
257
+ : events[0];
258
+ });
259
+
260
+ const selectAttribute = computed(() => {
261
+ if (!hasValue(dateValue.value)) return null;
262
+ const attribute = {
263
+ key: 'select-drag',
264
+ ...props.selectAttribute,
265
+ dates: dateValue.value,
266
+ pinPage: true,
267
+ };
268
+ const { dot, bar, highlight, content } = attribute;
269
+ if (!dot && !bar && !highlight && !content) {
270
+ attribute.highlight = true;
271
+ }
272
+ return attribute;
273
+ });
274
+
275
+ const dragAttribute = computed(() => {
276
+ if (!isRange.value || !hasValue(dragValue.value)) {
277
+ return null;
278
+ }
279
+ const attribute = {
280
+ key: 'select-drag',
281
+ ...props.dragAttribute,
282
+ dates: dragValue.value,
283
+ };
284
+ const { dot, bar, highlight, content } = attribute;
285
+ if (!dot && !bar && !highlight && !content) {
286
+ attribute.highlight = {
287
+ startEnd: {
288
+ fillMode: 'outline',
289
+ },
290
+ };
291
+ }
292
+ return attribute;
293
+ });
294
+
295
+ const attributes = computed(() => {
296
+ const attrs = isArray(props.attributes) ? [...props.attributes] : [];
297
+ if (dragAttribute.value) {
298
+ attrs.unshift(dragAttribute.value);
299
+ } else if (selectAttribute.value) {
300
+ attrs.unshift(selectAttribute.value);
301
+ }
302
+ return attrs;
303
+ });
304
+
305
+ const rules = computed(() => {
306
+ return normalizeConfig(
307
+ props.rules === 'auto' ? getAutoRules() : (props.rules ?? {}),
308
+ );
309
+ });
310
+
311
+ // #endregion Computed
312
+
313
+ function getAutoRules() {
314
+ const _rules = {
315
+ ms: [0, 999],
316
+ sec: [0, 59],
317
+ min: [0, 59],
318
+ hr: [0, 23],
319
+ };
320
+ const accuracy = isDateMode.value ? 0 : props.timeAccuracy;
321
+ return [0, 1].map(i => {
322
+ switch (accuracy) {
323
+ case 0:
324
+ return {
325
+ hours: _rules.hr[i],
326
+ minutes: _rules.min[i],
327
+ seconds: _rules.sec[i],
328
+ milliseconds: _rules.ms[i],
329
+ };
330
+ case 1:
331
+ return {
332
+ minutes: _rules.min[i],
333
+ seconds: _rules.sec[i],
334
+ milliseconds: _rules.ms[i],
335
+ };
336
+ case 3:
337
+ return { milliseconds: _rules.ms[i] };
338
+ case 4:
339
+ return {};
340
+ default:
341
+ return { seconds: _rules.sec[i], milliseconds: _rules.ms[i] };
342
+ }
343
+ });
344
+ }
345
+
346
+ function normalizeConfig<T>(config: T | T[]): T[] {
347
+ if (isArray(config)) {
348
+ if (config.length === 1) return [config[0], config[0]];
349
+ return config;
350
+ }
351
+ return [config, config];
352
+ }
353
+
354
+ function normalizeDateConfig(
355
+ config: Partial<DateConfig> | Partial<DateConfig>[],
356
+ ): DateConfig[] {
357
+ return normalizeConfig(config).map(
358
+ (c, i) =>
359
+ ({
360
+ ...c,
361
+ rules: rules.value[i],
362
+ }) as DateConfig,
363
+ );
364
+ }
365
+
366
+ function hasDateValue(
367
+ value: DatePickerDate,
368
+ ): value is Exclude<DatePickerDate, null> {
369
+ if (value == null) return false;
370
+ if (isNumber(value)) return !isNaN(value);
371
+ if (isDate(value)) return !isNaN(value.getTime());
372
+ if (isString(value)) return value !== '';
373
+ return isDateParts(value);
374
+ }
375
+
376
+ function hasRangeValue(value: unknown): value is DatePickerRangeObject {
377
+ return (
378
+ isObject(value) &&
379
+ 'start' in value &&
380
+ 'end' in value &&
381
+ hasDateValue(value.start ?? null) &&
382
+ hasDateValue(value.end ?? null)
383
+ );
384
+ }
385
+
386
+ function hasValue(
387
+ value: DatePickerModel,
388
+ ): value is Exclude<DatePickerDate, null> | DatePickerRangeObject {
389
+ return hasRangeValue(value) || hasDateValue(value);
390
+ }
391
+
392
+ function valuesAreEqual(
393
+ a: null | Date | SimpleDateRange,
394
+ b: null | Date | SimpleDateRange,
395
+ ): boolean {
396
+ if (a == null && b == null) return true;
397
+ if (a == null || b == null) return false;
398
+ const aIsDate = isDate(a);
399
+ const bIsDate = isDate(b);
400
+ if (aIsDate && bIsDate) return a.getTime() === b.getTime();
401
+ if (aIsDate || bIsDate) return false;
402
+ return valuesAreEqual(a.start, b.start) && valuesAreEqual(a.end, b.end);
403
+ }
404
+
405
+ function valueIsDisabled(value: Date | SimpleDateRange | null) {
406
+ if (!hasValue(value) || !disabledAttribute.value) return false;
407
+ return disabledAttribute.value.intersectsRange(locale.value.range(value));
408
+ }
409
+
410
+ function normalizeValue(
411
+ value: DatePickerModel,
412
+ config: DateConfig[],
413
+ patch: DatePatch,
414
+ targetPriority?: ValueTarget,
415
+ ): Date | SimpleDateRange | null {
416
+ if (!hasValue(value)) return null;
417
+ if (hasRangeValue(value)) {
418
+ const start = locale.value.toDate(value.start, {
419
+ ...config[0],
420
+ fillDate: valueStart.value ?? undefined,
421
+ patch,
422
+ });
423
+ const end = locale.value.toDate(value.end, {
424
+ ...config[1],
425
+ fillDate: valueEnd.value ?? undefined,
426
+ patch,
427
+ });
428
+ return sortRange({ start, end }, targetPriority);
429
+ }
430
+ return locale.value.toDateOrNull(value, {
431
+ ...config[0],
432
+ fillDate: dateValue.value as Date,
433
+ patch,
434
+ });
435
+ }
436
+
437
+ function denormalizeValue(
438
+ value: null | Date | SimpleDateRange,
439
+ config: DateConfig[],
440
+ ) {
441
+ if (hasRangeValue(value)) {
442
+ return {
443
+ start: locale.value.fromDate(value.start, config[0]),
444
+ end: locale.value.fromDate(value.end, config[1]),
445
+ };
446
+ }
447
+ if (isRange.value) {
448
+ return null;
449
+ }
450
+ return locale.value.fromDate(value, config[0]);
451
+ }
452
+
453
+ function updateValue(
454
+ value: any,
455
+ opts: Partial<UpdateOptions> = {},
456
+ ): Promise<ReturnType<typeof forceUpdateValue>> {
457
+ clearTimeout(updateTimeout);
458
+ return new Promise(resolve => {
459
+ const { debounce = 0, ...args } = opts;
460
+ if (debounce > 0) {
461
+ updateTimeout = setTimeout(() => {
462
+ resolve(forceUpdateValue(value, args));
463
+ }, debounce);
464
+ } else {
465
+ resolve(forceUpdateValue(value, args));
466
+ }
467
+ });
468
+ }
469
+
470
+ function forceUpdateValue(
471
+ value: any,
472
+ {
473
+ config = modelConfig.value,
474
+ patch = 'dateTime',
475
+ clearIfEqual = false,
476
+ formatInput: fInput = true,
477
+ hidePopover: hPopover = false,
478
+ dragging = isDragging.value,
479
+ targetPriority,
480
+ moveToValue: mValue = false,
481
+ }: Partial<UpdateOptions> = {},
482
+ ) {
483
+ // 1. Normalization
484
+ const normalizedConfig = normalizeDateConfig(config);
485
+ let normalizedValue = normalizeValue(
486
+ value,
487
+ normalizedConfig,
488
+ patch,
489
+ targetPriority,
490
+ );
491
+
492
+ // 2a. Validation against disabled dates
493
+ const isDisabled = valueIsDisabled(normalizedValue);
494
+ if (isDisabled) {
495
+ if (dragging) return null;
496
+ normalizedValue = dateValue.value;
497
+ // Don't allow hiding popover
498
+ hPopover = false;
499
+ // 2b. Validation against is-required or clearIfEqual
500
+ } else if (normalizedValue == null && props.isRequired) {
501
+ // Reset to previous value if it was cleared but is required
502
+ normalizedValue = dateValue.value;
503
+ // 2c. Validation against clearIfEqual
504
+ } else if (
505
+ // Clear value if same value was passed
506
+ normalizedValue != null &&
507
+ valuesAreEqual(dateValue.value, normalizedValue) &&
508
+ clearIfEqual
509
+ ) {
510
+ normalizedValue = null;
511
+ }
512
+
513
+ // 3. Assignment
514
+ const valueRef = dragging ? dragValue : dateValue;
515
+ const notify = !valuesAreEqual(valueRef.value, normalizedValue);
516
+ valueRef.value = normalizedValue;
517
+ // Clear drag value if needed
518
+ if (!dragging) dragValue.value = null;
519
+ // Denormalize value using the model config
520
+ const denormalizedValue = denormalizeValue(
521
+ normalizedValue,
522
+ modelConfig.value,
523
+ );
524
+
525
+ // 4. Notification
526
+ if (notify) {
527
+ watchValue = false;
528
+ emit(dragging ? 'drag' : 'update:modelValue', denormalizedValue);
529
+ nextTick(() => (watchValue = true));
530
+ }
531
+
532
+ // 5. Hide popover if needed
533
+ if (hPopover && !dragging) hidePopover();
534
+
535
+ // 6. Format inputs if needed
536
+ if (fInput) formatInput();
537
+
538
+ // 7. Move to range target if needed
539
+ if (mValue) {
540
+ nextTick(() => moveToValue(targetPriority ?? 'start'));
541
+ }
542
+
543
+ return denormalizedValue;
544
+ }
545
+
546
+ function formatInput() {
547
+ nextTick(() => {
548
+ const config = normalizeDateConfig({
549
+ type: 'string',
550
+ mask: inputMask.value,
551
+ });
552
+ const value = denormalizeValue(
553
+ dragValue.value ?? dateValue.value,
554
+ config,
555
+ );
556
+ if (isRange.value) {
557
+ // @ts-ignore
558
+ inputValues.value = [value && value.start, value && value.end];
559
+ } else {
560
+ inputValues.value = [value as string, ''];
561
+ }
562
+ });
563
+ }
564
+
565
+ function onInputUpdate(
566
+ inputValue: string,
567
+ target: ValueTarget,
568
+ opts: Partial<UpdateOptions>,
569
+ ) {
570
+ inputValues.value.splice(target === 'start' ? 0 : 1, 1, inputValue);
571
+ const value = isRange.value
572
+ ? {
573
+ start: inputValues.value[0],
574
+ end: inputValues.value[1] || inputValues.value[0],
575
+ }
576
+ : inputValue;
577
+ const config = {
578
+ type: 'string',
579
+ mask: inputMask.value,
580
+ };
581
+ updateValue(value, {
582
+ ...opts,
583
+ config,
584
+ patch: inputMaskPatch.value,
585
+ targetPriority: target,
586
+ moveToValue: true,
587
+ });
588
+ }
589
+
590
+ function onInputInput(target: ValueTarget) {
591
+ return (e: InputEvent) => {
592
+ if (!props.updateOnInput) return;
593
+ onInputUpdate((<HTMLInputElement>e.currentTarget).value, target, {
594
+ formatInput: false,
595
+ hidePopover: false,
596
+ debounce: props.inputDebounce,
597
+ });
598
+ };
599
+ }
600
+
601
+ function onInputChange(target: ValueTarget) {
602
+ return (e: InputEvent) => {
603
+ onInputUpdate((<HTMLInputElement>e.currentTarget).value, target, {
604
+ formatInput: true,
605
+ hidePopover: false,
606
+ });
607
+ };
608
+ }
609
+
610
+ function onInputKeyup(e: KeyboardEvent) {
611
+ // Escape key only
612
+ if (e.key !== 'Escape') return;
613
+ updateValue(dateValue.value, {
614
+ formatInput: true,
615
+ hidePopover: true,
616
+ });
617
+ }
618
+
619
+ function getDateParts(value: any): (DateParts | null)[] {
620
+ if (isRange.value) {
621
+ return [
622
+ value && value.start ? locale.value.getDateParts(value.start) : null,
623
+ value && value.end ? locale.value.getDateParts(value.end) : null,
624
+ ];
625
+ }
626
+ return [value ? locale.value.getDateParts(value) : null];
627
+ }
628
+
629
+ function cancelDrag() {
630
+ dragValue.value = null;
631
+ formatInput();
632
+ }
633
+
634
+ function onPopoverBeforeShow(el: HTMLElement) {
635
+ emit('popover-will-show', el);
636
+ }
637
+
638
+ function onPopoverAfterShow(el: HTMLElement) {
639
+ emit('popover-did-show', el);
640
+ }
641
+
642
+ function onPopoverBeforeHide(el: HTMLElement) {
643
+ cancelDrag();
644
+ emit('popover-will-hide', el);
645
+ }
646
+
647
+ function onPopoverAfterHide(el: HTMLElement) {
648
+ emit('popover-did-hide', el);
649
+ }
650
+
651
+ function handleDayClick(day: CalendarDay) {
652
+ const opts: Partial<UpdateOptions> = {
653
+ patch: 'date',
654
+ formatInput: true,
655
+ hidePopover: true,
656
+ };
657
+ if (isRange.value) {
658
+ const dragging = !isDragging.value;
659
+ if (dragging) {
660
+ dragTrackingValue = { start: day.startDate, end: day.endDate };
661
+ } else if (dragTrackingValue != null) {
662
+ dragTrackingValue.end = day.date;
663
+ }
664
+ updateValue(dragTrackingValue, {
665
+ ...opts,
666
+ dragging,
667
+ });
668
+ } else {
669
+ updateValue(day.date, {
670
+ ...opts,
671
+ clearIfEqual: !props.isRequired,
672
+ });
673
+ }
674
+ }
675
+
676
+ function onDayClick(day: CalendarDay, event: MouseEvent) {
677
+ handleDayClick(day);
678
+ // Re-emit event
679
+ emit('dayclick', day, event);
680
+ }
681
+
682
+ function onDayKeydown(day: CalendarDay, event: KeyboardEvent) {
683
+ switch (event.key) {
684
+ case ' ':
685
+ case 'Enter': {
686
+ handleDayClick(day);
687
+ event.preventDefault();
688
+ break;
689
+ }
690
+ case 'Escape': {
691
+ hidePopover();
692
+ }
693
+ }
694
+ // Re-emit event
695
+ emit('daykeydown', day, event);
696
+ }
697
+
698
+ function onDayMouseEnter(day: CalendarDay, event: MouseEvent) {
699
+ if (!isDragging.value || dragTrackingValue == null) return;
700
+ dragTrackingValue.end = day.date;
701
+ updateValue(sortRange(dragTrackingValue), {
702
+ patch: 'date',
703
+ formatInput: true,
704
+ });
705
+ }
706
+
707
+ function showPopover(opts: Partial<PopoverOptions> = {}) {
708
+ sp({
709
+ ...popover.value,
710
+ ...opts,
711
+ isInteractive: true,
712
+ id: datePickerPopoverId.value,
713
+ });
714
+ }
715
+
716
+ function hidePopover(opts: Partial<PopoverOptions> = {}) {
717
+ hp({
718
+ hideDelay: 10,
719
+ force: true,
720
+ ...popover.value,
721
+ ...opts,
722
+ id: datePickerPopoverId.value,
723
+ });
724
+ }
725
+
726
+ function togglePopover(opts: Partial<PopoverOptions>) {
727
+ tp({
728
+ ...popover.value,
729
+ ...opts,
730
+ isInteractive: true,
731
+ id: datePickerPopoverId.value,
732
+ });
733
+ }
734
+
735
+ function sortRange(range: SimpleDateRange, priority?: ValueTarget) {
736
+ const { start, end } = range;
737
+ if (start > end) {
738
+ switch (priority) {
739
+ case 'start':
740
+ return { start, end: start };
741
+ case 'end':
742
+ return { start: end, end };
743
+ default:
744
+ return { start: end, end: start };
745
+ }
746
+ }
747
+ return { start, end };
748
+ }
749
+
750
+ async function move(target: MoveTarget, opts: Partial<MoveOptions> = {}) {
751
+ if (calendarRef.value == null) return false;
752
+ return calendarRef.value.move(target, opts);
753
+ }
754
+
755
+ async function moveBy(pages: number, opts: Partial<MoveOptions> = {}) {
756
+ if (calendarRef.value == null) return false;
757
+ return calendarRef.value.moveBy(pages, opts);
758
+ }
759
+
760
+ async function moveToValue(
761
+ target: ValueTarget,
762
+ opts: Partial<MoveOptions> = {},
763
+ ) {
764
+ const dValue = dateValue.value;
765
+ if (calendarRef.value == null || !hasValue(dValue)) return false;
766
+ const start = target !== 'end';
767
+ const position = start ? 1 : -1;
768
+ const date = hasRangeValue(dValue)
769
+ ? start
770
+ ? dValue.start
771
+ : dValue.end
772
+ : dValue;
773
+ const page = getPageAddressForDate(date, 'monthly', locale.value);
774
+ return calendarRef.value.move(page, { position, ...opts });
775
+ }
776
+
777
+ // #endregion Methods
778
+
779
+ // #region Watch
780
+
781
+ watch(
782
+ () => props.isRange,
783
+ val => {
784
+ if (val) {
785
+ console.warn(
786
+ 'The `is-range` prop will be deprecated in future releases. Please use the `range` modifier.',
787
+ );
788
+ }
789
+ },
790
+ { immediate: true },
791
+ );
792
+
793
+ watch(
794
+ () => isRange.value,
795
+ () => {
796
+ forceUpdateValue(null, { formatInput: true });
797
+ },
798
+ );
799
+
800
+ watch(
801
+ () => inputMask.value,
802
+ () => formatInput(),
803
+ );
804
+
805
+ watch(
806
+ () => props.modelValue,
807
+ val => {
808
+ if (!watchValue) return;
809
+ forceUpdateValue(val, {
810
+ formatInput: true,
811
+ hidePopover: false,
812
+ });
813
+ },
814
+ );
815
+
816
+ watch(
817
+ () => rules.value,
818
+ () => {
819
+ if (isObject(props.rules)) {
820
+ forceUpdateValue(props.modelValue, {
821
+ formatInput: true,
822
+ hidePopover: false,
823
+ });
824
+ }
825
+ },
826
+ );
827
+
828
+ watch(
829
+ () => props.timezone,
830
+ () => {
831
+ forceUpdateValue(dateValue.value, { formatInput: true });
832
+ },
833
+ );
834
+
835
+ // #endregion Watch
836
+
837
+ // #region Lifecycle
838
+
839
+ // Set initial date value (no validation applied)
840
+ const config = normalizeConfig(modelConfig.value);
841
+ dateValue.value = normalizeValue(
842
+ props.modelValue ?? null,
843
+ config,
844
+ 'dateTime',
845
+ );
846
+
847
+ onMounted(() => {
848
+ forceUpdateValue(props.modelValue, {
849
+ formatInput: true,
850
+ hidePopover: false,
851
+ });
852
+ });
853
+
854
+ onUnmounted(() => {
855
+ clearTimeout(updateTimeout);
856
+ hidePopover({ hideDelay: 0 });
857
+ });
858
+
859
+ // Created
860
+ // Waiting a tick allows calendar to initialize page
861
+ nextTick(() => (showCalendar.value = true));
862
+
863
+ // #endregion Lifecycle
864
+
865
+ const context = {
866
+ ...baseCtx,
867
+ showCalendar,
868
+ datePickerPopoverId,
869
+ popoverRef,
870
+ popoverEvents,
871
+ calendarRef,
872
+ isRange,
873
+ isTimeMode,
874
+ isDateTimeMode,
875
+ is24hr: toRef(props, 'is24hr'),
876
+ hideTimeHeader: toRef(props, 'hideTimeHeader'),
877
+ timeAccuracy: toRef(props, 'timeAccuracy'),
878
+ isDragging,
879
+ inputValue,
880
+ inputEvents,
881
+ dateParts,
882
+ attributes,
883
+ rules,
884
+ move,
885
+ moveBy,
886
+ moveToValue,
887
+ updateValue,
888
+ showPopover,
889
+ hidePopover,
890
+ togglePopover,
891
+ onDayClick,
892
+ onDayKeydown,
893
+ onDayMouseEnter,
894
+ onPopoverBeforeShow,
895
+ onPopoverAfterShow,
896
+ onPopoverBeforeHide,
897
+ onPopoverAfterHide,
898
+ };
899
+ provide(contextKey, context);
900
+ return context;
901
+ }
902
+
903
+ export function useDatePicker() {
904
+ const context = inject<DatePickerContext>(contextKey);
905
+ if (context) return context;
906
+ throw new Error(
907
+ 'DatePicker context missing. Please verify this component is nested within a valid context provider.',
908
+ );
909
+ }