@rotki/ui-library 2.22.1 → 2.23.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 (105) hide show
  1. package/dist/components/avatars/RuiAvatar.vue_vue_type_script_setup_true_lang.js +1 -1
  2. package/dist/components/avatars/RuiAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  3. package/dist/components/buttons/button/RuiButton.vue.d.ts +1 -1
  4. package/dist/components/buttons/button/button-styles.js +1 -1
  5. package/dist/components/buttons/button/button-styles.js.map +1 -1
  6. package/dist/components/buttons/button-group/RuiButtonGroup.js.map +1 -1
  7. package/dist/components/buttons/button-group/RuiButtonGroup.vue.d.ts +1 -1
  8. package/dist/components/buttons/button-group/RuiButtonGroup.vue_vue_type_script_setup_true_lang.js +1 -1
  9. package/dist/components/buttons/button-group/RuiButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  10. package/dist/components/calendar/RuiCalendar.js.map +1 -1
  11. package/dist/components/calendar/RuiCalendar.vue_vue_type_script_setup_true_lang.js +3 -1
  12. package/dist/components/calendar/RuiCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  13. package/dist/components/calendar/RuiCalendarGrid.js.map +1 -1
  14. package/dist/components/calendar/RuiCalendarGrid.vue_vue_type_script_setup_true_lang.js +121 -25
  15. package/dist/components/calendar/RuiCalendarGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
  16. package/dist/components/calendar/RuiCalendarMenu.vue_vue_type_script_setup_true_lang.js +11 -4
  17. package/dist/components/calendar/RuiCalendarMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  18. package/dist/components/date-time-picker/RuiDateTimePicker.js.map +1 -1
  19. package/dist/components/date-time-picker/RuiDateTimePicker.vue.d.ts +23 -1
  20. package/dist/components/date-time-picker/RuiDateTimePicker.vue_vue_type_script_setup_true_lang.js +136 -24
  21. package/dist/components/date-time-picker/RuiDateTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  22. package/dist/components/date-time-picker/RuiDateTimePickerMenu.js.map +1 -1
  23. package/dist/components/date-time-picker/RuiDateTimePickerMenu.vue.d.ts +7 -0
  24. package/dist/components/date-time-picker/RuiDateTimePickerMenu.vue_vue_type_script_setup_true_lang.js +70 -25
  25. package/dist/components/date-time-picker/RuiDateTimePickerMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  26. package/dist/components/date-time-picker/RuiTimezoneSelect.js.map +1 -1
  27. package/dist/components/date-time-picker/RuiTimezoneSelect.vue_vue_type_script_setup_true_lang.js +7 -3
  28. package/dist/components/date-time-picker/RuiTimezoneSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
  29. package/dist/components/date-time-picker/segment-utils.d.ts +28 -0
  30. package/dist/components/date-time-picker/segment-utils.js +42 -1
  31. package/dist/components/date-time-picker/segment-utils.js.map +1 -1
  32. package/dist/components/date-time-picker/types.d.ts +2 -0
  33. package/dist/components/date-time-picker/types.js.map +1 -1
  34. package/dist/components/date-time-picker/use-date-time-selection.d.ts +6 -0
  35. package/dist/components/date-time-picker/use-date-time-selection.js +65 -42
  36. package/dist/components/date-time-picker/use-date-time-selection.js.map +1 -1
  37. package/dist/components/date-time-picker/use-keyboard-handler.js +4 -3
  38. package/dist/components/date-time-picker/use-keyboard-handler.js.map +1 -1
  39. package/dist/components/date-time-picker/utils.d.ts +5 -0
  40. package/dist/components/date-time-picker/utils.js +12 -1
  41. package/dist/components/date-time-picker/utils.js.map +1 -1
  42. package/dist/components/forms/auto-complete/RuiAutoComplete.vue.d.ts +1 -1
  43. package/dist/components/forms/auto-complete/RuiAutoComplete.vue_vue_type_script_setup_true_lang.js +2 -5
  44. package/dist/components/forms/auto-complete/RuiAutoComplete.vue_vue_type_script_setup_true_lang.js.map +1 -1
  45. package/dist/components/forms/category-picker/RuiCategoryPicker.vue.d.ts +1 -1
  46. package/dist/components/forms/category-picker/RuiCategoryPicker.vue_vue_type_script_setup_true_lang.js +3 -2
  47. package/dist/components/forms/category-picker/RuiCategoryPicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  48. package/dist/components/forms/checkbox/RuiCheckbox.vue.d.ts +1 -1
  49. package/dist/components/forms/checkbox/checkbox-group/RuiCheckboxGroup.vue.d.ts +1 -1
  50. package/dist/components/forms/file-upload/RuiFileUpload.vue_vue_type_script_setup_true_lang.js +2 -1
  51. package/dist/components/forms/file-upload/RuiFileUpload.vue_vue_type_script_setup_true_lang.js.map +1 -1
  52. package/dist/components/forms/radio-button/radio/RuiRadio.vue.d.ts +1 -1
  53. package/dist/components/forms/radio-button/radio-group/RuiRadioGroup.vue.d.ts +1 -1
  54. package/dist/components/forms/select/RuiMenuSelect.vue.d.ts +1 -1
  55. package/dist/components/forms/select/RuiMenuSelect.vue_vue_type_script_setup_true_lang.js +2 -1
  56. package/dist/components/forms/select/RuiMenuSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
  57. package/dist/components/forms/text-area/RuiTextArea.vue_vue_type_script_setup_true_lang.js +2 -2
  58. package/dist/components/forms/text-area/RuiTextArea.vue_vue_type_script_setup_true_lang.js.map +1 -1
  59. package/dist/components/forms/text-field/RuiTextField.vue_vue_type_script_setup_true_lang.js +2 -2
  60. package/dist/components/forms/text-field/RuiTextField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  61. package/dist/components/tables/RuiDataTable.vue.d.ts +1 -1
  62. package/dist/components/tables/RuiTableHead.vue.d.ts +1 -1
  63. package/dist/components/tables/data-table/RuiDataTableBody.vue.d.ts +1 -1
  64. package/dist/components/tables/data-table/RuiDataTableBody.vue_vue_type_script_setup_true_lang.js +2 -8
  65. package/dist/components/tables/data-table/RuiDataTableBody.vue_vue_type_script_setup_true_lang.js.map +1 -1
  66. package/dist/components/tables/data-table/RuiDataTableCell.vue.d.ts +1 -1
  67. package/dist/components/tables/data-table/RuiDataTableExpandedRow.vue.d.ts +1 -1
  68. package/dist/components/tables/data-table/RuiDataTableGroupRow.vue.d.ts +1 -1
  69. package/dist/components/tables/data-table/RuiDataTableMobileSort.vue.d.ts +1 -1
  70. package/dist/components/tables/data-table/RuiDataTableRow.vue.d.ts +1 -1
  71. package/dist/components/tables/data-table/RuiDataTableRow.vue_vue_type_script_setup_true_lang.js +1 -2
  72. package/dist/components/tables/data-table/RuiDataTableRow.vue_vue_type_script_setup_true_lang.js.map +1 -1
  73. package/dist/components/tables/use-pagination-navigation.js.map +1 -1
  74. package/dist/components/tabs/tab-items/RuiTabItems.vue.d.ts +1 -1
  75. package/dist/components/tabs/tabs/use-tab-scroll.js +2 -1
  76. package/dist/components/tabs/tabs/use-tab-scroll.js.map +1 -1
  77. package/dist/composables/forms/auto-complete/search.d.ts +1 -2
  78. package/dist/composables/forms/auto-complete/search.js.map +1 -1
  79. package/dist/composables/forms/auto-complete/value.d.ts +1 -2
  80. package/dist/composables/forms/auto-complete/value.js.map +1 -1
  81. package/dist/i18n/keys.d.ts +10 -0
  82. package/dist/i18n/keys.js +15 -5
  83. package/dist/i18n/keys.js.map +1 -1
  84. package/dist/icons/icons_1.d.ts +1 -1
  85. package/dist/icons/icons_1.js +25 -22
  86. package/dist/icons/icons_1.js.map +1 -1
  87. package/dist/icons/icons_2.d.ts +7 -7
  88. package/dist/icons/icons_2.js +126 -92
  89. package/dist/icons/icons_2.js.map +1 -1
  90. package/dist/icons/icons_3.d.ts +12 -12
  91. package/dist/icons/icons_3.js +119 -177
  92. package/dist/icons/icons_3.js.map +1 -1
  93. package/dist/icons/icons_4.d.ts +26 -26
  94. package/dist/icons/icons_4.js +320 -285
  95. package/dist/icons/icons_4.js.map +1 -1
  96. package/dist/icons/icons_5.d.ts +13 -1
  97. package/dist/icons/icons_5.js +149 -9
  98. package/dist/icons/icons_5.js.map +1 -1
  99. package/dist/icons/index.d.ts +1 -1
  100. package/dist/icons/index.js +34 -22
  101. package/dist/icons/index.js.map +1 -1
  102. package/dist/index.js +7 -7
  103. package/dist/style.css +26 -762
  104. package/dist/web-types.json +75 -10
  105. package/package.json +32 -32
@@ -1,18 +1,25 @@
1
1
  import { tv } from "../../utils/tv.js";
2
2
  import { CalendarStateSymbol, getDaysOfWeek } from "./state.js";
3
- import { Fragment, createElementBlock, createElementVNode, defineComponent, inject, mergeModels, normalizeClass, openBlock, ref, renderList, toDisplayString, unref, useModel, watch, withModifiers } from "vue";
3
+ import { Fragment, computed, createElementBlock, createElementVNode, defineComponent, inject, mergeModels, nextTick, normalizeClass, openBlock, ref, renderList, toDisplayString, unref, useModel, useTemplateRef, watch, withModifiers } from "vue";
4
4
  import { get, isDefined, set } from "@vueuse/core";
5
5
  //#region src/components/calendar/RuiCalendarGrid.vue?vue&type=script&setup=true&lang.ts
6
6
  var _hoisted_1 = { class: "w-full p-2 pt-0" };
7
- var _hoisted_2 = { class: "grid grid-cols-7 pb-2" };
8
- var _hoisted_3 = { class: "grid grid-cols-7 gap-0.5" };
9
- var _hoisted_4 = [
7
+ var _hoisted_2 = {
8
+ role: "row",
9
+ class: "contents"
10
+ };
11
+ var _hoisted_3 = [
10
12
  "data-id",
11
13
  "data-current-month",
12
14
  "data-selected",
13
15
  "data-today",
14
16
  "disabled",
15
- "onClick"
17
+ "tabindex",
18
+ "aria-label",
19
+ "aria-selected",
20
+ "aria-current",
21
+ "onClick",
22
+ "onFocus"
16
23
  ];
17
24
  var RuiCalendarGrid_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
18
25
  name: "RuiCalendarGrid",
@@ -112,40 +119,129 @@ var RuiCalendarGrid_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
112
119
  } else updateModel = new Date(dayData.date);
113
120
  set(model, updateModel);
114
121
  }
