@rotki/ui-library 2.20.2 → 2.21.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.
- package/dist/__test__/options.d.ts +7 -0
- package/dist/components/calendar/RuiCalendarMenu.js.map +1 -1
- package/dist/components/calendar/RuiCalendarMenu.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/components/calendar/RuiCalendarMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/date-time-picker/RuiDateTimePicker.js.map +1 -1
- package/dist/components/date-time-picker/RuiDateTimePicker.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/components/date-time-picker/RuiDateTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/auto-complete/RuiAutoComplete.js.map +1 -1
- package/dist/components/forms/auto-complete/RuiAutoComplete.vue.d.ts +12 -1
- package/dist/components/forms/auto-complete/RuiAutoComplete.vue_vue_type_script_setup_true_lang.js +137 -51
- package/dist/components/forms/auto-complete/RuiAutoComplete.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/file-upload/RuiFileUpload.js +7 -0
- package/dist/components/forms/file-upload/RuiFileUpload.js.map +1 -0
- package/dist/components/forms/file-upload/RuiFileUpload.vue.d.ts +61 -0
- package/dist/components/forms/file-upload/RuiFileUpload.vue_vue_type_script_setup_true_lang.js +269 -0
- package/dist/components/forms/file-upload/RuiFileUpload.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/forms/select/RuiMenuSelect.js.map +1 -1
- package/dist/components/forms/select/RuiMenuSelect.vue_vue_type_script_setup_true_lang.js +7 -1
- package/dist/components/forms/select/RuiMenuSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/index.d.ts +3 -2
- package/dist/components/index.js +2 -1
- package/dist/components/overlays/menu/RuiMenu.js.map +1 -1
- package/dist/components/overlays/menu/RuiMenu.vue.d.ts +6 -0
- package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js +3 -2
- package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/composables/dropdown-menu.d.ts +12 -1
- package/dist/composables/dropdown-menu.js +46 -7
- package/dist/composables/dropdown-menu.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/library-icons.json +2 -0
- package/dist/style.css +48 -0
- package/dist/utils/helpers.d.ts +4 -0
- package/dist/utils/helpers.js +21 -1
- package/dist/utils/helpers.js.map +1 -1
- package/dist/web-types.json +262 -1
- package/package.json +1 -1
|
@@ -2,4 +2,11 @@ export interface SelectOption {
|
|
|
2
2
|
id: string;
|
|
3
3
|
label: string;
|
|
4
4
|
}
|
|
5
|
+
export interface GroupedSelectOption {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
category: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const groupedOptions: GroupedSelectOption[];
|
|
5
12
|
export declare const options: SelectOption[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RuiCalendarMenu.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 { 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\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
|
|
1
|
+
{"version":3,"file":"RuiCalendarMenu.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":""}
|
|
@@ -2,6 +2,7 @@ import { tv } from "../../utils/tv.js";
|
|
|
2
2
|
import RuiIcon_default from "../icons/RuiIcon.js";
|
|
3
3
|
import RuiButton_default from "../buttons/button/RuiButton.js";
|
|
4
4
|
import { CalendarStateSymbol, getShortMonthNames } from "./state.js";
|
|
5
|
+
import { Placement } from "../../composables/floating.js";
|
|
5
6
|
import RuiMenu_default from "../overlays/menu/RuiMenu.js";
|
|
6
7
|
import { Fragment, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, inject, mergeModels, normalizeClass, openBlock, ref, renderList, toDisplayString, unref, useModel, watch, withCtx, withModifiers } from "vue";
|
|
7
8
|
import { get, isDefined, set } from "@vueuse/core";
|
|
@@ -35,6 +36,7 @@ var RuiCalendarMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ *
|
|
|
35
36
|
setup(__props, { emit: __emit }) {
|
|
36
37
|
const modelValue = useModel(__props, "modelValue");
|
|
37
38
|
const emit = __emit;
|
|
39
|
+
const MENU_OPTIONS = { placement: Placement.bottomStart };
|
|
38
40
|
const viewMode = ref("months");
|
|
39
41
|
const startYear = ref(__props.viewYear - 6);
|
|
40
42
|
const months = getShortMonthNames();
|
|
@@ -175,7 +177,7 @@ var RuiCalendarMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ *
|
|
|
175
177
|
modelValue: modelValue.value,
|
|
176
178
|
"onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => modelValue.value = $event),
|
|
177
179
|
"anchor-el": __props.anchorEl,
|
|
178
|
-
|
|
180
|
+
options: MENU_OPTIONS
|
|
179
181
|
}, {
|
|
180
182
|
default: withCtx(() => [createElementVNode("div", _hoisted_1, [createElementVNode("div", _hoisted_2, [
|
|
181
183
|
createVNode(RuiButton_default, {
|
|
@@ -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 { 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\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 placement=\"bottom-start\"\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 { 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 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 placement=\"bottom-start\"\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 { 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":""}
|
package/dist/components/date-time-picker/RuiDateTimePicker.vue_vue_type_script_setup_true_lang.js
CHANGED
|
@@ -2,6 +2,7 @@ import { cn } from "../../utils/tv.js";
|
|
|
2
2
|
import RuiIcon_default from "../icons/RuiIcon.js";
|
|
3
3
|
import RuiButton_default from "../buttons/button/RuiButton.js";
|
|
4
4
|
import { useFormTextDetail } from "../../utils/form-text-detail.js";
|
|
5
|
+
import { Placement } from "../../composables/floating.js";
|
|
5
6
|
import RuiMenu_default from "../overlays/menu/RuiMenu.js";
|
|
6
7
|
import { getNonRootAttrs, getRootAttrs } from "../../utils/helpers.js";
|
|
7
8
|
import { dateTimePickerStyles } from "./date-time-picker-styles.js";
|
|
@@ -73,6 +74,7 @@ var RuiDateTimePicker_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
73
74
|
setup(__props) {
|
|
74
75
|
const modelValue = useModel(__props, "modelValue");
|
|
75
76
|
const menuOpen = useModel(__props, "menuOpen");
|
|
77
|
+
const MENU_OPTIONS = { placement: Placement.bottomStart };
|
|
76
78
|
const baseFormats = {
|
|
77
79
|
"day-first": "DD/MM/YYYY HH:mm",
|
|
78
80
|
"month-first": "MM/DD/YYYY HH:mm",
|
|
@@ -226,7 +228,7 @@ var RuiDateTimePicker_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
226
228
|
"onUpdate:modelValue": _cache[23] || (_cache[23] = ($event) => isRef(isOpen) ? isOpen.value = $event : null)
|
|
227
229
|
}, unref(getRootAttrs)(_ctx.$attrs, []), {
|
|
228
230
|
class: unref(ui).wrapper({ class: unref(cn)(_ctx.$attrs.class) }),
|
|
229
|
-
|
|
231
|
+
options: MENU_OPTIONS,
|
|
230
232
|
dense: __props.dense,
|
|
231
233
|
hint: __props.hint,
|
|
232
234
|
disabled: __props.disabled,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RuiDateTimePicker.vue_vue_type_script_setup_true_lang.js","names":["$attrs"],"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 { 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 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 placement=\"bottom-start\"\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":"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"}
|
|
1
|
+
{"version":3,"file":"RuiDateTimePicker.vue_vue_type_script_setup_true_lang.js","names":["$attrs"],"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":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RuiAutoComplete.js","names":[],"sources":["../../../../src/components/forms/auto-complete/RuiAutoComplete.vue"],"sourcesContent":["<script lang=\"ts\" setup generic=\"TValue, TItem\">\nimport type { VueClassValue } from '@/types/class-value';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport RuiChip from '@/components/chips/RuiChip.vue';\nimport { autoCompleteStyles, type AutoCompleteVariant } from '@/components/forms/auto-complete/auto-complete-styles';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiMenu, { type MenuProps } from '@/components/overlays/menu/RuiMenu.vue';\nimport RuiProgress from '@/components/progress/RuiProgress.vue';\nimport {\n type KeyOfType,\n useDropdownMenu,\n useDropdownOptionProperty,\n} from '@/composables/dropdown-menu';\nimport {\n useAutoCompleteFocus,\n useAutoCompleteKeyboardNavigation,\n useAutoCompleteSearch,\n useAutoCompleteValue,\n} from '@/composables/forms/auto-complete';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { getNonRootAttrs, getRootAttrs, getTextToken } from '@/utils/helpers';\nimport { isEqual } from '@/utils/is-equal';\nimport { cn } from '@/utils/tv';\n\nexport type AutoCompleteModelValue<TValue> =\n TValue extends Array<infer U> ? U[] : TValue | undefined;\n\nexport interface RuiAutoCompleteClassNames {\n root?: VueClassValue;\n label?: VueClassValue;\n menu?: VueClassValue;\n}\n\nexport interface AutoCompleteProps<TValue, TItem> {\n options?: TItem[];\n keyAttr?: KeyOfType<TItem, TValue extends Array<infer U> ? U : TValue>;\n textAttr?: keyof TItem;\n disabled?: boolean;\n loading?: boolean;\n readOnly?: boolean;\n dense?: boolean;\n clearable?: boolean;\n label?: string;\n menuOptions?: MenuProps;\n classNames?: RuiAutoCompleteClassNames;\n /** @deprecated Use `classNames.label` instead */\n labelClass?: string;\n /** @deprecated Use `classNames.menu` instead */\n menuClass?: string;\n prependWidth?: number;\n appendWidth?: number;\n itemHeight?: number;\n variant?: AutoCompleteVariant;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hideDetails?: boolean;\n autoSelectFirst?: boolean;\n chips?: boolean;\n noFilter?: boolean;\n hideNoData?: boolean;\n noDataText?: string;\n filter?: (item: TItem, queryText: string) => boolean;\n hideSelected?: boolean;\n placeholder?: string;\n returnObject?: boolean;\n customValue?: boolean;\n hideCustomValue?: boolean;\n required?: boolean;\n hideSearchInput?: boolean;\n hideSelectionWrapper?: boolean;\n}\n\ndefineOptions({\n name: 'RuiAutoComplete',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<AutoCompleteModelValue<TValue>>({ required: true });\n\nconst searchInputModel = defineModel<string>('searchInput', { default: '' });\n\nconst {\n options = [],\n disabled = false,\n loading = false,\n readOnly = false,\n dense = false,\n clearable = false,\n hideDetails = false,\n chips = false,\n label = 'Select',\n menuOptions,\n classNames,\n labelClass,\n menuClass,\n variant = 'default',\n hint,\n keyAttr,\n textAttr,\n itemHeight,\n errorMessages = [],\n successMessages = [],\n autoSelectFirst = false,\n noFilter = false,\n hideNoData = false,\n noDataText = 'No data available',\n filter,\n hideSelected = false,\n placeholder = '',\n returnObject = false,\n customValue = false,\n hideCustomValue = false,\n required = false,\n hideSearchInput = false,\n hideSelectionWrapper = false,\n} = defineProps<AutoCompleteProps<TValue, TItem>>();\n\nconst slots = defineSlots<{\n 'activator'?: (props: {\n disabled: boolean;\n value: TItem[];\n variant: string;\n readOnly: boolean;\n attrs: Record<string, unknown>;\n open: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n 'activator.label'?: (props: { value: TItem[] }) => any;\n 'selection.prepend'?: (props: { index: number; item: TItem }) => any;\n 'selection'?: (props: { index: number; item: TItem; chipAttrs: Record<string, unknown> }) => any;\n 'item.prepend'?: (props: { disabled: boolean; item: TItem; active: boolean }) => any;\n 'item'?: (props: { disabled: boolean; item: TItem; active: boolean }) => any;\n 'item.append'?: (props: { disabled: boolean; item: TItem; active: boolean }) => any;\n 'no-data'?: () => any;\n}>();\n\nconst { getText, getIdentifier } = useDropdownOptionProperty<TValue, TItem>({\n keyAttr,\n textAttr,\n});\n\nconst textInput = useTemplateRef<HTMLInputElement>('textInput');\nconst activator = useTemplateRef<HTMLDivElement>('activator');\nconst menuRef = useTemplateRef<HTMLDivElement>('menuRef');\nconst menuWrapperRef = useTemplateRef<HTMLDivElement>('menuWrapperRef');\n\nconst { focused: activatorFocusedWithin } = useFocusWithin(activator);\nconst { focused: menuWrapperFocusedWithin } = useFocusWithin(menuWrapperRef);\nconst { focused: searchInputFocused } = useFocus(textInput);\nconst { focused: activatorFocused } = useFocus(activator);\n\nconst {\n internalSearch,\n filteredOptions,\n justOpened,\n updateInternalSearch,\n textValueToProperValue,\n} = useAutoCompleteSearch<TItem>(\n () => options,\n searchInputModel,\n {\n keyAttr: () => keyAttr,\n textAttr: () => textAttr,\n noFilter: () => noFilter,\n filter: () => filter,\n customValue: () => customValue,\n hideCustomValue: () => hideCustomValue,\n returnObject: () => returnObject,\n },\n);\n\nconst isOpen = ref<boolean>(false);\nconst isHovered = ref<boolean>(false);\n\n// Calculate multiple from modelValue directly to avoid circular dependency\nconst multiple = computed<boolean>(() => Array.isArray(get(modelValue)));\n\nconst shouldApplyValueAsSearch = computed<boolean>(\n () => !(slots.selection || get(multiple) || chips),\n);\n\nconst { value, setSelected } = useAutoCompleteValue<AutoCompleteModelValue<TValue>, TItem>(\n modelValue,\n () => options,\n {\n keyAttr: () => keyAttr,\n returnObject: () => returnObject,\n customValue: () => customValue,\n },\n {\n getIdentifier,\n getText,\n textValueToProperValue,\n shouldApplyValueAsSearch,\n isOpen,\n multiple,\n updateInternalSearch,\n },\n);\n\nconst resolvedItemHeight = itemHeight ?? (dense ? 30 : 48);\n\nconst {\n containerProps,\n wrapperProps,\n renderedData,\n menuWidth,\n isActiveItem,\n itemIndexInValue,\n highlightedIndex,\n moveHighlight,\n applyHighlighted,\n optionsWithSelectedHidden,\n userNavigated,\n} = useDropdownMenu<TValue, TItem>({\n itemHeight: resolvedItemHeight,\n keyAttr,\n textAttr,\n options: filteredOptions,\n dense: () => dense,\n value,\n menuRef,\n setValue,\n autoSelectFirst,\n hideSelected,\n isOpen,\n getText,\n getIdentifier,\n});\n\nconst {\n focusedValueIndex,\n moveSelectedValueHighlight,\n onEnter,\n onInputDeletePressed,\n onTab,\n setValueFocus,\n} = useAutoCompleteKeyboardNavigation<TItem>(\n {\n chips: () => chips,\n customValue: () => customValue,\n multiple,\n },\n {\n activator,\n applyHighlighted,\n clear,\n filteredOptions,\n getText,\n highlightedIndex,\n internalSearch,\n isOpen,\n removeValue: (item: TItem): void => { setValue(item); },\n searchInputFocused,\n setSearchAsValue,\n userNavigated,\n value,\n },\n);\n\nconst {\n anyFocused: focusAnyFocused,\n inputClass: focusInputClass,\n onActivatorFocused: focusOnActivatorFocused,\n onInputFocused: focusOnInputFocused,\n setInputFocus: focusSetInputFocus,\n} = useAutoCompleteFocus(\n {\n customValue: () => customValue,\n disabled: () => disabled,\n shouldApplyValueAsSearch,\n },\n {\n activatorFocused,\n activatorFocusedWithin,\n focusedValueIndex,\n internalSearch,\n isOpen,\n justOpened,\n menuWrapperFocusedWithin,\n searchInputFocused,\n setSearchAsValue,\n textInput,\n updateInternalSearch,\n },\n);\n\nconst menuMinHeight = computed<number>(\n () => Math.min(5, get(optionsWithSelectedHidden).length) * resolvedItemHeight,\n);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst valueSet = computed<boolean>(() => get(value).length > 0);\n\nconst usedPlaceholder = computed<string>(() => {\n if (get(searchInputFocused))\n return placeholder;\n return '';\n});\n\nconst outlined = computed<boolean>(() => variant === 'outlined');\nconst float = computed<boolean>(() => (get(isOpen) || get(valueSet) || get(searchInputFocused)) && get(outlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float) || !label)\n return '';\n return required ? `${label} ﹡` : label;\n});\n\nconst ui = computed<ReturnType<typeof autoCompleteStyles>>(() => autoCompleteStyles({\n filled: variant === 'filled',\n outlined: get(outlined),\n float: get(float),\n opened: get(isOpen),\n hovered: get(isHovered),\n dense,\n disabled,\n readonly: readOnly,\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n}));\n\nconst highlightedClass = autoCompleteStyles({}).highlighted();\n\nfunction updateSearchInput(event: Event): void {\n const target = event.target;\n if (!(target instanceof HTMLInputElement))\n return;\n\n const value = target.value;\n set(isOpen, true);\n updateInternalSearch(value);\n set(justOpened, false);\n}\n\nasync function setValue(val: TItem, skipRefocused = false): Promise<void> {\n const isMultiple = get(multiple);\n\n if (isMultiple) {\n const newValue = [...get(value)];\n const indexInValue = itemIndexInValue(val);\n if (indexInValue === -1) {\n updateInternalSearch();\n newValue.push(val);\n }\n else {\n newValue.splice(indexInValue, 1);\n }\n set(value, newValue);\n }\n else {\n if (get(shouldApplyValueAsSearch))\n updateInternalSearch(getText(val));\n else updateInternalSearch();\n\n set(value, [val]);\n }\n\n if (!isMultiple) {\n if (!skipRefocused) {\n set(activatorFocused, true);\n get(activator)?.focus();\n await nextTick(() => {\n set(isOpen, false);\n });\n }\n else {\n set(isOpen, false);\n }\n }\n else if (!skipRefocused) {\n set(searchInputFocused, true);\n }\n}\n\nfunction setSearchAsValue(): void {\n const searchToBeValue = get(internalSearch);\n if (!searchToBeValue)\n return;\n\n const newValue: TItem = textValueToProperValue(searchToBeValue);\n setValue(newValue, true);\n}\n\nfunction clear(): void {\n updateInternalSearch();\n set(modelValue, (Array.isArray(get(modelValue)) ? [] : undefined) as AutoCompleteModelValue<TValue>);\n}\n\nfunction chipAttrs(item: TItem, index: number): Record<string, unknown> {\n return {\n 'data-index': index,\n 'data-value': getIdentifier(item),\n 'onKeydown': (event: KeyboardEvent): void => {\n const { key } = event;\n if (['Backspace', 'Delete'].includes(key)) {\n event.stopPropagation();\n event.preventDefault();\n setValue(item);\n }\n },\n 'onClick': (e: MouseEvent): void => {\n e.stopPropagation();\n setValueFocus(index);\n },\n 'onClick:close': (): void => {\n setValue(item);\n },\n };\n}\n\nfunction setSelectionRange(start: number, end: number): void {\n set(searchInputFocused, true);\n get(textInput)?.setSelectionRange?.(start, end);\n}\n\nfunction arrowClicked(event: MouseEvent): void {\n if (get(isOpen)) {\n set(isOpen, false);\n event.stopPropagation();\n }\n}\n\nfunction openMenu(): void {\n set(isOpen, true);\n}\n\nfunction closeMenu(): void {\n set(isOpen, false);\n}\n\n// Optimize options watcher with shallow comparison first\nwatch(() => options, (curr, old) => {\n if (curr === old || customValue)\n return;\n\n // Only do deep comparison if reference changed\n if (isEqual(curr, old))\n return;\n\n setSelected(get(value));\n});\n\ndefineExpose({\n closeMenu,\n focus: focusSetInputFocus,\n openMenu,\n setSelectionRange,\n});\n</script>\n\n<template>\n <RuiMenu\n v-model=\"isOpen\"\n v-bind=\"{ ...getRootAttrs($attrs, []), ...menuOptions }\"\n :class=\"ui.wrapper({ class: cn($attrs.class) })\"\n placement=\"bottom-start\"\n :close-on-content-click=\"false\"\n :full-width=\"true\"\n :persist-on-activator-click=\"true\"\n :menu-class=\"[\n { hidden: optionsWithSelectedHidden.length === 0 && customValue && !slots['no-data'] },\n menuOptions?.menuClass,\n ]\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n :dense=\"dense\"\n :show-details=\"!hideDetails\"\n :disabled=\"disabled\"\n disable-auto-focus\n >\n <template #activator=\"{ attrs, open, hasError: slotHasError, hasSuccess: slotHasSuccess }\">\n <slot\n name=\"activator\"\n v-bind=\"{ disabled, value, variant, readOnly, attrs, open, hasError: slotHasError, hasSuccess: slotHasSuccess }\"\n >\n <div\n ref=\"activator\"\n :class=\"ui.activator({ class: cn(classNames?.label) ?? labelClass })\"\n v-bind=\"{\n ...getNonRootAttrs($attrs, ['onClick', 'class']),\n ...(readOnly ? {} : attrs),\n }\"\n role=\"combobox\"\n :aria-expanded=\"open\"\n :aria-disabled=\"disabled || undefined\"\n :aria-readonly=\"readOnly || undefined\"\n :aria-required=\"required || undefined\"\n :aria-busy=\"loading || undefined\"\n data-id=\"activator\"\n :aria-invalid=\"hasError\"\n :tabindex=\"disabled || readOnly ? -1 : 0\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n @click=\"focusSetInputFocus()\"\n @focus=\"focusOnActivatorFocused()\"\n @keydown.enter=\"onEnter($event)\"\n @keydown.tab=\"onTab($event)\"\n @keydown.left=\"moveSelectedValueHighlight($event, false)\"\n @keydown.right=\"moveSelectedValueHighlight($event, true)\"\n @keydown.up.prevent=\"moveHighlight(true)\"\n @keydown.down.prevent=\"moveHighlight(false)\"\n @keydown.home.prevent=\"highlightedIndex = 0\"\n @keydown.end.prevent=\"highlightedIndex = optionsWithSelectedHidden.length - 1\"\n >\n <span\n v-if=\"outlined || (!valueSet && !searchInputFocused)\"\n :class=\"[\n ui.label(),\n { 'pr-2': !valueSet && !open && outlined },\n ]\"\n >\n <slot\n name=\"activator.label\"\n v-bind=\"{ value }\"\n >\n {{ label }}\n </slot>\n <span\n v-if=\"required\"\n :class=\"ui.required()\"\n >\n ﹡\n </span>\n </span>\n <div\n data-id=\"value\"\n :class=\"ui.value()\"\n >\n <template\n v-for=\"(item, i) in value\"\n :key=\"getIdentifier(item)\"\n >\n <RuiChip\n v-if=\"chips\"\n :key=\"getTextToken(getIdentifier(item))\"\n tabindex=\"-1\"\n :size=\"dense ? 'sm' : 'md'\"\n closeable\n :class=\"{ 'leading-3': dense }\"\n clickable\n v-bind=\"chipAttrs(item, i)\"\n >\n <div class=\"flex\">\n <slot\n name=\"selection.prepend\"\n :index=\"i\"\n v-bind=\"{ item }\"\n />\n <slot\n :index=\"i\"\n name=\"selection\"\n v-bind=\"{ item, chipAttrs: chipAttrs(item, i) }\"\n >\n {{ getText(item) }}\n </slot>\n </div>\n </RuiChip>\n <div\n v-else-if=\"\n multiple\n || (!searchInputFocused && (slots['selection.prepend'] || slots.selection))\n \"\n :class=\"hideSelectionWrapper ? 'contents' : 'flex'\"\n >\n <slot\n name=\"selection.prepend\"\n :index=\"i\"\n v-bind=\"{ item }\"\n />\n <slot\n v-if=\"multiple || slots.selection\"\n :index=\"i\"\n name=\"selection\"\n v-bind=\"{ item, chipAttrs: chipAttrs(item, i) }\"\n >\n {{ getText(item) }}\n </slot>\n </div>\n </template>\n <input\n ref=\"textInput\"\n :disabled=\"disabled\"\n :value=\"internalSearch\"\n class=\"bg-transparent outline-none\"\n type=\"text\"\n :placeholder=\"usedPlaceholder\"\n :class=\"[focusInputClass, { hidden: hideSearchInput }]\"\n :aria-invalid=\"hasError\"\n aria-autocomplete=\"list\"\n @keydown.delete=\"onInputDeletePressed()\"\n @input.stop=\"updateSearchInput($event)\"\n @focus=\"focusOnInputFocused()\"\n />\n </div>\n\n <RuiButton\n v-if=\"clearable && valueSet && !disabled\"\n variant=\"text\"\n icon\n size=\"sm\"\n tabindex=\"-1\"\n color=\"error\"\n data-id=\"clear\"\n :class=\"[\n ui.clear(),\n focusAnyFocused && '!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 @click=\"arrowClicked($event)\"\n >\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n\n <RuiProgress\n v-if=\"loading\"\n :class=\"ui.progress()\"\n color=\"primary\"\n thickness=\"3\"\n variant=\"indeterminate\"\n />\n </div>\n <fieldset\n v-if=\"outlined\"\n :class=\"ui.fieldset()\"\n >\n <legend :class=\"ui.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </slot>\n </template>\n <template #default=\"{ width }\">\n <div ref=\"menuWrapperRef\">\n <div\n v-if=\"optionsWithSelectedHidden.length > 0\"\n :ref=\"containerProps.ref\"\n :class=\"ui.menu({ class: cn(classNames?.menu) ?? menuClass })\"\n :style=\"[containerProps.style, { width: `${width}px`, minWidth: menuWidth, minHeight: `${menuMinHeight}px` }]\"\n @scroll=\"containerProps.onScroll\"\n @keydown.up.prevent=\"moveHighlight(true)\"\n @keydown.down.prevent=\"moveHighlight(false)\"\n >\n <div\n v-bind=\"wrapperProps\"\n ref=\"menuRef\"\n >\n <RuiButton\n v-for=\"{ item, _index } in renderedData\"\n :key=\"getIdentifier(item)?.toString()\"\n :active=\"isActiveItem(item)\"\n :aria-selected=\"isActiveItem(item)\"\n :size=\"dense ? 'sm' : undefined\"\n tabindex=\"0\"\n variant=\"list\"\n :data-highlighted=\"highlightedIndex === _index\"\n :class=\"{\n [highlightedClass]: !isActiveItem(item) && highlightedIndex === _index,\n }\"\n @click=\"setValue(item)\"\n >\n <template #prepend>\n <slot\n name=\"item.prepend\"\n v-bind=\"{ disabled, item, active: isActiveItem(item) }\"\n />\n </template>\n <slot\n name=\"item\"\n v-bind=\"{ disabled, item, active: isActiveItem(item) }\"\n >\n {{ getText(item) }}\n </slot>\n <template #append>\n <slot\n name=\"item.append\"\n v-bind=\"{ disabled, item, active: isActiveItem(item) }\"\n />\n </template>\n </RuiButton>\n </div>\n </div>\n\n <div\n v-else-if=\"!hideNoData\"\n :style=\"{ width: `${width}px`, minWidth: menuWidth }\"\n :class=\"classNames?.menu ?? menuClass\"\n >\n <slot name=\"no-data\">\n <div\n v-if=\"!customValue\"\n class=\"p-4\"\n data-id=\"no-data\"\n >\n {{ noDataText }}\n </div>\n </slot>\n </div>\n </div>\n </template>\n </RuiMenu>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"RuiAutoComplete.js","names":[],"sources":["../../../../src/components/forms/auto-complete/RuiAutoComplete.vue"],"sourcesContent":["<script lang=\"ts\" setup generic=\"TValue, TItem\">\nimport type { VueClassValue } from '@/types/class-value';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport RuiChip from '@/components/chips/RuiChip.vue';\nimport { autoCompleteStyles, type AutoCompleteVariant } from '@/components/forms/auto-complete/auto-complete-styles';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiMenu, { type MenuProps } from '@/components/overlays/menu/RuiMenu.vue';\nimport RuiProgress from '@/components/progress/RuiProgress.vue';\nimport {\n type GroupBy,\n type ItemDisabled,\n type KeyOfType,\n useDropdownMenu,\n useDropdownOptionProperty,\n} from '@/composables/dropdown-menu';\nimport { type FloatingOptions, Placement } from '@/composables/floating';\nimport {\n useAutoCompleteFocus,\n useAutoCompleteKeyboardNavigation,\n useAutoCompleteSearch,\n useAutoCompleteValue,\n} from '@/composables/forms/auto-complete';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { getNonRootAttrs, getRootAttrs, getTextToken } from '@/utils/helpers';\nimport { isEqual } from '@/utils/is-equal';\nimport { cn } from '@/utils/tv';\n\nexport type AutoCompleteModelValue<TValue> =\n TValue extends Array<infer U> ? U[] : TValue | undefined;\n\nexport interface RuiAutoCompleteClassNames {\n root?: VueClassValue;\n label?: VueClassValue;\n menu?: VueClassValue;\n}\n\nexport interface AutoCompleteProps<TValue, TItem> {\n options?: TItem[];\n keyAttr?: KeyOfType<TItem, TValue extends Array<infer U> ? U : TValue>;\n textAttr?: keyof TItem;\n disabled?: boolean;\n loading?: boolean;\n readOnly?: boolean;\n dense?: boolean;\n clearable?: boolean;\n label?: string;\n menuOptions?: MenuProps;\n classNames?: RuiAutoCompleteClassNames;\n /** @deprecated Use `classNames.label` instead */\n labelClass?: string;\n /** @deprecated Use `classNames.menu` instead */\n menuClass?: string;\n prependWidth?: number;\n appendWidth?: number;\n itemHeight?: number;\n variant?: AutoCompleteVariant;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hideDetails?: boolean;\n autoSelectFirst?: boolean;\n chips?: boolean;\n noFilter?: boolean;\n hideNoData?: boolean;\n noDataText?: string;\n filter?: (item: TItem, queryText: string) => boolean;\n hideSelected?: boolean;\n placeholder?: string;\n returnObject?: boolean;\n customValue?: boolean;\n hideCustomValue?: boolean;\n required?: boolean;\n hideSearchInput?: boolean;\n hideSelectionWrapper?: boolean;\n groupBy?: GroupBy<TItem>;\n itemDisabled?: ItemDisabled<TItem>;\n}\n\ndefineOptions({\n name: 'RuiAutoComplete',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<AutoCompleteModelValue<TValue>>({ required: true });\n\nconst searchInputModel = defineModel<string>('searchInput', { default: '' });\n\nconst {\n options = [],\n disabled = false,\n loading = false,\n readOnly = false,\n dense = false,\n clearable = false,\n hideDetails = false,\n chips = false,\n label = 'Select',\n menuOptions,\n classNames,\n labelClass,\n menuClass,\n variant = 'default',\n hint,\n keyAttr,\n textAttr,\n itemHeight,\n errorMessages = [],\n successMessages = [],\n autoSelectFirst = false,\n noFilter = false,\n hideNoData = false,\n noDataText = 'No data available',\n filter,\n hideSelected = false,\n placeholder = '',\n returnObject = false,\n customValue = false,\n hideCustomValue = false,\n required = false,\n hideSearchInput = false,\n hideSelectionWrapper = false,\n groupBy,\n itemDisabled,\n} = defineProps<AutoCompleteProps<TValue, TItem>>();\n\nconst slots = defineSlots<{\n 'activator'?: (props: {\n disabled: boolean;\n value: TItem[];\n variant: string;\n readOnly: boolean;\n attrs: Record<string, unknown>;\n open: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n 'activator.label'?: (props: { value: TItem[] }) => any;\n 'selection.prepend'?: (props: { index: number; item: TItem }) => any;\n 'selection'?: (props: { index: number; item: TItem; chipAttrs: Record<string, unknown> }) => any;\n 'item.prepend'?: (props: { disabled: boolean; item: TItem; active: boolean }) => any;\n 'item'?: (props: { disabled: boolean; item: TItem; active: boolean }) => any;\n 'item.append'?: (props: { disabled: boolean; item: TItem; active: boolean }) => any;\n 'group-header'?: (props: { group: string; items: TItem[] }) => any;\n 'no-data'?: () => any;\n 'placeholder'?: (props: { disabled: boolean; readOnly: boolean }) => any;\n 'footer'?: () => any;\n}>();\n\nconst { getText, getIdentifier } = useDropdownOptionProperty<TValue, TItem>({\n keyAttr,\n textAttr,\n});\n\nconst textInput = useTemplateRef<HTMLInputElement>('textInput');\nconst activator = useTemplateRef<HTMLDivElement>('activator');\nconst menuRef = useTemplateRef<HTMLDivElement>('menuRef');\nconst menuWrapperRef = useTemplateRef<HTMLDivElement>('menuWrapperRef');\n\nconst { focused: activatorFocusedWithin } = useFocusWithin(activator);\nconst { focused: menuWrapperFocusedWithin } = useFocusWithin(menuWrapperRef);\nconst { focused: searchInputFocused } = useFocus(textInput);\nconst { focused: activatorFocused } = useFocus(activator);\n\nconst {\n internalSearch,\n filteredOptions,\n justOpened,\n updateInternalSearch,\n textValueToProperValue,\n} = useAutoCompleteSearch<TItem>(\n () => options,\n searchInputModel,\n {\n keyAttr: () => keyAttr,\n textAttr: () => textAttr,\n noFilter: () => noFilter,\n filter: () => filter,\n customValue: () => customValue,\n hideCustomValue: () => hideCustomValue,\n returnObject: () => returnObject,\n },\n);\n\nconst isOpen = ref<boolean>(false);\nconst isHovered = ref<boolean>(false);\n\n// Calculate multiple from modelValue directly to avoid circular dependency\nconst multiple = computed<boolean>(() => Array.isArray(get(modelValue)));\n\nconst shouldApplyValueAsSearch = computed<boolean>(\n () => !(slots.selection || get(multiple) || chips),\n);\n\nconst { value, setSelected } = useAutoCompleteValue<AutoCompleteModelValue<TValue>, TItem>(\n modelValue,\n () => options,\n {\n keyAttr: () => keyAttr,\n returnObject: () => returnObject,\n customValue: () => customValue,\n },\n {\n getIdentifier,\n getText,\n textValueToProperValue,\n shouldApplyValueAsSearch,\n isOpen,\n multiple,\n updateInternalSearch,\n },\n);\n\nconst resolvedItemHeight = itemHeight ?? (dense ? 30 : 48);\n\nconst {\n containerProps,\n wrapperProps,\n renderedData,\n menuWidth,\n isActiveItem,\n itemIndexInValue,\n highlightedIndex,\n moveHighlight,\n applyHighlighted,\n optionsWithSelectedHidden,\n userNavigated,\n groupedOptions,\n isGrouped,\n isItemDisabled,\n} = useDropdownMenu<TValue, TItem>({\n itemHeight: resolvedItemHeight,\n keyAttr,\n textAttr,\n options: filteredOptions,\n dense: () => dense,\n value,\n menuRef,\n setValue,\n autoSelectFirst,\n hideSelected,\n isOpen,\n getText,\n getIdentifier,\n groupBy: () => groupBy,\n itemDisabled: () => itemDisabled,\n});\n\nconst {\n focusedValueIndex,\n moveSelectedValueHighlight,\n onEnter,\n onInputDeletePressed,\n onTab,\n setValueFocus,\n} = useAutoCompleteKeyboardNavigation<TItem>(\n {\n chips: () => chips,\n customValue: () => customValue,\n multiple,\n },\n {\n activator,\n applyHighlighted,\n clear,\n filteredOptions,\n getText,\n highlightedIndex,\n internalSearch,\n isOpen,\n removeValue: (item: TItem): void => { setValue(item); },\n searchInputFocused,\n setSearchAsValue,\n userNavigated,\n value,\n },\n);\n\nconst {\n anyFocused: focusAnyFocused,\n inputClass: focusInputClass,\n onActivatorFocused: focusOnActivatorFocused,\n onInputFocused: focusOnInputFocused,\n setInputFocus: focusSetInputFocus,\n} = useAutoCompleteFocus(\n {\n customValue: () => customValue,\n disabled: () => disabled,\n shouldApplyValueAsSearch,\n },\n {\n activatorFocused,\n activatorFocusedWithin,\n focusedValueIndex,\n internalSearch,\n isOpen,\n justOpened,\n menuWrapperFocusedWithin,\n searchInputFocused,\n setSearchAsValue,\n textInput,\n updateInternalSearch,\n },\n);\n\nconst menuMinHeight = computed<number>(\n () => Math.min(5, get(optionsWithSelectedHidden).length) * resolvedItemHeight,\n);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst valueSet = computed<boolean>(() => get(value).length > 0);\n\n// True when the consumer's #placeholder slot is taking over the resting\n// content area, so the outlined/resting label must hide to avoid overlapping\n// the slot content.\nconst placeholderSlotActive = computed<boolean>(() => Boolean(slots.placeholder) && !get(valueSet) && !get(searchInputFocused));\n\nconst usedPlaceholder = computed<string>(() => {\n if (get(searchInputFocused))\n return placeholder;\n return '';\n});\n\nconst outlined = computed<boolean>(() => variant === 'outlined');\nconst float = computed<boolean>(() => (get(isOpen) || get(valueSet) || get(searchInputFocused)) && get(outlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float) || !label)\n return '';\n return required ? `${label} ﹡` : label;\n});\n\nconst ui = computed<ReturnType<typeof autoCompleteStyles>>(() => autoCompleteStyles({\n filled: variant === 'filled',\n outlined: get(outlined),\n float: get(float),\n opened: get(isOpen),\n hovered: get(isHovered),\n dense,\n disabled,\n readonly: readOnly,\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n}));\n\nconst highlightedClass = autoCompleteStyles({}).highlighted();\n\nfunction updateSearchInput(event: Event): void {\n const target = event.target;\n if (!(target instanceof HTMLInputElement))\n return;\n\n const value = target.value;\n set(isOpen, true);\n updateInternalSearch(value);\n set(justOpened, false);\n}\n\nasync function setValue(val: TItem, skipRefocused = false): Promise<void> {\n const isMultiple = get(multiple);\n\n if (isMultiple) {\n const newValue = [...get(value)];\n const indexInValue = itemIndexInValue(val);\n if (indexInValue === -1) {\n updateInternalSearch();\n newValue.push(val);\n }\n else {\n newValue.splice(indexInValue, 1);\n }\n set(value, newValue);\n }\n else {\n if (get(shouldApplyValueAsSearch))\n updateInternalSearch(getText(val));\n else updateInternalSearch();\n\n set(value, [val]);\n }\n\n if (!isMultiple) {\n if (!skipRefocused) {\n set(activatorFocused, true);\n get(activator)?.focus();\n await nextTick(() => {\n set(isOpen, false);\n });\n }\n else {\n set(isOpen, false);\n }\n }\n else if (!skipRefocused) {\n set(searchInputFocused, true);\n }\n}\n\nfunction setSearchAsValue(): void {\n const searchToBeValue = get(internalSearch);\n if (!searchToBeValue)\n return;\n\n const newValue: TItem = textValueToProperValue(searchToBeValue);\n setValue(newValue, true);\n}\n\nfunction clear(): void {\n updateInternalSearch();\n set(modelValue, (Array.isArray(get(modelValue)) ? [] : undefined) as AutoCompleteModelValue<TValue>);\n}\n\nfunction chipAttrs(item: TItem, index: number): Record<string, unknown> {\n return {\n 'data-index': index,\n 'data-value': getIdentifier(item),\n 'onKeydown': (event: KeyboardEvent): void => {\n const { key } = event;\n if (['Backspace', 'Delete'].includes(key)) {\n event.stopPropagation();\n event.preventDefault();\n setValue(item);\n }\n },\n 'onClick': (e: MouseEvent): void => {\n e.stopPropagation();\n setValueFocus(index);\n },\n 'onClick:close': (): void => {\n setValue(item);\n },\n };\n}\n\nfunction setSelectionRange(start: number, end: number): void {\n set(searchInputFocused, true);\n get(textInput)?.setSelectionRange?.(start, end);\n}\n\nfunction arrowClicked(event: MouseEvent): void {\n if (get(isOpen)) {\n set(isOpen, false);\n event.stopPropagation();\n }\n}\n\nfunction openMenu(): void {\n set(isOpen, true);\n}\n\nfunction closeMenu(): void {\n set(isOpen, false);\n}\n\n// Optimize options watcher with shallow comparison first\nwatch(() => options, (curr, old) => {\n if (curr === old || customValue)\n return;\n\n // Only do deep comparison if reference changed\n if (isEqual(curr, old))\n return;\n\n setSelected(get(value));\n});\n\nconst menuFloatingOptions = computed<FloatingOptions>(() => ({\n placement: Placement.bottomStart,\n ...menuOptions?.options,\n}));\n\ndefineExpose({\n closeMenu,\n focus: focusSetInputFocus,\n openMenu,\n setSelectionRange,\n});\n</script>\n\n<template>\n <RuiMenu\n v-model=\"isOpen\"\n v-bind=\"{ ...getRootAttrs($attrs, []), ...menuOptions }\"\n :class=\"ui.wrapper({ class: cn($attrs.class) })\"\n :options=\"menuFloatingOptions\"\n :close-on-content-click=\"false\"\n :full-width=\"true\"\n :persist-on-activator-click=\"true\"\n :menu-class=\"[\n { hidden: optionsWithSelectedHidden.length === 0 && customValue && !slots['no-data'] },\n menuOptions?.menuClass,\n ]\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n :dense=\"dense\"\n :show-details=\"!hideDetails\"\n :disabled=\"disabled\"\n disable-auto-focus\n >\n <template #activator=\"{ attrs, open, hasError: slotHasError, hasSuccess: slotHasSuccess }\">\n <slot\n name=\"activator\"\n v-bind=\"{ disabled, value, variant, readOnly, attrs, open, hasError: slotHasError, hasSuccess: slotHasSuccess }\"\n >\n <div\n ref=\"activator\"\n :class=\"ui.activator({ class: cn(classNames?.label) ?? labelClass })\"\n v-bind=\"{\n ...getNonRootAttrs($attrs, ['onClick', 'class']),\n ...(readOnly ? {} : attrs),\n }\"\n role=\"combobox\"\n :aria-expanded=\"open\"\n :aria-disabled=\"disabled || undefined\"\n :aria-readonly=\"readOnly || undefined\"\n :aria-required=\"required || undefined\"\n :aria-busy=\"loading || undefined\"\n data-id=\"activator\"\n :aria-invalid=\"hasError\"\n :tabindex=\"disabled || readOnly ? -1 : 0\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n @click=\"focusSetInputFocus()\"\n @focus=\"focusOnActivatorFocused()\"\n @keydown.enter=\"onEnter($event)\"\n @keydown.tab=\"onTab($event)\"\n @keydown.left=\"moveSelectedValueHighlight($event, false)\"\n @keydown.right=\"moveSelectedValueHighlight($event, true)\"\n @keydown.up.prevent=\"moveHighlight(true)\"\n @keydown.down.prevent=\"moveHighlight(false)\"\n @keydown.home.prevent=\"highlightedIndex = 0\"\n @keydown.end.prevent=\"highlightedIndex = optionsWithSelectedHidden.length - 1\"\n >\n <span\n v-if=\"(outlined || (!valueSet && !searchInputFocused)) && !placeholderSlotActive\"\n :class=\"[\n ui.label(),\n { 'pr-2': !valueSet && !open && outlined },\n ]\"\n >\n <slot\n name=\"activator.label\"\n v-bind=\"{ value }\"\n >\n {{ label }}\n </slot>\n <span\n v-if=\"required\"\n :class=\"ui.required()\"\n >\n ﹡\n </span>\n </span>\n <div\n data-id=\"value\"\n :class=\"ui.value()\"\n >\n <!--\n Placeholder slot occupies the same flex space the input takes\n when focused, so transitioning into the focused state doesn't\n shift the activator's content. pointer-events-none is required\n so clicks pass through to the activator (otherwise the slot\n swallows the first click and the menu needs two taps to open).\n -->\n <div\n v-if=\"!valueSet && !searchInputFocused && slots.placeholder\"\n data-id=\"placeholder\"\n class=\"flex-1 min-w-0 pointer-events-none\"\n >\n <slot\n name=\"placeholder\"\n v-bind=\"{ disabled, readOnly }\"\n />\n </div>\n <template\n v-for=\"(item, i) in value\"\n :key=\"getIdentifier(item)\"\n >\n <RuiChip\n v-if=\"chips\"\n :key=\"getTextToken(getIdentifier(item))\"\n tabindex=\"-1\"\n :size=\"dense ? 'sm' : 'md'\"\n closeable\n :class=\"{ 'leading-3': dense }\"\n clickable\n v-bind=\"chipAttrs(item, i)\"\n >\n <div class=\"flex\">\n <slot\n name=\"selection.prepend\"\n :index=\"i\"\n v-bind=\"{ item }\"\n />\n <slot\n :index=\"i\"\n name=\"selection\"\n v-bind=\"{ item, chipAttrs: chipAttrs(item, i) }\"\n >\n {{ getText(item) }}\n </slot>\n </div>\n </RuiChip>\n <div\n v-else-if=\"\n multiple\n || (!searchInputFocused && (slots['selection.prepend'] || slots.selection))\n \"\n :class=\"hideSelectionWrapper ? 'contents' : 'flex'\"\n >\n <slot\n name=\"selection.prepend\"\n :index=\"i\"\n v-bind=\"{ item }\"\n />\n <slot\n v-if=\"multiple || slots.selection\"\n :index=\"i\"\n name=\"selection\"\n v-bind=\"{ item, chipAttrs: chipAttrs(item, i) }\"\n >\n {{ getText(item) }}\n </slot>\n </div>\n </template>\n <input\n ref=\"textInput\"\n :disabled=\"disabled\"\n :value=\"internalSearch\"\n class=\"bg-transparent outline-none\"\n type=\"text\"\n :placeholder=\"usedPlaceholder\"\n :class=\"[focusInputClass, { hidden: hideSearchInput }]\"\n :aria-invalid=\"hasError\"\n aria-autocomplete=\"list\"\n @keydown.delete=\"onInputDeletePressed()\"\n @input.stop=\"updateSearchInput($event)\"\n @focus=\"focusOnInputFocused()\"\n />\n </div>\n\n <RuiButton\n v-if=\"clearable && valueSet && !disabled\"\n variant=\"text\"\n icon\n size=\"sm\"\n tabindex=\"-1\"\n color=\"error\"\n data-id=\"clear\"\n :class=\"[\n ui.clear(),\n focusAnyFocused && '!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 @click=\"arrowClicked($event)\"\n >\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n\n <RuiProgress\n v-if=\"loading\"\n :class=\"ui.progress()\"\n color=\"primary\"\n thickness=\"3\"\n variant=\"indeterminate\"\n />\n </div>\n <fieldset\n v-if=\"outlined\"\n :class=\"ui.fieldset()\"\n >\n <legend :class=\"ui.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </slot>\n </template>\n <template #default=\"{ width }\">\n <div ref=\"menuWrapperRef\">\n <div\n v-if=\"optionsWithSelectedHidden.length > 0\"\n :ref=\"containerProps.ref\"\n :class=\"ui.menu({ class: cn(classNames?.menu) ?? menuClass })\"\n :style=\"[containerProps.style, { width: `${width}px`, minWidth: menuWidth, minHeight: `${menuMinHeight}px` }]\"\n @scroll=\"containerProps.onScroll\"\n @keydown.up.prevent=\"moveHighlight(true)\"\n @keydown.down.prevent=\"moveHighlight(false)\"\n >\n <div\n v-if=\"isGrouped\"\n ref=\"menuRef\"\n >\n <template\n v-for=\"(bucket, bucketIndex) in groupedOptions\"\n :key=\"bucket.group || `group-${bucketIndex}`\"\n >\n <div\n class=\"sticky top-0 z-10 bg-white dark:bg-rui-grey-900\"\n data-id=\"group-header\"\n >\n <slot\n name=\"group-header\"\n v-bind=\"{ group: bucket.group, items: bucket.items }\"\n >\n <div class=\"px-3 py-1 text-xs uppercase tracking-wide text-rui-text-secondary\">\n {{ bucket.group }}\n </div>\n </slot>\n </div>\n <RuiButton\n v-for=\"item in bucket.items\"\n :key=\"getIdentifier(item)?.toString()\"\n :active=\"isActiveItem(item)\"\n :aria-selected=\"isActiveItem(item)\"\n :size=\"dense ? 'sm' : undefined\"\n :disabled=\"isItemDisabled(item)\"\n tabindex=\"0\"\n variant=\"list\"\n :data-highlighted=\"optionsWithSelectedHidden.indexOf(item) === highlightedIndex\"\n :data-disabled=\"isItemDisabled(item) || undefined\"\n :class=\"{\n [highlightedClass]: !isActiveItem(item) && optionsWithSelectedHidden.indexOf(item) === highlightedIndex,\n }\"\n @click=\"!isItemDisabled(item) && setValue(item)\"\n >\n <template #prepend>\n <slot\n name=\"item.prepend\"\n v-bind=\"{ disabled: isItemDisabled(item), item, active: isActiveItem(item) }\"\n />\n </template>\n <slot\n name=\"item\"\n v-bind=\"{ disabled: isItemDisabled(item), item, active: isActiveItem(item) }\"\n >\n {{ getText(item) }}\n </slot>\n <template #append>\n <slot\n name=\"item.append\"\n v-bind=\"{ disabled: isItemDisabled(item), item, active: isActiveItem(item) }\"\n />\n </template>\n </RuiButton>\n </template>\n </div>\n <div\n v-else\n v-bind=\"wrapperProps\"\n ref=\"menuRef\"\n >\n <RuiButton\n v-for=\"{ item, _index } in renderedData\"\n :key=\"getIdentifier(item)?.toString()\"\n :active=\"isActiveItem(item)\"\n :aria-selected=\"isActiveItem(item)\"\n :size=\"dense ? 'sm' : undefined\"\n :disabled=\"isItemDisabled(item)\"\n tabindex=\"0\"\n variant=\"list\"\n :data-highlighted=\"highlightedIndex === _index\"\n :data-disabled=\"isItemDisabled(item) || undefined\"\n :class=\"{\n [highlightedClass]: !isActiveItem(item) && highlightedIndex === _index,\n }\"\n @click=\"!isItemDisabled(item) && setValue(item)\"\n >\n <template #prepend>\n <slot\n name=\"item.prepend\"\n v-bind=\"{ disabled: isItemDisabled(item), item, active: isActiveItem(item) }\"\n />\n </template>\n <slot\n name=\"item\"\n v-bind=\"{ disabled: isItemDisabled(item), item, active: isActiveItem(item) }\"\n >\n {{ getText(item) }}\n </slot>\n <template #append>\n <slot\n name=\"item.append\"\n v-bind=\"{ disabled: isItemDisabled(item), item, active: isActiveItem(item) }\"\n />\n </template>\n </RuiButton>\n </div>\n </div>\n\n <div\n v-else-if=\"!hideNoData\"\n :style=\"{ width: `${width}px`, minWidth: menuWidth }\"\n :class=\"classNames?.menu ?? menuClass\"\n >\n <slot name=\"no-data\">\n <div\n v-if=\"!customValue\"\n class=\"p-4\"\n data-id=\"no-data\"\n >\n {{ noDataText }}\n </div>\n </slot>\n </div>\n <!--\n The scroll container above reserves ~15px on the right for the\n scrollbar gutter. We render an equivalent right-padding here so the\n footer's content aligns with the option rows' content rather than\n the menu's outer edge. `pr-[var(--rui-scrollbar-gutter,15px)]`\n lets consumers override if their theme reserves a different width.\n -->\n <div\n v-if=\"slots.footer\"\n class=\"bg-white dark:bg-rui-grey-900 border-t border-black/[0.12] dark:border-white/[0.12] pr-[var(--rui-scrollbar-gutter,15px)] -mb-2\"\n data-id=\"footer\"\n >\n <slot name=\"footer\" />\n </div>\n </div>\n </template>\n </RuiMenu>\n</template>\n"],"mappings":""}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { VueClassValue } from '../../../types/class-value';
|
|
2
2
|
import { type AutoCompleteVariant } from '../../../components/forms/auto-complete/auto-complete-styles.js';
|
|
3
3
|
import { type MenuProps } from '../../../components/overlays/menu/RuiMenu.vue';
|
|
4
|
-
import { type KeyOfType } from '../../../composables/dropdown-menu.js';
|
|
4
|
+
import { type GroupBy, type ItemDisabled, type KeyOfType } from '../../../composables/dropdown-menu.js';
|
|
5
5
|
export type AutoCompleteModelValue<TValue> = TValue extends Array<infer U> ? U[] : TValue | undefined;
|
|
6
6
|
export interface RuiAutoCompleteClassNames {
|
|
7
7
|
root?: VueClassValue;
|
|
@@ -46,6 +46,8 @@ export interface AutoCompleteProps<TValue, TItem> {
|
|
|
46
46
|
required?: boolean;
|
|
47
47
|
hideSearchInput?: boolean;
|
|
48
48
|
hideSelectionWrapper?: boolean;
|
|
49
|
+
groupBy?: GroupBy<TItem>;
|
|
50
|
+
itemDisabled?: ItemDisabled<TItem>;
|
|
49
51
|
}
|
|
50
52
|
declare const __VLS_export: <TValue, TItem>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
51
53
|
props: import("vue").PublicProps & __VLS_PrettifyLocal<(AutoCompleteProps<TValue, TItem> & {
|
|
@@ -102,7 +104,16 @@ declare const __VLS_export: <TValue, TItem>(__VLS_props: NonNullable<Awaited<typ
|
|
|
102
104
|
item: TItem;
|
|
103
105
|
active: boolean;
|
|
104
106
|
}) => any;
|
|
107
|
+
'group-header'?: (props: {
|
|
108
|
+
group: string;
|
|
109
|
+
items: TItem[];
|
|
110
|
+
}) => any;
|
|
105
111
|
'no-data'?: () => any;
|
|
112
|
+
placeholder?: (props: {
|
|
113
|
+
disabled: boolean;
|
|
114
|
+
readOnly: boolean;
|
|
115
|
+
}) => any;
|
|
116
|
+
footer?: () => any;
|
|
106
117
|
};
|
|
107
118
|
emit: ((event: "update:modelValue", value: AutoCompleteModelValue<TValue>) => void) & ((event: "update:searchInput", value: string) => void);
|
|
108
119
|
}>) => import("vue").VNode & {
|