122
+ const gridRef = useTemplateRef("grid");
123
+ const activeKey = ref("");
124
+ const dayLabelFormatter = new Intl.DateTimeFormat("en-US", { dateStyle: "full" });
125
+ const KEY_DAY_OFFSETS = {
126
+ ArrowDown: 7,
127
+ ArrowLeft: -1,
128
+ ArrowRight: 1,
129
+ ArrowUp: -7
130
+ };
131
+ const weeks = computed(() => {
132
+ const days = get(calendarDays);
133
+ const result = [];
134
+ for (let i = 0; i < days.length; i += 7) result.push(days.slice(i, i + 7));
135
+ return result;
136
+ });
137
+ function preferredActiveKey() {
138
+ const days = get(calendarDays);
139
+ const selected = days.find((day) => day.isSelected && day.isInRange);
140
+ const todayInView = days.find((day) => day.isToday && day.isCurrentMonth && day.isInRange);
141
+ const firstSelectable = days.find((day) => day.isCurrentMonth && day.isInRange);
142
+ return (selected ?? todayInView ?? firstSelectable ?? days[0])?.key ?? "";
143
+ }
144
+ function syncActiveKey() {
145
+ if (!get(calendarDays).some((day) => day.key === get(activeKey))) set(activeKey, preferredActiveKey());
146
+ }
147
+ function keyToDate(key) {
148
+ const [year, month, day] = key.split("-").map(Number);
149
+ return new Date(year ?? 0, (month ?? 1) - 1, day ?? 1, 12, 0, 0, 0);
150
+ }
151
+ function focusKey(key) {
152
+ set(activeKey, key);
153
+ nextTick(() => {
154
+ get(gridRef)?.querySelector(`[data-id="${key}"]`)?.focus();
155
+ });
156
+ }
157
+ function moveTo(target) {
158
+ const { maxDate, minDate } = calendarState;
159
+ if (!isDateInRangeCheck(target, isDefined(maxDate) ? get(maxDate) : null, isDefined(minDate) ? get(minDate) : null)) return;
160
+ const key = createDateKey(target);
161
+ if (target.getMonth() === __props.viewMonth && target.getFullYear() === __props.viewYear) {
162
+ focusKey(key);
163
+ return;
164
+ }
165
+ set(calendarState.viewMonth, target.getMonth());
166
+ set(calendarState.viewYear, target.getFullYear());
167
+ nextTick(() => {
168
+ focusKey(key);
169
+ });
170
+ }
171
+ function onKeydown(event) {
172
+ const active = get(activeKey);
173
+ if (!active) return;
174
+ const current = keyToDate(active);
175
+ const dayOffset = KEY_DAY_OFFSETS[event.key];
176
+ if (dayOffset !== void 0) {
177
+ event.preventDefault();
178
+ moveTo(new Date(current.getFullYear(), current.getMonth(), current.getDate() + dayOffset, 12, 0, 0, 0));
179
+ } else if (event.key === "Home" || event.key === "End") {
180
+ event.preventDefault();
181
+ const offset = event.key === "Home" ? -current.getDay() : 6 - current.getDay();
182
+ moveTo(new Date(current.getFullYear(), current.getMonth(), current.getDate() + offset, 12, 0, 0, 0));
183
+ } else if (event.key === "PageUp" || event.key === "PageDown") {
184
+ event.preventDefault();
185
+ const delta = event.key === "PageUp" ? -1 : 1;
186
+ moveTo(new Date(current.getFullYear(), current.getMonth() + delta, current.getDate(), 12, 0, 0, 0));
187
+ }
188
+ }
115
189
  calculateCalendarDays();
190
+ syncActiveKey();
116
191
  watch([() => __props.viewMonth, () => __props.viewYear], () => {
117
192
  calculateCalendarDays();
193
+ syncActiveKey();
118
194
  });
119
195
  watch(model, () => {
120
196
  calculateCalendarDays();
197
+ syncActiveKey();
121
198
  });
122
199
  watch([() => calendarState.maxDate, () => calendarState.minDate], () => {
123
200
  calculateCalendarDays();
201
+ syncActiveKey();
124
202
  });
125
203
  return (_ctx, _cache) => {
126
- return openBlock(), createElementBlock("div", _hoisted_1, [createElementVNode("div", _hoisted_2, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(daysOfWeek), (day) => {
204
+ return openBlock(), createElementBlock("div", _hoisted_1, [createElementVNode("div", {
205
+ ref: "grid",
206
+ role: "grid",
207
+ class: "grid grid-cols-7 gap-0.5",
208
+ onKeydown: _cache[0] || (_cache[0] = ($event) => onKeydown($event))
209
+ }, [createElementVNode("div", _hoisted_2, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(daysOfWeek), (day) => {
127
210
  return openBlock(), createElementBlock("span", {
128
211
  key: day,
129
- class: "py-2 text-xs font-medium text-center text-gray-500 dark:text-gray-400"
212
+ role: "columnheader",
213
+ class: "py-2 pb-4 text-xs font-medium text-center text-gray-500 dark:text-gray-400"
130
214
  }, toDisplayString(day), 1);
131
- }), 128))]), createElementVNode("div", _hoisted_3, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(get)(calendarDays.value), (dayData) => {
132
- return openBlock(), createElementBlock("button", {
133
- key: dayData.key,
134
- type: "button",
135
- "data-id": dayData.key,
136
- "data-current-month": dayData.isCurrentMonth || void 0,
137
- "data-selected": dayData.isSelected || void 0,
138
- "data-today": dayData.isToday && !dayData.isSelected || void 0,
139
- class: normalizeClass(unref(dayButton)({
140
- selected: dayData.isSelected,
141
- currentMonth: !dayData.isSelected && dayData.isCurrentMonth,
142
- inRange: dayData.isInRange,
143
- today: dayData.isToday && !dayData.isSelected
144
- })),
145
- disabled: !dayData.isInRange,
146
- onClick: withModifiers(($event) => selectDate(dayData), ["stop"])
147
- }, toDisplayString(dayData.dayNumber), 11, _hoisted_4);
148
- }), 128))])]);
215
+ }), 128))]), (openBlock(true), createElementBlock(Fragment, null, renderList(weeks.value, (week, index) => {
216
+ return openBlock(), createElementBlock("div", {
217
+ key: index,
218
+ role: "row",
219
+ class: "contents"
220
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(week, (dayData) => {
221
+ return openBlock(), createElementBlock("button", {
222
+ key: dayData.key,
223
+ type: "button",
224
+ role: "gridcell",
225
+ "data-id": dayData.key,
226
+ "data-current-month": dayData.isCurrentMonth || void 0,
227
+ "data-selected": dayData.isSelected || void 0,
228
+ "data-today": dayData.isToday && !dayData.isSelected || void 0,
229
+ class: normalizeClass(unref(dayButton)({
230
+ selected: dayData.isSelected,
231
+ currentMonth: !dayData.isSelected && dayData.isCurrentMonth,
232
+ inRange: dayData.isInRange,
233
+ today: dayData.isToday && !dayData.isSelected
234
+ })),
235
+ disabled: !dayData.isInRange,
236
+ tabindex: dayData.key === activeKey.value ? 0 : -1,
237
+ "aria-label": unref(dayLabelFormatter).format(dayData.date),
238
+ "aria-selected": dayData.isSelected,
239
+ "aria-current": dayData.isToday ? "date" : void 0,
240
+ onClick: withModifiers(($event) => selectDate(dayData), ["stop"]),
241
+ onFocus: ($event) => activeKey.value = dayData.key
242
+ }, toDisplayString(dayData.dayNumber), 43, _hoisted_3);
243
+ }), 128))]);
244
+ }), 128))], 544)]);
149
245
  };
150
246
  }
151
247
  });
@@ -1 +1 @@
1
- {"version":3,"file":"RuiCalendarGrid.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/calendar/RuiCalendarGrid.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { get, isDefined, set } from '@vueuse/core';\nimport { inject, ref, watch } from 'vue';\nimport {\n CalendarStateSymbol,\n getDaysOfWeek,\n type RuiCalendarState,\n} from '@/components/calendar/state';\nimport { tv } from '@/utils/tv';\n\ndefineOptions({\n name: 'RuiCalendarGrid',\n inheritAttrs: false,\n});\n\nconst model = defineModel<Date | undefined>();\n\nconst { viewMonth, viewYear } = defineProps<{\n viewMonth: number;\n viewYear: number;\n}>();\n\nconst calendarState = inject<RuiCalendarState>(CalendarStateSymbol) as RuiCalendarState;\n\n// Helper functions to reduce duplication\nfunction createDateKey(date: Date): string {\n return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;\n}\n\n// Pre-calculated constants\nconst daysOfWeek = getDaysOfWeek();\nconst today = new Date();\nconst todayKey = createDateKey(today);\n\nconst dayButton = tv({\n base: 'h-9 w-full flex items-center justify-center text-sm rounded-full mx-auto max-w-[2.25rem] transition-colors duration-150 ease-in-out border-none outline-none cursor-pointer focus:ring-2 focus:ring-rui-primary focus:ring-opacity-50',\n variants: {\n selected: {\n true: 'bg-rui-primary text-white hover:bg-rui-primary/90 dark:hover:bg-rui-primary/90',\n },\n currentMonth: {\n true: 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700',\n false: 'text-gray-400 dark:text-gray-600',\n },\n inRange: {\n false: 'opacity-50 cursor-not-allowed hover:bg-transparent',\n },\n today: {\n true: `relative after:content-[''] after:absolute after:size-1 after:rounded-full after:bottom-1 after:left-1/2 after:-translate-x-1/2 after:bg-rui-primary`,\n },\n },\n compoundVariants: [\n {\n selected: true,\n class: 'text-white dark:text-white',\n },\n ],\n});\n\n// Pre-calculated arrays for performance - using refs for better control\nconst calendarDays = ref<\n Array<{\n date: Date;\n isCurrentMonth: boolean;\n isInRange: boolean;\n isSelected: boolean;\n isToday: boolean;\n key: string;\n dayNumber: number;\n }>\n>([]);\n\nfunction createDayData(\n date: Date,\n isCurrentMonth: boolean,\n modelValue: Date | null | undefined,\n maxDateValue: Date | null,\n minDateValue: Date | null,\n): {\n date: Date;\n isCurrentMonth: boolean;\n isInRange: boolean;\n isSelected: boolean;\n isToday: boolean;\n key: string;\n dayNumber: number;\n} {\n const key = createDateKey(date);\n return {\n date,\n isCurrentMonth,\n isInRange: isDateInRangeCheck(date, maxDateValue, minDateValue),\n isSelected: modelValue ? isDateSelectedCheck(date, modelValue) : false,\n isToday: key === todayKey,\n key,\n dayNumber: date.getDate(),\n };\n}\n\n// Pre-calculation functions using VueUse get/set\nfunction calculateCalendarDays(): void {\n const firstDayOfMonth = new Date(viewYear, viewMonth, 1);\n const lastDayOfMonth = new Date(viewYear, viewMonth + 1, 0);\n const daysInMonth = lastDayOfMonth.getDate();\n const startingDayOfWeek = firstDayOfMonth.getDay();\n\n const { maxDate, minDate } = calendarState;\n const modelValue = get(model);\n const maxDateValue = isDefined(maxDate) ? get(maxDate) : null;\n const minDateValue = isDefined(minDate) ? get(minDate) : null;\n\n const result = [];\n\n // Previous month days\n if (startingDayOfWeek > 0) {\n const prevMonthLastDay = new Date(viewYear, viewMonth, 0).getDate();\n for (let i = startingDayOfWeek - 1; i >= 0; i--) {\n const date = new Date(viewYear, viewMonth - 1, prevMonthLastDay - i, 12, 0, 0, 0);\n result.push(createDayData(date, false, modelValue, maxDateValue, minDateValue));\n }\n }\n\n // Current month days\n for (let i = 1; i <= daysInMonth; i++) {\n const date = new Date(viewYear, viewMonth, i, 12, 0, 0, 0);\n result.push(createDayData(date, true, modelValue, maxDateValue, minDateValue));\n }\n\n // Next month days\n const totalDaysSoFar = result.length;\n const daysToAdd = 42 - totalDaysSoFar;\n for (let i = 1; i <= daysToAdd; i++) {\n const date = new Date(viewYear, viewMonth + 1, i, 12, 0, 0, 0);\n result.push(createDayData(date, false, modelValue, maxDateValue, minDateValue));\n }\n\n set(calendarDays, result);\n}\n\nfunction isDateInRangeCheck(\n date: Date,\n maxDateValue: Date | null,\n minDateValue: Date | null,\n): boolean {\n if (maxDateValue && date > maxDateValue)\n return false;\n if (minDateValue && date < minDateValue)\n return false;\n return true;\n}\n\nfunction isDateSelectedCheck(date: Date, modelValue: Date): boolean {\n return (\n date.getFullYear() === modelValue.getFullYear() &&\n date.getMonth() === modelValue.getMonth() &&\n date.getDate() === modelValue.getDate()\n );\n}\n\nfunction selectDate(dayData: { date: Date; isInRange: boolean; isSelected: boolean }): void {\n if (!dayData.isInRange)\n return;\n\n let updateModel: Date | undefined;\n\n if (dayData.isSelected && calendarState.allowEmpty) {\n updateModel = undefined;\n }\n else if (isDefined(model)) {\n const modelValue = get(model);\n const newDate = new Date(dayData.date);\n newDate.setHours(modelValue.getHours());\n newDate.setMinutes(modelValue.getMinutes());\n newDate.setSeconds(modelValue.getSeconds());\n updateModel = newDate;\n }\n else {\n updateModel = new Date(dayData.date);\n }\n set(model, updateModel);\n}\n\n// Initial calculation\ncalculateCalendarDays();\n\n// Watchers for recalculation only when needed\nwatch([() => viewMonth, () => viewYear], () => {\n calculateCalendarDays();\n});\n\nwatch(model, () => {\n calculateCalendarDays();\n});\n\nwatch(\n [() => calendarState.maxDate, () => calendarState.minDate],\n () => {\n calculateCalendarDays();\n },\n);\n</script>\n\n<template>\n <div class=\"w-full p-2 pt-0\">\n <div class=\"grid grid-cols-7 pb-2\">\n <span\n v-for=\"day in daysOfWeek\"\n :key=\"day\"\n class=\"py-2 text-xs font-medium text-center text-gray-500 dark:text-gray-400\"\n >\n {{ day }}\n </span>\n </div>\n\n <div class=\"grid grid-cols-7 gap-0.5\">\n <button\n v-for=\"dayData in get(calendarDays)\"\n :key=\"dayData.key\"\n type=\"button\"\n :data-id=\"dayData.key\"\n :data-current-month=\"dayData.isCurrentMonth || undefined\"\n :data-selected=\"dayData.isSelected || undefined\"\n :data-today=\"(dayData.isToday && !dayData.isSelected) || undefined\"\n :class=\"dayButton({\n selected: dayData.isSelected,\n currentMonth: !dayData.isSelected && dayData.isCurrentMonth,\n inRange: dayData.isInRange,\n today: dayData.isToday && !dayData.isSelected,\n })\"\n :disabled=\"!dayData.isInRange\"\n @click.stop=\"selectDate(dayData)\"\n >\n {{ dayData.dayNumber }}\n </button>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"RuiCalendarGrid.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/calendar/RuiCalendarGrid.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { get, isDefined, set } from '@vueuse/core';\nimport { computed, inject, nextTick, ref, useTemplateRef, watch } from 'vue';\nimport {\n CalendarStateSymbol,\n getDaysOfWeek,\n type RuiCalendarState,\n} from '@/components/calendar/state';\nimport { tv } from '@/utils/tv';\n\ndefineOptions({\n name: 'RuiCalendarGrid',\n inheritAttrs: false,\n});\n\nconst model = defineModel<Date | undefined>();\n\nconst { viewMonth, viewYear } = defineProps<{\n viewMonth: number;\n viewYear: number;\n}>();\n\nconst calendarState = inject<RuiCalendarState>(CalendarStateSymbol) as RuiCalendarState;\n\n// Helper functions to reduce duplication\nfunction createDateKey(date: Date): string {\n return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;\n}\n\n// Pre-calculated constants\nconst daysOfWeek = getDaysOfWeek();\nconst today = new Date();\nconst todayKey = createDateKey(today);\n\nconst dayButton = tv({\n base: 'h-9 w-full flex items-center justify-center text-sm rounded-full mx-auto max-w-[2.25rem] transition-colors duration-150 ease-in-out border-none outline-none cursor-pointer focus:ring-2 focus:ring-rui-primary focus:ring-opacity-50',\n variants: {\n selected: {\n true: 'bg-rui-primary text-white hover:bg-rui-primary/90 dark:hover:bg-rui-primary/90',\n },\n currentMonth: {\n true: 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700',\n false: 'text-gray-400 dark:text-gray-600',\n },\n inRange: {\n false: 'opacity-50 cursor-not-allowed hover:bg-transparent',\n },\n today: {\n true: `relative after:content-[''] after:absolute after:size-1 after:rounded-full after:bottom-1 after:left-1/2 after:-translate-x-1/2 after:bg-rui-primary`,\n },\n },\n compoundVariants: [\n {\n selected: true,\n class: 'text-white dark:text-white',\n },\n ],\n});\n\n// Pre-calculated arrays for performance - using refs for better control\nconst calendarDays = ref<\n Array<{\n date: Date;\n isCurrentMonth: boolean;\n isInRange: boolean;\n isSelected: boolean;\n isToday: boolean;\n key: string;\n dayNumber: number;\n }>\n>([]);\n\nfunction createDayData(\n date: Date,\n isCurrentMonth: boolean,\n modelValue: Date | null | undefined,\n maxDateValue: Date | null,\n minDateValue: Date | null,\n): {\n date: Date;\n isCurrentMonth: boolean;\n isInRange: boolean;\n isSelected: boolean;\n isToday: boolean;\n key: string;\n dayNumber: number;\n} {\n const key = createDateKey(date);\n return {\n date,\n isCurrentMonth,\n isInRange: isDateInRangeCheck(date, maxDateValue, minDateValue),\n isSelected: modelValue ? isDateSelectedCheck(date, modelValue) : false,\n isToday: key === todayKey,\n key,\n dayNumber: date.getDate(),\n };\n}\n\n// Pre-calculation functions using VueUse get/set\nfunction calculateCalendarDays(): void {\n const firstDayOfMonth = new Date(viewYear, viewMonth, 1);\n const lastDayOfMonth = new Date(viewYear, viewMonth + 1, 0);\n const daysInMonth = lastDayOfMonth.getDate();\n const startingDayOfWeek = firstDayOfMonth.getDay();\n\n const { maxDate, minDate } = calendarState;\n const modelValue = get(model);\n const maxDateValue = isDefined(maxDate) ? get(maxDate) : null;\n const minDateValue = isDefined(minDate) ? get(minDate) : null;\n\n const result = [];\n\n // Previous month days\n if (startingDayOfWeek > 0) {\n const prevMonthLastDay = new Date(viewYear, viewMonth, 0).getDate();\n for (let i = startingDayOfWeek - 1; i >= 0; i--) {\n const date = new Date(viewYear, viewMonth - 1, prevMonthLastDay - i, 12, 0, 0, 0);\n result.push(createDayData(date, false, modelValue, maxDateValue, minDateValue));\n }\n }\n\n // Current month days\n for (let i = 1; i <= daysInMonth; i++) {\n const date = new Date(viewYear, viewMonth, i, 12, 0, 0, 0);\n result.push(createDayData(date, true, modelValue, maxDateValue, minDateValue));\n }\n\n // Next month days\n const totalDaysSoFar = result.length;\n const daysToAdd = 42 - totalDaysSoFar;\n for (let i = 1; i <= daysToAdd; i++) {\n const date = new Date(viewYear, viewMonth + 1, i, 12, 0, 0, 0);\n result.push(createDayData(date, false, modelValue, maxDateValue, minDateValue));\n }\n\n set(calendarDays, result);\n}\n\nfunction isDateInRangeCheck(\n date: Date,\n maxDateValue: Date | null,\n minDateValue: Date | null,\n): boolean {\n if (maxDateValue && date > maxDateValue)\n return false;\n if (minDateValue && date < minDateValue)\n return false;\n return true;\n}\n\nfunction isDateSelectedCheck(date: Date, modelValue: Date): boolean {\n return (\n date.getFullYear() === modelValue.getFullYear() &&\n date.getMonth() === modelValue.getMonth() &&\n date.getDate() === modelValue.getDate()\n );\n}\n\nfunction selectDate(dayData: { date: Date; isInRange: boolean; isSelected: boolean }): void {\n if (!dayData.isInRange)\n return;\n\n let updateModel: Date | undefined;\n\n if (dayData.isSelected && calendarState.allowEmpty) {\n updateModel = undefined;\n }\n else if (isDefined(model)) {\n const modelValue = get(model);\n const newDate = new Date(dayData.date);\n newDate.setHours(modelValue.getHours());\n newDate.setMinutes(modelValue.getMinutes());\n newDate.setSeconds(modelValue.getSeconds());\n updateModel = newDate;\n }\n else {\n updateModel = new Date(dayData.date);\n }\n set(model, updateModel);\n}\n\n// --- keyboard navigation -------------------------------------------------\n// The grid is a roving tabindex: one day is tabbable and the arrows move it,\n// so crossing the calendar costs one Tab instead of 42.\n\nconst gridRef = useTemplateRef<HTMLDivElement>('grid');\nconst activeKey = ref<string>('');\n\nconst dayLabelFormatter = new Intl.DateTimeFormat('en-US', { dateStyle: 'full' });\n\nconst KEY_DAY_OFFSETS: Record<string, number> = {\n ArrowDown: 7,\n ArrowLeft: -1,\n ArrowRight: 1,\n ArrowUp: -7,\n};\n\nconst weeks = computed<Array<typeof calendarDays.value>>(() => {\n const days = get(calendarDays);\n const result = [];\n for (let i = 0; i < days.length; i += 7)\n result.push(days.slice(i, i + 7));\n return result;\n});\n\nfunction preferredActiveKey(): string {\n const days = get(calendarDays);\n const selected = days.find(day => day.isSelected && day.isInRange);\n const todayInView = days.find(day => day.isToday && day.isCurrentMonth && day.isInRange);\n const firstSelectable = days.find(day => day.isCurrentMonth && day.isInRange);\n return (selected ?? todayInView ?? firstSelectable ?? days[0])?.key ?? '';\n}\n\nfunction syncActiveKey(): void {\n const days = get(calendarDays);\n if (!days.some(day => day.key === get(activeKey)))\n set(activeKey, preferredActiveKey());\n}\n\nfunction keyToDate(key: string): Date {\n const [year, month, day] = key.split('-').map(Number);\n return new Date(year ?? 0, (month ?? 1) - 1, day ?? 1, 12, 0, 0, 0);\n}\n\nfunction focusKey(key: string): void {\n set(activeKey, key);\n nextTick(() => {\n get(gridRef)?.querySelector<HTMLButtonElement>(`[data-id=\"${key}\"]`)?.focus();\n });\n}\n\nfunction moveTo(target: Date): void {\n const { maxDate, minDate } = calendarState;\n const maxDateValue = isDefined(maxDate) ? get(maxDate) : null;\n const minDateValue = isDefined(minDate) ? get(minDate) : null;\n\n // min/max bound a contiguous range, so a blocked step means the edge is here\n if (!isDateInRangeCheck(target, maxDateValue, minDateValue))\n return;\n\n const key = createDateKey(target);\n\n // Landing on another month turns the page, even when that day happens to be\n // rendered as a spill day in the current window — otherwise the header and\n // the focused day disagree about which month is being edited.\n if (target.getMonth() === viewMonth && target.getFullYear() === viewYear) {\n focusKey(key);\n return;\n }\n\n set(calendarState.viewMonth, target.getMonth());\n set(calendarState.viewYear, target.getFullYear());\n nextTick(() => {\n focusKey(key);\n });\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n const active = get(activeKey);\n if (!active)\n return;\n\n const current = keyToDate(active);\n const dayOffset = KEY_DAY_OFFSETS[event.key];\n\n if (dayOffset !== undefined) {\n event.preventDefault();\n moveTo(new Date(current.getFullYear(), current.getMonth(), current.getDate() + dayOffset, 12, 0, 0, 0));\n }\n else if (event.key === 'Home' || event.key === 'End') {\n event.preventDefault();\n const offset = event.key === 'Home' ? -current.getDay() : 6 - current.getDay();\n moveTo(new Date(current.getFullYear(), current.getMonth(), current.getDate() + offset, 12, 0, 0, 0));\n }\n else if (event.key === 'PageUp' || event.key === 'PageDown') {\n event.preventDefault();\n const delta = event.key === 'PageUp' ? -1 : 1;\n moveTo(new Date(current.getFullYear(), current.getMonth() + delta, current.getDate(), 12, 0, 0, 0));\n }\n}\n\n// Initial calculation\ncalculateCalendarDays();\nsyncActiveKey();\n\n// Watchers for recalculation only when needed\nwatch([() => viewMonth, () => viewYear], () => {\n calculateCalendarDays();\n syncActiveKey();\n});\n\nwatch(model, () => {\n calculateCalendarDays();\n syncActiveKey();\n});\n\nwatch(\n [() => calendarState.maxDate, () => calendarState.minDate],\n () => {\n calculateCalendarDays();\n syncActiveKey();\n },\n);\n</script>\n\n<template>\n <div class=\"w-full p-2 pt-0\">\n <div\n ref=\"grid\"\n role=\"grid\"\n class=\"grid grid-cols-7 gap-0.5\"\n @keydown=\"onKeydown($event)\"\n >\n <div\n role=\"row\"\n class=\"contents\"\n >\n <span\n v-for=\"day in daysOfWeek\"\n :key=\"day\"\n role=\"columnheader\"\n class=\"py-2 pb-4 text-xs font-medium text-center text-gray-500 dark:text-gray-400\"\n >\n {{ day }}\n </span>\n </div>\n\n <div\n v-for=\"(week, index) in weeks\"\n :key=\"index\"\n role=\"row\"\n class=\"contents\"\n >\n <button\n v-for=\"dayData in week\"\n :key=\"dayData.key\"\n type=\"button\"\n role=\"gridcell\"\n :data-id=\"dayData.key\"\n :data-current-month=\"dayData.isCurrentMonth || undefined\"\n :data-selected=\"dayData.isSelected || undefined\"\n :data-today=\"(dayData.isToday && !dayData.isSelected) || undefined\"\n :class=\"dayButton({\n selected: dayData.isSelected,\n currentMonth: !dayData.isSelected && dayData.isCurrentMonth,\n inRange: dayData.isInRange,\n today: dayData.isToday && !dayData.isSelected,\n })\"\n :disabled=\"!dayData.isInRange\"\n :tabindex=\"dayData.key === activeKey ? 0 : -1\"\n :aria-label=\"dayLabelFormatter.format(dayData.date)\"\n :aria-selected=\"dayData.isSelected\"\n :aria-current=\"dayData.isToday ? 'date' : undefined\"\n @click.stop=\"selectDate(dayData)\"\n @focus=\"activeKey = dayData.key\"\n >\n {{ dayData.dayNumber }}\n </button>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -104,11 +104,12 @@ var RuiCalendarMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
104
104
  set(yearsInRange, new Array(range.length).fill(true));
105
105
  return;
106
106
  }
107
- set(yearsInRange, range.map((year) => {
107
+ const result = range.map((year) => {
108
108
  const date = new Date(year, currentMonth);
109
109
  if (isDefined(maxDate) && date > get(maxDate)) return false;
110
110
  return !isDefined(minDate) || date >= get(minDate);
111
- }));
111
+ });
112
+ set(yearsInRange, result);
112
113
  }
113
114
  function toggleSelection() {
114
115
  set(viewMode, get(viewMode) === "months" ? "years" : "months");
@@ -137,7 +138,10 @@ var RuiCalendarMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
137
138
  month: __props.viewMonth,
138
139
  year: nextYear
139
140
  });
140
- } else set(startYear, get(startYear) + 11 + 1);
141
+ } else {
142
+ const lastYearInRange = get(startYear) + 11;
143
+ set(startYear, lastYearInRange + 1);
144
+ }
141
145
  }
142
146
  function handlePrev() {
143
147
  if (get(viewMode) === "months") {
@@ -146,7 +150,10 @@ var RuiCalendarMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
146
150
  month: __props.viewMonth,
147
151
  year: previousYear
148
152
  });
149
- } else set(startYear, get(startYear) - 12);
153
+ } else {
154
+ const firstYearInRange = get(startYear);
155
+ set(startYear, firstYearInRange - 12);
156
+ }
150
157
  }
151
158
  calculateYearRange();
152
159
  calculateTitle();
@@ -1 +1 @@
1
- {"version":3,"file":"RuiCalendarMenu.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/calendar/RuiCalendarMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { get, isDefined, set } from '@vueuse/core';\nimport { ref, watch } from 'vue';\nimport {\n CalendarStateSymbol,\n getShortMonthNames,\n type MonthYearSelection,\n type RuiCalendarState,\n} from '@/components/calendar/state';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiMenu from '@/components/overlays/menu/RuiMenu.vue';\nimport { type FloatingOptions, Placement } from '@/composables/floating';\nimport { tv } from '@/utils/tv';\nimport RuiButton from '../buttons/button/RuiButton.vue';\n\ndefineOptions({\n name: 'RuiCalendarMenu',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<boolean>();\n\nconst { viewMonth, viewYear } = defineProps<{\n anchorEl: HTMLElement | undefined;\n viewMonth: number;\n viewYear: number;\n}>();\n\nconst emit = defineEmits<{\n select: [MonthYearSelection];\n}>();\n\nconst MENU_OPTIONS: FloatingOptions = { placement: Placement.bottomStart };\n\n// Core reactive state - minimal\nconst viewMode = ref<'months' | 'years'>('months');\nconst startYear = ref<number>(viewYear - 6);\n\n// Pre-calculated constants\nconst months = getShortMonthNames();\nconst calendarState = inject<RuiCalendarState>(CalendarStateSymbol) as RuiCalendarState;\n\nconst navButtonClass = 'p-1 rounded-full text-gray-500 hover:text-rui-primary hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-600 dark:hover:text-rui-primary';\n\nconst cellButton = tv({\n base: 'h-9 w-full flex items-center justify-center text-sm font-medium rounded-md transition-colors duration-150 ease-in-out border-none outline-none cursor-pointer focus:ring-2 focus:ring-rui-primary focus:ring-opacity-50',\n variants: {\n selected: {\n true: 'bg-rui-primary text-white hover:bg-rui-primary-darker active:bg-rui-primary-darker dark:bg-rui-primary dark:text-white dark:hover:bg-rui-primary-darker',\n },\n inRange: {\n true: 'text-gray-700 bg-transparent hover:bg-gray-100 active:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-700 dark:active:bg-gray-600',\n false: 'opacity-50 cursor-not-allowed bg-transparent hover:bg-transparent active:bg-transparent dark:hover:bg-transparent dark:active:bg-transparent pointer-events-none text-gray-400 dark:text-gray-600',\n },\n selectedOutOfRange: {\n true: 'bg-gray-300 text-gray-500 dark:bg-gray-600 dark:text-gray-400',\n },\n },\n});\n\n// Pre-calculated arrays for performance - using refs for better control\nconst yearRange = ref<number[]>([]);\nconst monthsInRange = ref<boolean[]>([]);\nconst yearsInRange = ref<boolean[]>([]);\nconst title = ref<string>('');\nconst canGoToNext = ref<boolean>(true);\nconst canGoToPrev = ref<boolean>(true);\n\n// Pre-calculation functions using VueUse get/set\nfunction calculateYearRange(): void {\n const start = get(startYear);\n set(\n yearRange,\n Array.from({ length: 12 }, (_, i) => start + i),\n );\n}\n\nfunction calculateTitle(): void {\n if (get(viewMode) === 'months') {\n set(title, viewYear.toString());\n }\n else {\n const range = get(yearRange);\n set(title, `${range[0]} - ${range.at(-1)}`);\n }\n}\n\nfunction calculateNavigation(): void {\n const { maxDate, minDate } = calendarState;\n const mode = get(viewMode);\n\n if (mode === 'months') {\n const nextYear = viewYear + 1;\n const previousYear = viewYear - 1;\n\n set(canGoToNext, !isDefined(maxDate) || nextYear <= get(maxDate).getFullYear());\n set(canGoToPrev, !isDefined(minDate) || previousYear >= get(minDate).getFullYear());\n }\n else {\n const lastYearInRange = get(startYear) + 11;\n const firstYearInRange = get(startYear);\n\n // Check if we can navigate to the next range of years\n const nextRangeStart = lastYearInRange + 1;\n set(canGoToNext, !isDefined(maxDate) || nextRangeStart <= get(maxDate).getFullYear());\n\n // Check if we can navigate to the previous range of years\n const prevRangeEnd = firstYearInRange - 1;\n set(canGoToPrev, !isDefined(minDate) || prevRangeEnd >= get(minDate).getFullYear());\n }\n}\n\nfunction calculateMonthsInRange(): void {\n const { minDate, maxDate } = calendarState;\n const currentYear = viewYear;\n const result = new Array(12).fill(true);\n\n if (isDefined(minDate) || isDefined(maxDate)) {\n for (let month = 0; month < 12; month++) {\n const date = new Date(currentYear, month);\n if (isDefined(maxDate) && date > get(maxDate)) {\n result[month] = false;\n }\n if (isDefined(minDate) && date < get(minDate)) {\n result[month] = false;\n }\n }\n }\n\n set(monthsInRange, result);\n}\n\nfunction calculateYearsInRange(): void {\n const { minDate, maxDate } = calendarState;\n const range = get(yearRange);\n const currentMonth = viewMonth;\n\n if (!isDefined(minDate) && !isDefined(maxDate)) {\n set(yearsInRange, new Array(range.length).fill(true));\n return;\n }\n\n const result = range.map((year) => {\n const date = new Date(year, currentMonth);\n if (isDefined(maxDate) && date > get(maxDate)) {\n return false;\n }\n return !isDefined(minDate) || date >= get(minDate);\n });\n\n set(yearsInRange, result);\n}\n\nfunction toggleSelection(): void {\n set(viewMode, get(viewMode) === 'months' ? 'years' : 'months');\n}\n\nfunction selectMonth(month: number): void {\n if (!get(monthsInRange)[month])\n return;\n emit('select', { month, year: viewYear });\n set(modelValue, false);\n}\n\nfunction selectYear(year: number): void {\n const yearIndex = get(yearRange).indexOf(year);\n if (yearIndex === -1 || !get(yearsInRange)[yearIndex])\n return;\n\n emit('select', { month: viewMonth, year });\n set(viewMode, 'months');\n}\n\nfunction handleNext(): void {\n if (get(viewMode) === 'months') {\n const nextYear = viewYear + 1;\n // Emit directly - navigation is already constrained by canGoToNext\n emit('select', { month: viewMonth, year: nextYear });\n }\n else {\n const lastYearInRange = get(startYear) + 11;\n set(startYear, lastYearInRange + 1);\n }\n}\n\nfunction handlePrev(): void {\n if (get(viewMode) === 'months') {\n const previousYear = viewYear - 1;\n // Emit directly - navigation is already constrained by canGoToPrev\n emit('select', { month: viewMonth, year: previousYear });\n }\n else {\n const firstYearInRange = get(startYear);\n set(startYear, firstYearInRange - 12);\n }\n}\n\n// Initial calculations\ncalculateYearRange();\ncalculateTitle();\ncalculateNavigation();\ncalculateMonthsInRange();\ncalculateYearsInRange();\n\n// Watchers for recalculation only when needed\nwatch(startYear, () => {\n calculateYearRange();\n calculateTitle();\n calculateNavigation();\n calculateYearsInRange();\n});\n\nwatch(viewMode, () => {\n calculateTitle();\n calculateNavigation();\n});\n\nwatch(\n () => viewYear,\n () => {\n calculateTitle();\n calculateNavigation();\n calculateMonthsInRange();\n calculateYearsInRange();\n },\n);\n\nwatch(\n () => viewMonth,\n () => {\n calculateYearsInRange();\n },\n);\n</script>\n\n<template>\n <RuiMenu\n v-model=\"modelValue\"\n :anchor-el=\"anchorEl\"\n :options=\"MENU_OPTIONS\"\n >\n <div class=\"w-64 shadow-lg overflow-hidden\">\n <div class=\"flex items-center justify-center p-1 font-medium text-gray-800 dark:text-gray-200 border-b border-rui-grey-200 dark:border-rui-grey-800 cursor-pointer hover:bg-rui-grey-100 dark:hover:bg-rui-grey-800\">\n <RuiButton\n type=\"button\"\n :class=\"navButtonClass\"\n icon\n :disabled=\"!get(canGoToPrev)\"\n variant=\"text\"\n @click.stop=\"handlePrev()\"\n >\n <RuiIcon\n name=\"lu-chevron-left\"\n size=\"20\"\n />\n </RuiButton>\n\n <div\n class=\"flex-1 text-center cursor-pointer\"\n @click.stop=\"toggleSelection()\"\n >\n {{ get(title) }}\n </div>\n\n <RuiButton\n type=\"button\"\n :class=\"navButtonClass\"\n icon\n :disabled=\"!get(canGoToNext)\"\n variant=\"text\"\n @click.stop=\"handleNext()\"\n >\n <RuiIcon\n name=\"lu-chevron-right\"\n size=\"20\"\n />\n </RuiButton>\n </div>\n\n <div\n v-if=\"viewMode === 'months'\"\n class=\"grid grid-cols-3 gap-1 p-2\"\n >\n <button\n v-for=\"(month, index) in months\"\n :key=\"month\"\n type=\"button\"\n :class=\"cellButton({\n selected: index === viewMonth && get(monthsInRange)[index],\n selectedOutOfRange: index === viewMonth && !get(monthsInRange)[index],\n inRange: index !== viewMonth ? get(monthsInRange)[index] : undefined,\n })\"\n :disabled=\"!get(monthsInRange)[index]\"\n @click.stop=\"selectMonth(index)\"\n >\n {{ month.substring(0, 3) }}\n </button>\n </div>\n\n <div\n v-else-if=\"viewMode === 'years'\"\n class=\"grid grid-cols-3 gap-1 p-2\"\n >\n <button\n v-for=\"(year, index) in yearRange\"\n :key=\"year\"\n type=\"button\"\n :class=\"cellButton({\n selected: year === viewYear && get(yearsInRange)[index],\n selectedOutOfRange: year === viewYear && !get(yearsInRange)[index],\n inRange: year !== viewYear ? get(yearsInRange)[index] : undefined,\n })\"\n :disabled=\"!get(yearsInRange)[index]\"\n @click.stop=\"selectYear(year)\"\n >\n {{ year }}\n </button>\n </div>\n </div>\n </RuiMenu>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"RuiCalendarMenu.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/calendar/RuiCalendarMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { get, isDefined, set } from '@vueuse/core';\nimport { ref, watch } from 'vue';\nimport {\n CalendarStateSymbol,\n getShortMonthNames,\n type MonthYearSelection,\n type RuiCalendarState,\n} from '@/components/calendar/state';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiMenu from '@/components/overlays/menu/RuiMenu.vue';\nimport { type FloatingOptions, Placement } from '@/composables/floating';\nimport { tv } from '@/utils/tv';\nimport RuiButton from '../buttons/button/RuiButton.vue';\n\ndefineOptions({\n name: 'RuiCalendarMenu',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<boolean>();\n\nconst { viewMonth, viewYear } = defineProps<{\n anchorEl: HTMLElement | undefined;\n viewMonth: number;\n viewYear: number;\n}>();\n\nconst emit = defineEmits<{\n select: [MonthYearSelection];\n}>();\n\nconst MENU_OPTIONS: FloatingOptions = { placement: Placement.bottomStart };\n\n// Core reactive state - minimal\nconst viewMode = ref<'months' | 'years'>('months');\nconst startYear = ref<number>(viewYear - 6);\n\n// Pre-calculated constants\nconst months = getShortMonthNames();\nconst calendarState = inject<RuiCalendarState>(CalendarStateSymbol) as RuiCalendarState;\n\nconst navButtonClass = 'p-1 rounded-full text-gray-500 hover:text-rui-primary hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-600 dark:hover:text-rui-primary';\n\nconst cellButton = tv({\n base: 'h-9 w-full flex items-center justify-center text-sm font-medium rounded-md transition-colors duration-150 ease-in-out border-none outline-none cursor-pointer focus:ring-2 focus:ring-rui-primary focus:ring-opacity-50',\n variants: {\n selected: {\n true: 'bg-rui-primary text-white hover:bg-rui-primary-darker active:bg-rui-primary-darker dark:bg-rui-primary dark:text-white dark:hover:bg-rui-primary-darker',\n },\n inRange: {\n true: 'text-gray-700 bg-transparent hover:bg-gray-100 active:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-700 dark:active:bg-gray-600',\n false: 'opacity-50 cursor-not-allowed bg-transparent hover:bg-transparent active:bg-transparent dark:hover:bg-transparent dark:active:bg-transparent pointer-events-none text-gray-400 dark:text-gray-600',\n },\n selectedOutOfRange: {\n true: 'bg-gray-300 text-gray-500 dark:bg-gray-600 dark:text-gray-400',\n },\n },\n});\n\n// Pre-calculated arrays for performance - using refs for better control\nconst yearRange = ref<number[]>([]);\nconst monthsInRange = ref<boolean[]>([]);\nconst yearsInRange = ref<boolean[]>([]);\nconst title = ref<string>('');\nconst canGoToNext = ref<boolean>(true);\nconst canGoToPrev = ref<boolean>(true);\n\n// Pre-calculation functions using VueUse get/set\nfunction calculateYearRange(): void {\n const start = get(startYear);\n set(\n yearRange,\n Array.from({ length: 12 }, (_, i) => start + i),\n );\n}\n\nfunction calculateTitle(): void {\n if (get(viewMode) === 'months') {\n set(title, viewYear.toString());\n }\n else {\n const range = get(yearRange);\n set(title, `${range[0]} - ${range.at(-1)}`);\n }\n}\n\nfunction calculateNavigation(): void {\n const { maxDate, minDate } = calendarState;\n const mode = get(viewMode);\n\n if (mode === 'months') {\n const nextYear = viewYear + 1;\n const previousYear = viewYear - 1;\n\n set(canGoToNext, !isDefined(maxDate) || nextYear <= get(maxDate).getFullYear());\n set(canGoToPrev, !isDefined(minDate) || previousYear >= get(minDate).getFullYear());\n }\n else {\n const lastYearInRange = get(startYear) + 11;\n const firstYearInRange = get(startYear);\n\n // Check if we can navigate to the next range of years\n const nextRangeStart = lastYearInRange + 1;\n set(canGoToNext, !isDefined(maxDate) || nextRangeStart <= get(maxDate).getFullYear());\n\n // Check if we can navigate to the previous range of years\n const prevRangeEnd = firstYearInRange - 1;\n set(canGoToPrev, !isDefined(minDate) || prevRangeEnd >= get(minDate).getFullYear());\n }\n}\n\nfunction calculateMonthsInRange(): void {\n const { minDate, maxDate } = calendarState;\n const currentYear = viewYear;\n const result = new Array(12).fill(true);\n\n if (isDefined(minDate) || isDefined(maxDate)) {\n for (let month = 0; month < 12; month++) {\n const date = new Date(currentYear, month);\n if (isDefined(maxDate) && date > get(maxDate)) {\n result[month] = false;\n }\n if (isDefined(minDate) && date < get(minDate)) {\n result[month] = false;\n }\n }\n }\n\n set(monthsInRange, result);\n}\n\nfunction calculateYearsInRange(): void {\n const { minDate, maxDate } = calendarState;\n const range = get(yearRange);\n const currentMonth = viewMonth;\n\n if (!isDefined(minDate) && !isDefined(maxDate)) {\n set(yearsInRange, new Array(range.length).fill(true));\n return;\n }\n\n const result = range.map((year) => {\n const date = new Date(year, currentMonth);\n if (isDefined(maxDate) && date > get(maxDate)) {\n return false;\n }\n return !isDefined(minDate) || date >= get(minDate);\n });\n\n set(yearsInRange, result);\n}\n\nfunction toggleSelection(): void {\n set(viewMode, get(viewMode) === 'months' ? 'years' : 'months');\n}\n\nfunction selectMonth(month: number): void {\n if (!get(monthsInRange)[month])\n return;\n emit('select', { month, year: viewYear });\n set(modelValue, false);\n}\n\nfunction selectYear(year: number): void {\n const yearIndex = get(yearRange).indexOf(year);\n if (yearIndex === -1 || !get(yearsInRange)[yearIndex])\n return;\n\n emit('select', { month: viewMonth, year });\n set(viewMode, 'months');\n}\n\nfunction handleNext(): void {\n if (get(viewMode) === 'months') {\n const nextYear = viewYear + 1;\n // Emit directly - navigation is already constrained by canGoToNext\n emit('select', { month: viewMonth, year: nextYear });\n }\n else {\n const lastYearInRange = get(startYear) + 11;\n set(startYear, lastYearInRange + 1);\n }\n}\n\nfunction handlePrev(): void {\n if (get(viewMode) === 'months') {\n const previousYear = viewYear - 1;\n // Emit directly - navigation is already constrained by canGoToPrev\n emit('select', { month: viewMonth, year: previousYear });\n }\n else {\n const firstYearInRange = get(startYear);\n set(startYear, firstYearInRange - 12);\n }\n}\n\n// Initial calculations\ncalculateYearRange();\ncalculateTitle();\ncalculateNavigation();\ncalculateMonthsInRange();\ncalculateYearsInRange();\n\n// Watchers for recalculation only when needed\nwatch(startYear, () => {\n calculateYearRange();\n calculateTitle();\n calculateNavigation();\n calculateYearsInRange();\n});\n\nwatch(viewMode, () => {\n calculateTitle();\n calculateNavigation();\n});\n\nwatch(\n () => viewYear,\n () => {\n calculateTitle();\n calculateNavigation();\n calculateMonthsInRange();\n calculateYearsInRange();\n },\n);\n\nwatch(\n () => viewMonth,\n () => {\n calculateYearsInRange();\n },\n);\n</script>\n\n<template>\n <RuiMenu\n v-model=\"modelValue\"\n :anchor-el=\"anchorEl\"\n :options=\"MENU_OPTIONS\"\n >\n <div class=\"w-64 shadow-lg overflow-hidden\">\n <div class=\"flex items-center justify-center p-1 font-medium text-gray-800 dark:text-gray-200 border-b border-rui-grey-200 dark:border-rui-grey-800 cursor-pointer hover:bg-rui-grey-100 dark:hover:bg-rui-grey-800\">\n <RuiButton\n type=\"button\"\n :class=\"navButtonClass\"\n icon\n :disabled=\"!get(canGoToPrev)\"\n variant=\"text\"\n @click.stop=\"handlePrev()\"\n >\n <RuiIcon\n name=\"lu-chevron-left\"\n size=\"20\"\n />\n </RuiButton>\n\n <div\n class=\"flex-1 text-center cursor-pointer\"\n @click.stop=\"toggleSelection()\"\n >\n {{ get(title) }}\n </div>\n\n <RuiButton\n type=\"button\"\n :class=\"navButtonClass\"\n icon\n :disabled=\"!get(canGoToNext)\"\n variant=\"text\"\n @click.stop=\"handleNext()\"\n >\n <RuiIcon\n name=\"lu-chevron-right\"\n size=\"20\"\n />\n </RuiButton>\n </div>\n\n <div\n v-if=\"viewMode === 'months'\"\n class=\"grid grid-cols-3 gap-1 p-2\"\n >\n <button\n v-for=\"(month, index) in months\"\n :key=\"month\"\n type=\"button\"\n :class=\"cellButton({\n selected: index === viewMonth && get(monthsInRange)[index],\n selectedOutOfRange: index === viewMonth && !get(monthsInRange)[index],\n inRange: index !== viewMonth ? get(monthsInRange)[index] : undefined,\n })\"\n :disabled=\"!get(monthsInRange)[index]\"\n @click.stop=\"selectMonth(index)\"\n >\n {{ month.substring(0, 3) }}\n </button>\n </div>\n\n <div\n v-else-if=\"viewMode === 'years'\"\n class=\"grid grid-cols-3 gap-1 p-2\"\n >\n <button\n v-for=\"(year, index) in yearRange\"\n :key=\"year\"\n type=\"button\"\n :class=\"cellButton({\n selected: year === viewYear && get(yearsInRange)[index],\n selectedOutOfRange: year === viewYear && !get(yearsInRange)[index],\n inRange: year !== viewYear ? get(yearsInRange)[index] : undefined,\n })\"\n :disabled=\"!get(yearsInRange)[index]\"\n @click.stop=\"selectYear(year)\"\n >\n {{ year }}\n </button>\n </div>\n </div>\n </RuiMenu>\n</template>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RuiDateTimePicker.js","names":[],"sources":["../../../src/components/date-time-picker/RuiDateTimePicker.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { DateTimeSegmentType } from '@/components/date-time-picker/types';\nimport type { TimePickerSelection } from '@/components/time-picker/RuiTimePicker.vue';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport { dateTimePickerStyles, type DateTimePickerVariant } from '@/components/date-time-picker/date-time-picker-styles';\nimport RuiDateTimePickerMenu from '@/components/date-time-picker/RuiDateTimePickerMenu.vue';\nimport { useDateTimeSelection } from '@/components/date-time-picker/use-date-time-selection';\nimport { useInputHandler } from '@/components/date-time-picker/use-input-handler';\nimport { useKeyboardHandler } from '@/components/date-time-picker/use-keyboard-handler';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiMenu from '@/components/overlays/menu/RuiMenu.vue';\nimport { type FloatingOptions, Placement } from '@/composables/floating';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { getNonRootAttrs, getRootAttrs } from '@/utils/helpers';\nimport { cn } from '@/utils/tv';\n\ntype DateFormat = 'year-first' | 'month-first' | 'day-first';\n\ntype DateTimeModelType = 'date' | 'epoch-ms' | 'epoch';\n\ntype ModelValueType<T extends DateTimeModelType> = T extends 'date'\n ? Date | undefined\n : T extends 'epoch-ms'\n ? number | undefined\n : T extends 'epoch'\n ? number | undefined\n : Date | number | undefined;\n\nexport interface RuiDateTimePickerProps {\n minDate?: Date | number;\n maxDate?: Date | number | 'now';\n format?: DateFormat;\n type?: DateTimeModelType;\n accuracy?: 'minute' | 'second' | 'millisecond';\n disabled?: boolean;\n allowEmpty?: boolean;\n readonly?: boolean;\n dense?: boolean;\n label?: string;\n variant?: DateTimePickerVariant;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hideDetails?: boolean;\n required?: boolean;\n}\n\ndefineOptions({\n name: 'RuiDateTimePicker',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<ModelValueType<DateTimeModelType>>({ required: true });\nconst menuOpen = defineModel<boolean>('menuOpen', { default: false });\n\nconst {\n disabled = false,\n readonly = false,\n allowEmpty = false,\n dense = false,\n type = 'epoch-ms',\n hideDetails = false,\n label = 'Pick a date',\n variant = 'default',\n hint,\n maxDate,\n minDate,\n format = 'day-first',\n accuracy = 'minute',\n errorMessages = [],\n successMessages = [],\n required = false,\n} = defineProps<RuiDateTimePickerProps>();\n\ndefineSlots<{\n 'menu-content': () => any;\n}>();\n\nconst MENU_OPTIONS: FloatingOptions = { placement: Placement.bottomStart };\n\nconst baseFormats: Record<DateFormat, string> = {\n 'day-first': 'DD/MM/YYYY HH:mm',\n 'month-first': 'MM/DD/YYYY HH:mm',\n 'year-first': 'YYYY/MM/DD HH:mm',\n};\n\nconst isOpen = ref<boolean>(false);\nconst isHovered = ref<boolean>(false);\nconst cursorPosition = ref<number>(0);\nconst currentValue = ref<number>();\n\nconst textInput = useTemplateRef<HTMLInputElement>('textInput');\nconst activator = useTemplateRef<HTMLDivElement>('activator');\nconst menuWrapperRef = useTemplateRef<HTMLDivElement>('menuWrapperRef');\nconst calendarMenuOpen = ref<boolean>(false);\n\nconst { focused: activatorFocusedWithin } = useFocusWithin(activator);\nconst { focused: menuWrapperFocusedWithin } = useFocusWithin(menuWrapperRef);\nconst { focused: searchInputFocused } = useFocus(textInput);\n\nconst anyFocused = computed<boolean>(() => get(activatorFocusedWithin) || get(menuWrapperFocusedWithin));\n\nconst {\n clear: clearSelection,\n getDateTime,\n internalErrorMessages,\n maxAllowedDate,\n minAllowedDate,\n segmentData,\n selectedDate,\n selectedDay,\n selectedHour,\n selectedMillisecond,\n selectedMinute,\n selectedMonth,\n selectedSecond,\n selectedTime,\n selectedTimezone,\n selectedYear,\n setNow,\n valueSet,\n} = useDateTimeSelection({\n accuracy,\n allowEmpty,\n maxDate,\n minDate,\n modelValue,\n type,\n});\n\nconst { setValue, getCurrent } = useInputHandler(segmentData, currentValue);\n\nconst dateFormat = computed<string>(() => {\n const fmt = baseFormats[format];\n if (accuracy === 'second') {\n return fmt.replace('HH:mm', 'HH:mm:ss');\n }\n else if (accuracy === 'millisecond') {\n return fmt.replace('HH:mm', 'HH:mm:ss.SSS');\n }\n return fmt;\n});\n\nconst {\n clear: clearSegment,\n getCurrentSegment,\n handleBlur,\n handleClick,\n handleFocus,\n handleInput,\n handleInputSelection,\n handleKeyDown,\n handleMouseDown,\n handlePaste,\n setSegment,\n} = useKeyboardHandler({\n accuracy,\n currentValue,\n cursorPosition,\n dateFormat,\n disabled,\n getCurrent,\n getDateTime,\n readonly,\n setValue,\n textInput,\n});\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst isOutlined = computed<boolean>(() => variant === 'outlined');\n\nconst formattedDisplay = computed<string>(() => {\n let result = get(dateFormat);\n\n const replacements = [\n { pattern: 'YYYY', value: getDisplayValue(selectedYear, 4) },\n { pattern: 'MM', value: getDisplayValue(selectedMonth, 2) },\n { pattern: 'DD', value: getDisplayValue(selectedDay, 2) },\n { pattern: 'HH', value: getDisplayValue(selectedHour, 2) },\n { pattern: 'mm', value: getDisplayValue(selectedMinute, 2) },\n { pattern: 'ss', value: getDisplayValue(selectedSecond, 2) },\n { pattern: 'SSS', value: getDisplayValue(selectedMillisecond, 3) },\n ];\n\n for (const { pattern, value } of replacements) {\n if (value !== undefined) {\n result = result.replace(pattern, value);\n }\n }\n\n return result;\n});\n\nconst timeSelection = computed<TimePickerSelection>({\n get() {\n const type = getCurrentSegment()?.type;\n\n if (type === 'mm') {\n return 'minute';\n }\n else if (type === 'ss') {\n return 'second';\n }\n else if (type === 'SSS') {\n return 'millisecond';\n }\n return 'hour';\n },\n set(value: TimePickerSelection) {\n let segmentType: DateTimeSegmentType = 'HH';\n if (value === 'minute') {\n segmentType = 'mm';\n }\n else if (value === 'second') {\n segmentType = 'ss';\n }\n else if (value === 'millisecond') {\n segmentType = 'SSS';\n }\n\n setSegment(segmentType);\n },\n});\n\nconst float = computed<boolean>(() => (get(isOpen) || get(valueSet) || get(searchInputFocused)) && get(isOutlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float) || !label)\n return '';\n return required ? `${label} ﹡` : label;\n});\n\nconst ui = computed<ReturnType<typeof dateTimePickerStyles>>(() => dateTimePickerStyles({\n filled: variant === 'filled',\n outlined: get(isOutlined),\n float: get(float),\n opened: get(isOpen),\n hovered: get(isHovered),\n dense,\n disabled,\n readonly,\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n}));\n\nconst combinedErrorMessages = computed<string[]>(() => {\n if (!errorMessages)\n return get(internalErrorMessages);\n\n const propErrors = Array.isArray(errorMessages) ? errorMessages : [errorMessages];\n return [...propErrors, ...get(internalErrorMessages)];\n});\n\nfunction getDisplayValue(digit: Ref<number | undefined>, padding: number): string | undefined {\n return isDefined(digit) ? get(digit).toString().padStart(padding, '0') : undefined;\n}\n\nasync function setInputFocus(): Promise<void> {\n await nextTick(() => {\n set(searchInputFocused, true);\n });\n}\n\nfunction clear(segmentType?: string): void {\n if (!segmentType) {\n clearSelection();\n set(currentValue, undefined);\n return;\n }\n\n clearSegment(segmentType);\n}\n\nfunction handleInputClick(event: MouseEvent): void {\n // Handle segment selection first, before any DOM changes from menu opening\n handleClick(event);\n // Open menu if not already open\n if (!get(isOpen)) {\n set(isOpen, true);\n }\n}\n\nfunction arrowClicked(event: MouseEvent): void {\n if (get(isOpen)) {\n set(isOpen, false);\n event.stopPropagation();\n }\n}\n\nconst anyMenuOpen = computed<boolean>(() => get(isOpen) || get(calendarMenuOpen));\n\nwatch(anyMenuOpen, (value) => {\n set(menuOpen, value);\n});\n</script>\n\n<template>\n <RuiMenu\n v-model=\"isOpen\"\n v-bind=\"getRootAttrs($attrs, [])\"\n :class=\"ui.wrapper({ class: cn($attrs.class) })\"\n :options=\"MENU_OPTIONS\"\n :dense=\"dense\"\n :hint=\"hint\"\n :disabled=\"disabled\"\n :success-messages=\"successMessages\"\n :error-messages=\"combinedErrorMessages\"\n :close-on-content-click=\"false\"\n :show-details=\"!hideDetails\"\n :persistent=\"calendarMenuOpen\"\n full-width\n disable-auto-focus\n >\n <template #activator=\"{ attrs, open }\">\n <div\n ref=\"activator\"\n :class=\"ui.activator()\"\n v-bind=\"{\n ...getNonRootAttrs($attrs, ['onClick', 'class']),\n ...(readonly ? {} : attrs),\n }\"\n data-id=\"activator\"\n :aria-invalid=\"hasError\"\n :tabindex=\"disabled || readonly ? -1 : 0\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n @click=\"setInputFocus()\"\n >\n <span\n v-if=\"isOutlined && (searchInputFocused || open || valueSet)\"\n data-id=\"label\"\n :class=\"[\n ui.label(),\n { 'pr-2': !valueSet && !open && isOutlined },\n ]\"\n >\n {{ label }}\n <span\n v-if=\"required\"\n data-id=\"required-indicator\"\n :class=\"ui.required()\"\n >\n ﹡\n </span>\n </span>\n\n <span :class=\"ui.iconPrepend()\">\n <RuiIcon\n class=\"text-rui-text-secondary transition\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-calendar-days\"\n />\n </span>\n\n <div :class=\"ui.value()\">\n <input\n ref=\"textInput\"\n :disabled=\"disabled\"\n :value=\"formattedDisplay\"\n class=\"bg-transparent outline-none flex-1 min-w-0\"\n type=\"text\"\n :placeholder=\"dateFormat\"\n :readonly=\"readonly\"\n :aria-invalid=\"hasError\"\n @mousedown=\"handleMouseDown($event)\"\n @focus=\"handleFocus()\"\n @blur=\"handleBlur()\"\n @select=\"handleInputSelection($event)\"\n @click.stop=\"handleInputClick($event)\"\n @keydown=\"handleKeyDown($event)\"\n @paste=\"handlePaste($event)\"\n @input=\"handleInput($event)\"\n />\n </div>\n\n <RuiButton\n v-if=\"allowEmpty && valueSet && !disabled\"\n variant=\"text\"\n icon\n data-id=\"clear-button\"\n size=\"sm\"\n tabindex=\"-1\"\n color=\"error\"\n :class=\"[\n ui.clear(),\n anyFocused && '!visible',\n { 'mr-2': !dense },\n ]\"\n @click.stop.prevent=\"clear()\"\n >\n <RuiIcon\n name=\"lu-x\"\n size=\"18\"\n />\n </RuiButton>\n\n <span\n :class=\"ui.iconWrapper()\"\n data-id=\"append\"\n @click=\"arrowClicked($event)\"\n >\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n </div>\n <fieldset\n v-if=\"isOutlined\"\n :class=\"ui.fieldset()\"\n >\n <legend :class=\"ui.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </template>\n <template #default>\n <RuiDateTimePickerMenu\n ref=\"menuWrapperRef\"\n v-model:selected-date=\"selectedDate\"\n v-model:selected-time=\"selectedTime\"\n v-model:selected-hour=\"selectedHour\"\n v-model:selected-minute=\"selectedMinute\"\n v-model:selected-second=\"selectedSecond\"\n v-model:selected-millisecond=\"selectedMillisecond\"\n v-model:time-selection=\"timeSelection\"\n v-model:selected-timezone=\"selectedTimezone\"\n v-model:calendar-menu-open=\"calendarMenuOpen\"\n :accuracy=\"accuracy\"\n :max-date=\"maxAllowedDate\"\n :min-date=\"minAllowedDate\"\n @set-now=\"setNow()\"\n >\n <slot name=\"menu-content\" />\n </RuiDateTimePickerMenu>\n </template>\n </RuiMenu>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"RuiDateTimePicker.js","names":[],"sources":["../../../src/components/date-time-picker/RuiDateTimePicker.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { ComponentPublicInstance } from 'vue';\nimport type { DateTimePickerAction, DateTimeSegmentType } from '@/components/date-time-picker/types';\nimport type { TimePickerSelection } from '@/components/time-picker/RuiTimePicker.vue';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport { dateTimePickerStyles, type DateTimePickerVariant } from '@/components/date-time-picker/date-time-picker-styles';\nimport RuiDateTimePickerMenu from '@/components/date-time-picker/RuiDateTimePickerMenu.vue';\nimport { useDateTimeSelection } from '@/components/date-time-picker/use-date-time-selection';\nimport { useInputHandler } from '@/components/date-time-picker/use-input-handler';\nimport { useKeyboardHandler } from '@/components/date-time-picker/use-keyboard-handler';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiMenu from '@/components/overlays/menu/RuiMenu.vue';\nimport { type FloatingOptions, Placement } from '@/composables/floating';\nimport { useRuiI8n } from '@/composables/use-rui-i18n';\nimport { RUI_I18N_KEYS } from '@/i18n/keys';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { getNonRootAttrs, getRootAttrs } from '@/utils/helpers';\nimport { cn } from '@/utils/tv';\n\ntype DateFormat = 'year-first' | 'month-first' | 'day-first';\n\ntype DateTimeModelType = 'date' | 'epoch-ms' | 'epoch';\n\ntype ModelValueType<T extends DateTimeModelType> = T extends 'date'\n ? Date | undefined\n : T extends 'epoch-ms'\n ? number | undefined\n : T extends 'epoch'\n ? number | undefined\n : Date | number | undefined;\n\nexport interface RuiDateTimePickerProps {\n minDate?: Date | number;\n maxDate?: Date | number | 'now';\n format?: DateFormat;\n type?: DateTimeModelType;\n accuracy?: 'minute' | 'second' | 'millisecond';\n disabled?: boolean;\n allowEmpty?: boolean;\n readonly?: boolean;\n dense?: boolean;\n label?: string;\n variant?: DateTimePickerVariant;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hideDetails?: boolean;\n required?: boolean;\n /**\n * Renders the timezone selector in the menu. Off by default: the value is\n * emitted as a date or an epoch, so the picked timezone does not survive a\n * round trip and most consumers work in local time.\n */\n showTimezone?: boolean;\n /**\n * Actions rendered in the menu footer. `clear` is only rendered when the\n * picker is `allowEmpty`. Pass an empty array to drop the footer entirely.\n */\n actions?: DateTimePickerAction[];\n /**\n * Focuses the field once it is mounted. The native attribute is ignored for\n * an input inserted into an already loaded document, which is the usual case\n * for a picker revealed by an editor or a dialog.\n */\n autofocus?: boolean;\n}\n\ndefineOptions({\n name: 'RuiDateTimePicker',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<ModelValueType<DateTimeModelType>>({ required: true });\nconst menuOpen = defineModel<boolean>('menuOpen', { default: false });\n\nconst {\n disabled = false,\n readonly = false,\n allowEmpty = false,\n dense = false,\n type = 'epoch-ms',\n hideDetails = false,\n label,\n variant = 'default',\n hint,\n maxDate,\n minDate,\n format = 'day-first',\n accuracy = 'minute',\n errorMessages = [],\n successMessages = [],\n required = false,\n showTimezone = false,\n actions = ['now'],\n autofocus = false,\n} = defineProps<RuiDateTimePickerProps>();\n\ndefineSlots<{\n 'menu-content': () => any;\n}>();\n\nconst MENU_OPTIONS: FloatingOptions = { placement: Placement.bottomStart };\n\nconst baseFormats: Record<DateFormat, string> = {\n 'day-first': 'DD/MM/YYYY HH:mm',\n 'month-first': 'MM/DD/YYYY HH:mm',\n 'year-first': 'YYYY/MM/DD HH:mm',\n};\n\nconst isOpen = ref<boolean>(false);\nconst isHovered = ref<boolean>(false);\nconst cursorPosition = ref<number>(0);\nconst currentValue = ref<number>();\n\nconst { t } = useRuiI8n();\n\nconst keys = RUI_I18N_KEYS.dateTimePicker;\n\nconst fieldLabel = computed<string>(() => label ?? t(keys.label, 'Pick a date'));\nconst clearLabel = computed<string>(() => t(keys.clearValue, 'Clear the date'));\nconst toggleLabel = computed<string>(() => (get(isOpen)\n ? t(keys.closeCalendar, 'Close the calendar')\n : t(keys.openCalendar, 'Open the calendar')));\n\nconst textInput = useTemplateRef<HTMLInputElement>('textInput');\nconst activator = useTemplateRef<HTMLDivElement>('activator');\nconst menuWrapperRef = useTemplateRef<ComponentPublicInstance>('menuWrapperRef');\nconst calendarMenuOpen = ref<boolean>(false);\n\nconst { focused: activatorFocusedWithin } = useFocusWithin(activator);\nconst { focused: menuWrapperFocusedWithin } = useFocusWithin(menuWrapperRef);\nconst { focused: searchInputFocused } = useFocus(textInput);\n\nconst anyFocused = computed<boolean>(() => get(activatorFocusedWithin) || get(menuWrapperFocusedWithin));\n\nconst dateFormat = computed<string>(() => {\n const fmt = baseFormats[format];\n if (accuracy === 'second') {\n return fmt.replace('HH:mm', 'HH:mm:ss');\n }\n else if (accuracy === 'millisecond') {\n return fmt.replace('HH:mm', 'HH:mm:ss.SSS');\n }\n return fmt;\n});\n\nconst {\n clear: clearSelection,\n getDateTime,\n internalErrorMessages,\n maxAllowedDate,\n minAllowedDate,\n segmentData,\n selectedDate,\n selectedDay,\n selectedHour,\n selectedMillisecond,\n selectedMinute,\n selectedMonth,\n selectedSecond,\n selectedTime,\n selectedTimezone,\n selectedYear,\n setNow,\n setToday,\n valueSet,\n} = useDateTimeSelection({\n accuracy,\n allowEmpty,\n dateFormat,\n maxDate,\n minDate,\n modelValue,\n type,\n});\n\nconst { setValue, getCurrent } = useInputHandler(segmentData, currentValue);\n\nconst {\n clear: clearSegment,\n getCurrentSegment,\n handleBlur,\n handleClick,\n handleFocus,\n handleInput,\n handleInputSelection,\n handleKeyDown,\n handleMouseDown,\n handlePaste,\n setSegment,\n} = useKeyboardHandler({\n accuracy,\n currentValue,\n cursorPosition,\n dateFormat,\n disabled,\n getCurrent,\n getDateTime,\n readonly,\n setValue,\n textInput,\n});\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst isOutlined = computed<boolean>(() => variant === 'outlined');\n\n/** True once any segment holds a digit, even a partially typed date. */\nconst anySegmentSet = computed<boolean>(() => [\n selectedYear,\n selectedMonth,\n selectedDay,\n selectedHour,\n selectedMinute,\n selectedSecond,\n selectedMillisecond,\n].some(segment => isDefined(segment)));\n\nconst formattedDisplay = computed<string>(() => {\n // An untouched field shows its format through the placeholder rather than\n // holding the tokens as its value, where a screen reader reads them as\n // content and select-all copies them. The tokens stay while the field is\n // focused: that is when the segment machinery highlights them, and\n // collapsing the value mid-edit would blank the field under the cursor.\n // The guard is \"no segment set\" and not `valueSet`, so blurring a half\n // typed date keeps what was entered on screen.\n if (!get(anySegmentSet) && !get(searchInputFocused)) {\n return '';\n }\n\n let result = get(dateFormat);\n\n const replacements = [\n { pattern: 'YYYY', value: getDisplayValue(selectedYear, 4) },\n { pattern: 'MM', value: getDisplayValue(selectedMonth, 2) },\n { pattern: 'DD', value: getDisplayValue(selectedDay, 2) },\n { pattern: 'HH', value: getDisplayValue(selectedHour, 2) },\n { pattern: 'mm', value: getDisplayValue(selectedMinute, 2) },\n { pattern: 'ss', value: getDisplayValue(selectedSecond, 2) },\n { pattern: 'SSS', value: getDisplayValue(selectedMillisecond, 3) },\n ];\n\n for (const { pattern, value } of replacements) {\n if (value !== undefined) {\n result = result.replace(pattern, value);\n }\n }\n\n return result;\n});\n\nconst timeSelection = computed<TimePickerSelection>({\n get() {\n const type = getCurrentSegment()?.type;\n\n if (type === 'mm') {\n return 'minute';\n }\n else if (type === 'ss') {\n return 'second';\n }\n else if (type === 'SSS') {\n return 'millisecond';\n }\n return 'hour';\n },\n set(value: TimePickerSelection) {\n let segmentType: DateTimeSegmentType = 'HH';\n if (value === 'minute') {\n segmentType = 'mm';\n }\n else if (value === 'second') {\n segmentType = 'ss';\n }\n else if (value === 'millisecond') {\n segmentType = 'SSS';\n }\n\n setSegment(segmentType);\n },\n});\n\nconst float = computed<boolean>(() => (get(isOpen) || get(valueSet) || get(searchInputFocused)) && get(isOutlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float))\n return '';\n const resolved = get(fieldLabel);\n return required ? `${resolved} ﹡` : resolved;\n});\n\nconst ui = computed<ReturnType<typeof dateTimePickerStyles>>(() => dateTimePickerStyles({\n filled: variant === 'filled',\n outlined: get(isOutlined),\n float: get(float),\n opened: get(isOpen),\n hovered: get(isHovered),\n dense,\n disabled,\n readonly,\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n}));\n\nconst combinedErrorMessages = computed<string[]>(() => {\n if (!errorMessages)\n return get(internalErrorMessages);\n\n const propErrors = Array.isArray(errorMessages) ? errorMessages : [errorMessages];\n return [...propErrors, ...get(internalErrorMessages)];\n});\n\nfunction getDisplayValue(digit: Ref<number | undefined>, padding: number): string | undefined {\n return isDefined(digit) ? get(digit).toString().padStart(padding, '0') : undefined;\n}\n\nasync function setInputFocus(): Promise<void> {\n await nextTick(() => {\n set(searchInputFocused, true);\n });\n}\n\nfunction clear(segmentType?: string): void {\n if (!segmentType) {\n clearSelection();\n set(currentValue, undefined);\n return;\n }\n\n clearSegment(segmentType);\n}\n\nfunction handleInputClick(event: MouseEvent): void {\n // Handle segment selection first, before any DOM changes from menu opening\n handleClick(event);\n // Open menu if not already open\n if (!get(isOpen)) {\n set(isOpen, true);\n }\n}\n\n/**\n * The menu is teleported to the body, so it never sits next to the field in\n * the tab sequence. Opening it from the keyboard therefore moves focus into\n * the calendar; opening it with the mouse leaves focus in the field, which is\n * what `disable-auto-focus` on the menu is there for.\n */\nconst focusCalendarOnOpen = ref<boolean>(false);\n\nfunction focusCalendar(): void {\n // the menu is teleported and mounts a frame later, so this waits for the\n // wrapper to appear rather than guessing at a number of ticks\n set(focusCalendarOnOpen, true);\n}\n\nwatch(menuWrapperRef, (menu) => {\n if (!menu || !get(focusCalendarOnOpen))\n return;\n\n set(focusCalendarOnOpen, false);\n nextTick(() => {\n const el = menu.$el as HTMLElement | undefined;\n el?.querySelector<HTMLButtonElement>('[role=\"gridcell\"][tabindex=\"0\"]')?.focus({ preventScroll: true });\n });\n});\n\n/** Moves focus to the field. Exposed so a consumer can drive it from a ref. */\nfunction focus(): void {\n get(textInput)?.focus({ preventScroll: true });\n}\n\nfunction focusField(): void {\n nextTick(focus);\n}\n\nonMounted(() => {\n if (autofocus && !disabled)\n focusField();\n});\n\n/** Escape inside the calendar closes it and hands focus back to the field. */\nfunction closeFromMenu(): void {\n set(isOpen, false);\n focusField();\n}\n\n/**\n * The menu used to be mouse-only: the activator carried no key handlers, so\n * there was no way to reach the calendar from the keyboard. Alt+ArrowDown\n * opens it and Escape closes it, following the combobox convention, and the\n * append chevron is a real button for anyone who tabs to it instead.\n */\nfunction onKeyDown(event: KeyboardEvent): void {\n if (disabled || readonly)\n return;\n\n if (event.altKey && event.key === 'ArrowDown') {\n event.preventDefault();\n set(isOpen, true);\n focusCalendar();\n return;\n }\n\n if (event.key === 'Escape' && get(isOpen)) {\n set(isOpen, false);\n return;\n }\n\n handleKeyDown(event);\n}\n\nfunction arrowClicked(event: MouseEvent): void {\n if (get(isOpen)) {\n set(isOpen, false);\n event.stopPropagation();\n }\n}\n\nconst menuActions = computed<DateTimePickerAction[]>(\n () => actions.filter(action => action !== 'clear' || allowEmpty),\n);\n\nconst anyMenuOpen = computed<boolean>(() => get(isOpen) || get(calendarMenuOpen));\n\nwatch(anyMenuOpen, (value) => {\n set(menuOpen, value);\n});\n\ndefineExpose({\n focus,\n});\n</script>\n\n<template>\n <RuiMenu\n v-model=\"isOpen\"\n v-bind=\"getRootAttrs($attrs, [])\"\n :class=\"ui.wrapper({ class: cn($attrs.class) })\"\n :options=\"MENU_OPTIONS\"\n :dense=\"dense\"\n :hint=\"hint\"\n :disabled=\"disabled\"\n :success-messages=\"successMessages\"\n :error-messages=\"combinedErrorMessages\"\n :close-on-content-click=\"false\"\n :show-details=\"!hideDetails\"\n :persistent=\"calendarMenuOpen\"\n full-width\n disable-auto-focus\n >\n <template #activator=\"{ attrs, open }\">\n <div\n ref=\"activator\"\n :class=\"ui.activator()\"\n v-bind=\"{\n ...getNonRootAttrs($attrs, ['onClick', 'class']),\n ...(readonly ? {} : attrs),\n }\"\n data-id=\"activator\"\n :aria-invalid=\"hasError\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n @click=\"setInputFocus()\"\n >\n <span\n v-if=\"isOutlined && (searchInputFocused || open || valueSet)\"\n data-id=\"label\"\n :class=\"[\n ui.label(),\n { 'pr-2': !valueSet && !open && isOutlined },\n ]\"\n >\n {{ fieldLabel }}\n <span\n v-if=\"required\"\n data-id=\"required-indicator\"\n :class=\"ui.required()\"\n >\n ﹡\n </span>\n </span>\n\n <span :class=\"ui.iconPrepend()\">\n <RuiIcon\n class=\"text-rui-text-secondary transition\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-calendar-days\"\n />\n </span>\n\n <div :class=\"ui.value()\">\n <input\n ref=\"textInput\"\n :disabled=\"disabled\"\n :value=\"formattedDisplay\"\n class=\"bg-transparent outline-none flex-1 min-w-0\"\n type=\"text\"\n inputmode=\"numeric\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n :placeholder=\"dateFormat\"\n :readonly=\"readonly\"\n :aria-invalid=\"hasError\"\n :aria-label=\"fieldLabel\"\n :aria-required=\"required || undefined\"\n @mousedown=\"handleMouseDown($event)\"\n @focus=\"handleFocus()\"\n @blur=\"handleBlur()\"\n @select=\"handleInputSelection($event)\"\n @click.stop=\"handleInputClick($event)\"\n @keydown=\"onKeyDown($event)\"\n @paste=\"handlePaste($event)\"\n @input=\"handleInput($event)\"\n />\n </div>\n\n <RuiButton\n v-if=\"allowEmpty && valueSet && !disabled\"\n variant=\"text\"\n icon\n data-id=\"clear-button\"\n size=\"sm\"\n color=\"error\"\n :aria-label=\"clearLabel\"\n :class=\"[\n ui.clear(),\n anyFocused && '!visible',\n { 'mr-2': !dense },\n ]\"\n @click.stop.prevent=\"clear()\"\n >\n <RuiIcon\n name=\"lu-x\"\n size=\"18\"\n />\n </RuiButton>\n\n <button\n v-if=\"!disabled && !readonly\"\n type=\"button\"\n :class=\"ui.iconWrapper()\"\n data-id=\"append\"\n :aria-label=\"toggleLabel\"\n :aria-expanded=\"isOpen\"\n aria-haspopup=\"dialog\"\n @click=\"arrowClicked($event)\"\n >\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </button>\n <span\n v-else\n :class=\"ui.iconWrapper()\"\n data-id=\"append\"\n >\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n </div>\n <fieldset\n v-if=\"isOutlined\"\n :class=\"ui.fieldset()\"\n >\n <legend :class=\"ui.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </template>\n <template #default>\n <RuiDateTimePickerMenu\n ref=\"menuWrapperRef\"\n v-model:selected-date=\"selectedDate\"\n v-model:selected-time=\"selectedTime\"\n v-model:selected-hour=\"selectedHour\"\n v-model:selected-minute=\"selectedMinute\"\n v-model:selected-second=\"selectedSecond\"\n v-model:selected-millisecond=\"selectedMillisecond\"\n v-model:time-selection=\"timeSelection\"\n v-model:selected-timezone=\"selectedTimezone\"\n v-model:calendar-menu-open=\"calendarMenuOpen\"\n :accuracy=\"accuracy\"\n :max-date=\"maxAllowedDate\"\n :min-date=\"minAllowedDate\"\n :show-timezone=\"showTimezone\"\n :actions=\"menuActions\"\n @keydown.escape=\"closeFromMenu()\"\n @set-now=\"setNow()\"\n @set-today=\"setToday()\"\n @clear=\"clear()\"\n >\n <slot name=\"menu-content\" />\n </RuiDateTimePickerMenu>\n </template>\n </RuiMenu>\n</template>\n"],"mappings":""}
@@ -1,3 +1,4 @@
1
+ import type { DateTimePickerAction } from '../../components/date-time-picker/types.js';
1
2
  import { type DateTimePickerVariant } from '../../components/date-time-picker/date-time-picker-styles.js';
2
3
  type DateFormat = 'year-first' | 'month-first' | 'day-first';
3
4
  type DateTimeModelType = 'date' | 'epoch-ms' | 'epoch';
@@ -19,17 +20,38 @@ export interface RuiDateTimePickerProps {
19
20
  successMessages?: string | string[];
20
21
  hideDetails?: boolean;
21
22
  required?: boolean;
23
+ /**
24
+ * Renders the timezone selector in the menu. Off by default: the value is
25
+ * emitted as a date or an epoch, so the picked timezone does not survive a
26
+ * round trip and most consumers work in local time.
27
+ */
28
+ showTimezone?: boolean;
29
+ /**
30
+ * Actions rendered in the menu footer. `clear` is only rendered when the
31
+ * picker is `allowEmpty`. Pass an empty array to drop the footer entirely.
32
+ */
33
+ actions?: DateTimePickerAction[];
34
+ /**
35
+ * Focuses the field once it is mounted. The native attribute is ignored for
36
+ * an input inserted into an already loaded document, which is the usual case
37
+ * for a picker revealed by an editor or a dialog.
38
+ */
39
+ autofocus?: boolean;
22
40
  }
23
41
  type __VLS_Props = RuiDateTimePickerProps;
24
42
  type __VLS_Slots = {
25
43
  'menu-content': () => any;
26
44
  };
45
+ /** Moves focus to the field. Exposed so a consumer can drive it from a ref. */
46
+ declare function focus(): void;
27
47
  type __VLS_ModelProps = {
28
48
  modelValue: ModelValueType<DateTimeModelType>;
29
49
  'menuOpen'?: boolean;
30
50
  };
31
51
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
32
- declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
52
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
53
+ focus: typeof focus;
54
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
33
55
  "update:modelValue": (value: number | Date | undefined) => any;
34
56
  "update:menuOpen": (value: boolean) => any;
35
57
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{