@rotki/ui-library 2.25.0 → 2.25.1

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/README.md CHANGED
@@ -206,6 +206,7 @@ const i18n = createI18n({
206
206
  date_after_max: 'Date cannot be after {date}',
207
207
  date_before_min: 'Date cannot be before {date}',
208
208
  date_in_future: 'The selected date cannot be in the future',
209
+ paste_unreadable: 'Could not read a date from the pasted text',
209
210
  },
210
211
  },
211
212
  },
@@ -238,6 +239,7 @@ export const RUI_I18N_KEYS = {
238
239
  dateAfterMax: 'rui.date_time_picker.date_after_max',
239
240
  dateBeforeMin: 'rui.date_time_picker.date_before_min',
240
241
  dateInFuture: 'rui.date_time_picker.date_in_future',
242
+ pasteUnreadable: 'rui.date_time_picker.paste_unreadable',
241
243
  },
242
244
  } as const;
243
245
  ```
@@ -1 +1 @@
1
- {"version":3,"file":"RuiDateTimePicker.js","names":[],"sources":["../../../src/components/date-time-picker/RuiDateTimePicker.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { ComponentPublicInstance } from 'vue';\nimport type { DateTimePickerAction, DateTimeSegmentType } from '@/components/date-time-picker/types';\nimport type { TimePickerSelection } from '@/components/time-picker/RuiTimePicker.vue';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport { dateTimePickerStyles, type DateTimePickerVariant } from '@/components/date-time-picker/date-time-picker-styles';\nimport RuiDateTimePickerMenu from '@/components/date-time-picker/RuiDateTimePickerMenu.vue';\nimport { useDateTimeSelection } from '@/components/date-time-picker/use-date-time-selection';\nimport { useInputHandler } from '@/components/date-time-picker/use-input-handler';\nimport { useKeyboardHandler } from '@/components/date-time-picker/use-keyboard-handler';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiMenu from '@/components/overlays/menu/RuiMenu.vue';\nimport { type FloatingOptions, Placement } from '@/composables/floating';\nimport { useRuiI8n } from '@/composables/use-rui-i18n';\nimport { RUI_I18N_KEYS } from '@/i18n/keys';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { getNonRootAttrs, getRootAttrs } from '@/utils/helpers';\nimport { cn } from '@/utils/tv';\n\ntype DateFormat = 'year-first' | 'month-first' | 'day-first';\n\ntype DateTimeModelType = 'date' | 'epoch-ms' | 'epoch';\n\ntype ModelValueType<T extends DateTimeModelType> = T extends 'date'\n ? Date | undefined\n : T extends 'epoch-ms'\n ? number | undefined\n : T extends 'epoch'\n ? number | undefined\n : Date | number | undefined;\n\nexport interface RuiDateTimePickerProps {\n minDate?: Date | number;\n maxDate?: Date | number | 'now';\n format?: DateFormat;\n type?: DateTimeModelType;\n accuracy?: 'minute' | 'second' | 'millisecond';\n disabled?: boolean;\n allowEmpty?: boolean;\n readonly?: boolean;\n dense?: boolean;\n label?: string;\n variant?: DateTimePickerVariant;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hideDetails?: boolean;\n required?: boolean;\n /**\n * Renders the timezone selector in the menu. Off by default: the value is\n * emitted as a date or an epoch, so the picked timezone does not survive a\n * round trip and most consumers work in local time.\n */\n showTimezone?: boolean;\n /**\n * Actions rendered in the menu footer. `clear` is only rendered when the\n * picker is `allowEmpty`. Pass an empty array to drop the footer entirely.\n */\n actions?: DateTimePickerAction[];\n /**\n * Focuses the field once it is mounted. The native attribute is ignored for\n * an input inserted into an already loaded document, which is the usual case\n * for a picker revealed by an editor or a dialog.\n */\n autofocus?: boolean;\n /**\n * Accepts an entry that stops short of the full format, filling the segments\n * it never reached from one end of the day: `start` gives a bare date\n * 00:00:00.000 and `end` gives it 23:59:59.999. Meant for a range, where the\n * two bounds want opposite ends. Left unset, an entry missing its time is not\n * a value and nothing is emitted.\n *\n * The fill runs when the user is done with the field - leaving it, pressing\n * enter, or closing the calendar - and is written back into the segments, so\n * what it decided on is on screen.\n *\n * Spelled out rather than written as `PartialTimeMode`: a consumer that hands\n * this whole interface to its own `defineProps` needs every member resolvable\n * by the SFC compiler, which does not follow the type into the package.\n */\n partialTime?: 'start' | 'end';\n}\n\ndefineOptions({\n name: 'RuiDateTimePicker',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<ModelValueType<DateTimeModelType>>({ required: true });\nconst menuOpen = defineModel<boolean>('menuOpen', { default: false });\n\nconst {\n disabled = false,\n readonly = false,\n allowEmpty = false,\n dense = false,\n type = 'epoch-ms',\n hideDetails = false,\n label,\n variant = 'default',\n hint,\n maxDate,\n minDate,\n format = 'day-first',\n accuracy = 'minute',\n errorMessages = [],\n successMessages = [],\n required = false,\n showTimezone = false,\n actions = ['now'],\n autofocus = false,\n partialTime,\n} = defineProps<RuiDateTimePickerProps>();\n\ndefineSlots<{\n 'menu-content': () => any;\n}>();\n\nconst MENU_OPTIONS: FloatingOptions = { placement: Placement.bottomStart };\n\nconst baseFormats: Record<DateFormat, string> = {\n 'day-first': 'DD/MM/YYYY HH:mm',\n 'month-first': 'MM/DD/YYYY HH:mm',\n 'year-first': 'YYYY/MM/DD HH:mm',\n};\n\nconst isOpen = ref<boolean>(false);\nconst isHovered = ref<boolean>(false);\nconst cursorPosition = ref<number>(0);\nconst currentValue = ref<number>();\n\nconst { t } = useRuiI8n();\n\nconst keys = RUI_I18N_KEYS.dateTimePicker;\n\nconst fieldLabel = computed<string>(() => label ?? t(keys.label, 'Pick a date'));\nconst clearLabel = computed<string>(() => t(keys.clearValue, 'Clear the date'));\nconst toggleLabel = computed<string>(() => (get(isOpen)\n ? t(keys.closeCalendar, 'Close the calendar')\n : t(keys.openCalendar, 'Open the calendar')));\n\nconst textInput = useTemplateRef<HTMLInputElement>('textInput');\nconst activator = useTemplateRef<HTMLDivElement>('activator');\nconst menuWrapperRef = useTemplateRef<ComponentPublicInstance>('menuWrapperRef');\nconst calendarMenuOpen = ref<boolean>(false);\n\nconst { focused: activatorFocusedWithin } = useFocusWithin(activator);\nconst { focused: menuWrapperFocusedWithin } = useFocusWithin(menuWrapperRef);\nconst { focused: searchInputFocused } = useFocus(textInput);\n\nconst anyFocused = computed<boolean>(() => get(activatorFocusedWithin) || get(menuWrapperFocusedWithin));\n\nconst dateFormat = computed<string>(() => {\n const fmt = baseFormats[format];\n if (accuracy === 'second') {\n return fmt.replace('HH:mm', 'HH:mm:ss');\n }\n else if (accuracy === 'millisecond') {\n return fmt.replace('HH:mm', 'HH:mm:ss.SSS');\n }\n return fmt;\n});\n\nconst {\n clear: clearSelection,\n commitPartialTime,\n getDateTime,\n internalErrorMessages,\n maxAllowedDate,\n minAllowedDate,\n segmentData,\n selectedDate,\n modelDay,\n modelHour,\n modelMillisecond,\n modelMinute,\n modelMonth,\n modelSecond,\n selectedTime,\n modelTimezone,\n modelYear,\n setNow,\n setToday,\n valueSet,\n} = useDateTimeSelection({\n accuracy,\n allowEmpty,\n dateFormat,\n maxDate,\n minDate,\n modelValue,\n partialTime,\n type,\n});\n\nconst { setValue, getCurrent } = useInputHandler(segmentData, currentValue);\n\nconst {\n clear: clearSegment,\n getCurrentSegment,\n handleBlur,\n handleClick,\n handleFocus,\n handleInput,\n handleInputSelection,\n handleKeyDown,\n handleMouseDown,\n handlePaste,\n selectFirstSegment,\n setSegment,\n} = useKeyboardHandler({\n accuracy,\n currentValue,\n cursorPosition,\n dateFormat,\n disabled,\n getCurrent,\n getDateTime,\n readonly,\n setValue,\n textInput,\n});\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst isOutlined = computed<boolean>(() => variant === 'outlined');\n\n/** True once any segment holds a digit, even a partially typed date. */\nconst anySegmentSet = computed<boolean>(() => [\n modelYear,\n modelMonth,\n modelDay,\n modelHour,\n modelMinute,\n modelSecond,\n modelMillisecond,\n].some(segment => isDefined(segment)));\n\n/**\n * The field's text.\n *\n * An untouched field shows its format through the placeholder rather than\n * holding the tokens as its value, where a screen reader reads them as content\n * and select-all copies them. The tokens stay while the field is focused: that\n * is when the segment machinery highlights them, and collapsing the value\n * mid-edit would blank the field under the cursor. The guard is \"no segment\n * set\" rather than `valueSet`, so blurring a half-typed date keeps what was\n * entered on screen.\n */\nconst formattedDisplay = computed<string>(() => {\n if (!get(anySegmentSet) && !get(searchInputFocused)) {\n return '';\n }\n\n let result = get(dateFormat);\n\n const replacements = [\n { pattern: 'YYYY', value: getDisplayValue(modelYear, 4) },\n { pattern: 'MM', value: getDisplayValue(modelMonth, 2) },\n { pattern: 'DD', value: getDisplayValue(modelDay, 2) },\n { pattern: 'HH', value: getDisplayValue(modelHour, 2) },\n { pattern: 'mm', value: getDisplayValue(modelMinute, 2) },\n { pattern: 'ss', value: getDisplayValue(modelSecond, 2) },\n { pattern: 'SSS', value: getDisplayValue(modelMillisecond, 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\n/**\n * An untouched field holds no value until focus expands the format tokens into\n * it. That patch overwrites the input's value, and writing a value drops any\n * selection with it, so the highlight `handleFocus` just set is gone by the\n * time the tokens are on screen and the caret ends up after them. Re-select\n * the first segment once the tokens have actually landed.\n */\nwatch(formattedDisplay, (value, previous) => {\n if (previous === '' && value !== '' && get(searchInputFocused))\n selectFirstSegment();\n}, { flush: 'post' });\n\nconst timeSelection = computed<TimePickerSelection>({\n get() {\n const type = getCurrentSegment()?.type;\n\n if (type === 'mm') {\n return 'minute';\n }\n else if (type === 'ss') {\n return 'second';\n }\n else if (type === 'SSS') {\n return 'millisecond';\n }\n return 'hour';\n },\n set(value: TimePickerSelection) {\n let segmentType: DateTimeSegmentType = 'HH';\n if (value === 'minute') {\n segmentType = 'mm';\n }\n else if (value === 'second') {\n segmentType = 'ss';\n }\n else if (value === 'millisecond') {\n segmentType = 'SSS';\n }\n\n setSegment(segmentType);\n },\n});\n\nconst float = computed<boolean>(() => (get(isOpen) || get(valueSet) || get(searchInputFocused)) && get(isOutlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float))\n return '';\n const resolved = get(fieldLabel);\n return required ? `${resolved} ﹡` : resolved;\n});\n\nconst ui = computed<ReturnType<typeof dateTimePickerStyles>>(() => dateTimePickerStyles({\n filled: variant === 'filled',\n outlined: get(isOutlined),\n float: get(float),\n opened: get(isOpen),\n hovered: get(isHovered),\n dense,\n disabled,\n readonly,\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n}));\n\nconst combinedErrorMessages = computed<string[]>(() => {\n if (!errorMessages)\n return get(internalErrorMessages);\n\n const propErrors = Array.isArray(errorMessages) ? errorMessages : [errorMessages];\n return [...propErrors, ...get(internalErrorMessages)];\n});\n\nfunction getDisplayValue(digit: Ref<number | undefined>, padding: number): string | undefined {\n return isDefined(digit) ? get(digit).toString().padStart(padding, '0') : undefined;\n}\n\nasync function setInputFocus(): Promise<void> {\n await nextTick(() => {\n set(searchInputFocused, true);\n });\n}\n\nfunction clear(segmentType?: string): void {\n if (!segmentType) {\n clearSelection();\n set(currentValue, undefined);\n return;\n }\n\n clearSegment(segmentType);\n}\n\n/**\n * An entry left incomplete is completed once the user is done with the field,\n * never while they are still filling it in: a half typed hour is not a segment\n * they left out. Leaving the field counts, unless the calendar is open, since\n * then the focus is only moving into it and the menu closing covers that.\n */\nfunction onBlur(): void {\n handleBlur();\n if (!get(isOpen))\n commitPartialTime();\n}\n\nwatch(isOpen, (open) => {\n if (!open && !get(searchInputFocused))\n commitPartialTime();\n});\n\n/**\n * Selects the clicked segment before opening the menu, so the selection is not\n * lost to the DOM changes that opening brings.\n */\nfunction handleInputClick(event: MouseEvent): void {\n handleClick(event);\n if (!get(isOpen)) {\n set(isOpen, true);\n }\n}\n\n/**\n * The menu is teleported to the body, so it never sits next to the field in\n * the tab sequence. Opening it from the keyboard therefore moves focus into\n * the calendar; opening it with the mouse leaves focus in the field, which is\n * what `disable-auto-focus` on the menu is there for.\n */\nconst focusCalendarOnOpen = ref<boolean>(false);\n\n/**\n * Asks for focus to move into the calendar. The menu is teleported and mounts\n * a frame later, so the watcher below waits for the wrapper to appear rather\n * than guessing at a number of ticks.\n */\nfunction focusCalendar(): void {\n set(focusCalendarOnOpen, true);\n}\n\nwatch(menuWrapperRef, (menu) => {\n if (!menu || !get(focusCalendarOnOpen))\n return;\n\n set(focusCalendarOnOpen, false);\n nextTick(() => {\n const el = menu.$el as HTMLElement | undefined;\n el?.querySelector<HTMLButtonElement>('[role=\"gridcell\"][tabindex=\"0\"]')?.focus({ preventScroll: true });\n });\n});\n\n/** Moves focus to the field. Exposed so a consumer can drive it from a ref. */\nfunction focus(): void {\n get(textInput)?.focus({ preventScroll: true });\n}\n\nfunction focusField(): void {\n nextTick(focus);\n}\n\nonMounted(() => {\n if (autofocus && !disabled)\n focusField();\n});\n\n/** Escape inside the calendar closes it and hands focus back to the field. */\nfunction closeFromMenu(): void {\n set(isOpen, false);\n focusField();\n}\n\n/**\n * The menu used to be mouse-only: the activator carried no key handlers, so\n * there was no way to reach the calendar from the keyboard. Alt+ArrowDown\n * opens it and Escape closes it, following the combobox convention, and the\n * append chevron is a real button for anyone who tabs to it instead.\n *\n * Enter and Escape are the other ways to be done with the field, and the keys\n * a consumer tends to close its editor on. Escape reaches that point only once\n * the calendar is already shut, and discards nothing, since the segments stay\n * on screen either way, so it commits what was entered rather than dropping it\n * when the field goes. Both write the value before the key carries on\n * bubbling, so it survives that close.\n */\nfunction onKeyDown(event: KeyboardEvent): void {\n if (disabled || readonly)\n return;\n\n if (event.altKey && event.key === 'ArrowDown') {\n event.preventDefault();\n set(isOpen, true);\n focusCalendar();\n return;\n }\n\n if (event.key === 'Escape' && get(isOpen)) {\n set(isOpen, false);\n return;\n }\n\n if (event.key === 'Enter' || event.key === 'Escape')\n commitPartialTime();\n\n handleKeyDown(event);\n}\n\nfunction arrowClicked(event: MouseEvent): void {\n if (get(isOpen)) {\n set(isOpen, false);\n event.stopPropagation();\n }\n}\n\nconst menuActions = computed<DateTimePickerAction[]>(\n () => actions.filter(action => action !== 'clear' || allowEmpty),\n);\n\nconst anyMenuOpen = computed<boolean>(() => get(isOpen) || get(calendarMenuOpen));\n\nwatch(anyMenuOpen, (value) => {\n set(menuOpen, value);\n});\n\ndefineExpose({\n focus,\n});\n</script>\n\n<template>\n <RuiMenu\n v-model=\"isOpen\"\n v-bind=\"getRootAttrs($attrs, [])\"\n :class=\"ui.wrapper({ class: cn($attrs.class) })\"\n :options=\"MENU_OPTIONS\"\n :dense=\"dense\"\n :hint=\"hint\"\n :disabled=\"disabled\"\n :success-messages=\"successMessages\"\n :error-messages=\"combinedErrorMessages\"\n :close-on-content-click=\"false\"\n :show-details=\"!hideDetails\"\n :persistent=\"calendarMenuOpen\"\n full-width\n disable-auto-focus\n >\n <template #activator=\"{ attrs, open }\">\n <div\n ref=\"activator\"\n :class=\"ui.activator()\"\n v-bind=\"{\n ...getNonRootAttrs($attrs, ['onClick', 'class']),\n ...(readonly ? {} : attrs),\n }\"\n data-id=\"activator\"\n :aria-invalid=\"hasError\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n @click=\"setInputFocus()\"\n >\n <span\n v-if=\"isOutlined && (searchInputFocused || open || valueSet)\"\n data-id=\"label\"\n :class=\"[\n ui.label(),\n { 'pr-2': !valueSet && !open && isOutlined },\n ]\"\n >\n {{ fieldLabel }}\n <span\n v-if=\"required\"\n data-id=\"required-indicator\"\n :class=\"ui.required()\"\n >\n ﹡\n </span>\n </span>\n\n <span :class=\"ui.iconPrepend()\">\n <RuiIcon\n class=\"text-rui-text-secondary transition\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-calendar-days\"\n />\n </span>\n\n <div :class=\"ui.value()\">\n <input\n ref=\"textInput\"\n :disabled=\"disabled\"\n :value=\"formattedDisplay\"\n class=\"bg-transparent outline-none flex-1 min-w-0\"\n type=\"text\"\n inputmode=\"numeric\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n :placeholder=\"dateFormat\"\n :readonly=\"readonly\"\n :aria-invalid=\"hasError\"\n :aria-label=\"fieldLabel\"\n :aria-required=\"required || undefined\"\n @mousedown=\"handleMouseDown($event)\"\n @focus=\"handleFocus()\"\n @blur=\"onBlur()\"\n @select=\"handleInputSelection($event)\"\n @click.stop=\"handleInputClick($event)\"\n @keydown=\"onKeyDown($event)\"\n @paste=\"handlePaste($event)\"\n @input=\"handleInput($event)\"\n />\n </div>\n\n <RuiButton\n v-if=\"allowEmpty && valueSet && !disabled\"\n variant=\"text\"\n icon\n data-id=\"clear-button\"\n size=\"sm\"\n color=\"error\"\n :aria-label=\"clearLabel\"\n :class=\"[\n ui.clear(),\n anyFocused && '!visible',\n { 'mr-2': !dense },\n ]\"\n @click.stop.prevent=\"clear()\"\n >\n <RuiIcon\n name=\"lu-x\"\n size=\"18\"\n />\n </RuiButton>\n\n <!-- Out of the tab sequence, following the combobox pattern: the button only duplicates\n Alt+ArrowDown on the field, so leaving it in cost two stops to cross one date field\n and four to cross a from/to pair, for nothing a keyboard user could not already do.\n It stays a real button, so it keeps its label and its expanded state for anyone\n reaching it by any other means. -->\n <button\n v-if=\"!disabled && !readonly\"\n type=\"button\"\n tabindex=\"-1\"\n :class=\"ui.iconWrapper()\"\n data-id=\"append\"\n :aria-label=\"toggleLabel\"\n :aria-expanded=\"isOpen\"\n aria-haspopup=\"dialog\"\n @click=\"arrowClicked($event)\"\n >\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </button>\n <span\n v-else\n :class=\"ui.iconWrapper()\"\n data-id=\"append\"\n >\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n </div>\n <fieldset\n v-if=\"isOutlined\"\n :class=\"ui.fieldset()\"\n >\n <legend :class=\"ui.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </template>\n <template #default>\n <RuiDateTimePickerMenu\n ref=\"menuWrapperRef\"\n v-model:selected-date=\"selectedDate\"\n v-model:selected-time=\"selectedTime\"\n v-model:selected-hour=\"modelHour\"\n v-model:selected-minute=\"modelMinute\"\n v-model:selected-second=\"modelSecond\"\n v-model:selected-millisecond=\"modelMillisecond\"\n v-model:time-selection=\"timeSelection\"\n v-model:selected-timezone=\"modelTimezone\"\n v-model:calendar-menu-open=\"calendarMenuOpen\"\n :accuracy=\"accuracy\"\n :max-date=\"maxAllowedDate\"\n :min-date=\"minAllowedDate\"\n :show-timezone=\"showTimezone\"\n :actions=\"menuActions\"\n @keydown.escape=\"closeFromMenu()\"\n @set-now=\"setNow()\"\n @set-today=\"setToday()\"\n @clear=\"clear()\"\n >\n <slot name=\"menu-content\" />\n </RuiDateTimePickerMenu>\n </template>\n </RuiMenu>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"RuiDateTimePicker.js","names":[],"sources":["../../../src/components/date-time-picker/RuiDateTimePicker.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { ComponentPublicInstance } from 'vue';\nimport type { DateTimePickerAction, DateTimeSegmentType } from '@/components/date-time-picker/types';\nimport type { TimePickerSelection } from '@/components/time-picker/RuiTimePicker.vue';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport { dateTimePickerStyles, type DateTimePickerVariant } from '@/components/date-time-picker/date-time-picker-styles';\nimport RuiDateTimePickerMenu from '@/components/date-time-picker/RuiDateTimePickerMenu.vue';\nimport { useDateTimeSelection } from '@/components/date-time-picker/use-date-time-selection';\nimport { useInputHandler } from '@/components/date-time-picker/use-input-handler';\nimport { useKeyboardHandler } from '@/components/date-time-picker/use-keyboard-handler';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiMenu from '@/components/overlays/menu/RuiMenu.vue';\nimport { type FloatingOptions, Placement } from '@/composables/floating';\nimport { useRuiI8n } from '@/composables/use-rui-i18n';\nimport { RUI_I18N_KEYS } from '@/i18n/keys';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { getNonRootAttrs, getRootAttrs } from '@/utils/helpers';\nimport { cn } from '@/utils/tv';\n\ntype DateFormat = 'year-first' | 'month-first' | 'day-first';\n\ntype DateTimeModelType = 'date' | 'epoch-ms' | 'epoch';\n\ntype ModelValueType<T extends DateTimeModelType> = T extends 'date'\n ? Date | undefined\n : T extends 'epoch-ms'\n ? number | undefined\n : T extends 'epoch'\n ? number | undefined\n : Date | number | undefined;\n\nexport interface RuiDateTimePickerProps {\n minDate?: Date | number;\n maxDate?: Date | number | 'now';\n format?: DateFormat;\n type?: DateTimeModelType;\n accuracy?: 'minute' | 'second' | 'millisecond';\n disabled?: boolean;\n allowEmpty?: boolean;\n readonly?: boolean;\n dense?: boolean;\n label?: string;\n variant?: DateTimePickerVariant;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hideDetails?: boolean;\n required?: boolean;\n /**\n * Renders the timezone selector in the menu. Off by default: the value is\n * emitted as a date or an epoch, so the picked timezone does not survive a\n * round trip and most consumers work in local time.\n */\n showTimezone?: boolean;\n /**\n * Actions rendered in the menu footer. `clear` is only rendered when the\n * picker is `allowEmpty`. Pass an empty array to drop the footer entirely.\n */\n actions?: DateTimePickerAction[];\n /**\n * Focuses the field once it is mounted. The native attribute is ignored for\n * an input inserted into an already loaded document, which is the usual case\n * for a picker revealed by an editor or a dialog.\n */\n autofocus?: boolean;\n /**\n * Accepts an entry that stops short of the full format, filling the segments\n * it never reached from one end of the day: `start` gives a bare date\n * 00:00:00.000 and `end` gives it 23:59:59.999. Meant for a range, where the\n * two bounds want opposite ends. Left unset, an entry missing its time is not\n * a value and nothing is emitted.\n *\n * The fill runs when the user is done with the field - leaving it, pressing\n * enter, or closing the calendar - and is written back into the segments, so\n * what it decided on is on screen.\n *\n * Spelled out rather than written as `PartialTimeMode`: a consumer that hands\n * this whole interface to its own `defineProps` needs every member resolvable\n * by the SFC compiler, which does not follow the type into the package.\n */\n partialTime?: 'start' | 'end';\n}\n\ndefineOptions({\n name: 'RuiDateTimePicker',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<ModelValueType<DateTimeModelType>>({ required: true });\nconst menuOpen = defineModel<boolean>('menuOpen', { default: false });\n\nconst {\n disabled = false,\n readonly = false,\n allowEmpty = false,\n dense = false,\n type = 'epoch-ms',\n hideDetails = false,\n label,\n variant = 'default',\n hint,\n maxDate,\n minDate,\n format = 'day-first',\n accuracy = 'minute',\n errorMessages = [],\n successMessages = [],\n required = false,\n showTimezone = false,\n actions = ['now'],\n autofocus = false,\n partialTime,\n} = defineProps<RuiDateTimePickerProps>();\n\ndefineSlots<{\n 'menu-content': () => any;\n}>();\n\nconst MENU_OPTIONS: FloatingOptions = { placement: Placement.bottomStart };\n\nconst baseFormats: Record<DateFormat, string> = {\n 'day-first': 'DD/MM/YYYY HH:mm',\n 'month-first': 'MM/DD/YYYY HH:mm',\n 'year-first': 'YYYY/MM/DD HH:mm',\n};\n\nconst isOpen = ref<boolean>(false);\nconst isHovered = ref<boolean>(false);\nconst cursorPosition = ref<number>(0);\nconst currentValue = ref<number>();\n\nconst { t } = useRuiI8n();\n\nconst keys = RUI_I18N_KEYS.dateTimePicker;\n\nconst fieldLabel = computed<string>(() => label ?? t(keys.label, 'Pick a date'));\nconst clearLabel = computed<string>(() => t(keys.clearValue, 'Clear the date'));\nconst toggleLabel = computed<string>(() => (get(isOpen)\n ? t(keys.closeCalendar, 'Close the calendar')\n : t(keys.openCalendar, 'Open the calendar')));\n\nconst textInput = useTemplateRef<HTMLInputElement>('textInput');\nconst activator = useTemplateRef<HTMLDivElement>('activator');\nconst menuWrapperRef = useTemplateRef<ComponentPublicInstance>('menuWrapperRef');\nconst calendarMenuOpen = ref<boolean>(false);\n\nconst { focused: activatorFocusedWithin } = useFocusWithin(activator);\nconst { focused: menuWrapperFocusedWithin } = useFocusWithin(menuWrapperRef);\nconst { focused: searchInputFocused } = useFocus(textInput);\n\nconst anyFocused = computed<boolean>(() => get(activatorFocusedWithin) || get(menuWrapperFocusedWithin));\n\nconst dateFormat = computed<string>(() => {\n const fmt = baseFormats[format];\n if (accuracy === 'second') {\n return fmt.replace('HH:mm', 'HH:mm:ss');\n }\n else if (accuracy === 'millisecond') {\n return fmt.replace('HH:mm', 'HH:mm:ss.SSS');\n }\n return fmt;\n});\n\nconst {\n clear: clearSelection,\n commitPartialTime,\n getDateTime,\n internalErrorMessages,\n maxAllowedDate,\n minAllowedDate,\n segmentData,\n selectedDate,\n modelDay,\n modelHour,\n modelMillisecond,\n modelMinute,\n modelMonth,\n modelSecond,\n selectedTime,\n modelTimezone,\n modelYear,\n setNow,\n setToday,\n valueSet,\n} = useDateTimeSelection({\n accuracy,\n allowEmpty,\n dateFormat,\n maxDate,\n minDate,\n modelValue,\n partialTime,\n type,\n});\n\nconst { setValue, getCurrent } = useInputHandler(segmentData, currentValue);\n\nconst {\n clear: clearSegment,\n getCurrentSegment,\n handleBlur,\n handleClick,\n handleFocus,\n handleInput,\n handleInputSelection,\n handleKeyDown,\n handleMouseDown,\n handlePaste,\n pasteRejected,\n selectFirstSegment,\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 timezone: modelTimezone,\n});\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst isOutlined = computed<boolean>(() => variant === 'outlined');\n\n/** True once any segment holds a digit, even a partially typed date. */\nconst anySegmentSet = computed<boolean>(() => [\n modelYear,\n modelMonth,\n modelDay,\n modelHour,\n modelMinute,\n modelSecond,\n modelMillisecond,\n].some(segment => isDefined(segment)));\n\n/**\n * The field's text.\n *\n * An untouched field shows its format through the placeholder rather than\n * holding the tokens as its value, where a screen reader reads them as content\n * and select-all copies them. The tokens stay while the field is focused: that\n * is when the segment machinery highlights them, and collapsing the value\n * mid-edit would blank the field under the cursor. The guard is \"no segment\n * set\" rather than `valueSet`, so blurring a half-typed date keeps what was\n * entered on screen.\n */\nconst formattedDisplay = computed<string>(() => {\n if (!get(anySegmentSet) && !get(searchInputFocused)) {\n return '';\n }\n\n let result = get(dateFormat);\n\n const replacements = [\n { pattern: 'YYYY', value: getDisplayValue(modelYear, 4) },\n { pattern: 'MM', value: getDisplayValue(modelMonth, 2) },\n { pattern: 'DD', value: getDisplayValue(modelDay, 2) },\n { pattern: 'HH', value: getDisplayValue(modelHour, 2) },\n { pattern: 'mm', value: getDisplayValue(modelMinute, 2) },\n { pattern: 'ss', value: getDisplayValue(modelSecond, 2) },\n { pattern: 'SSS', value: getDisplayValue(modelMillisecond, 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\n/**\n * An untouched field holds no value until focus expands the format tokens into\n * it. That patch overwrites the input's value, and writing a value drops any\n * selection with it, so the highlight `handleFocus` just set is gone by the\n * time the tokens are on screen and the caret ends up after them. Re-select\n * the first segment once the tokens have actually landed.\n */\nwatch(formattedDisplay, (value, previous) => {\n if (previous === '' && value !== '' && get(searchInputFocused))\n selectFirstSegment();\n}, { flush: 'post' });\n\nconst timeSelection = computed<TimePickerSelection>({\n get() {\n const type = getCurrentSegment()?.type;\n\n if (type === 'mm') {\n return 'minute';\n }\n else if (type === 'ss') {\n return 'second';\n }\n else if (type === 'SSS') {\n return 'millisecond';\n }\n return 'hour';\n },\n set(value: TimePickerSelection) {\n let segmentType: DateTimeSegmentType = 'HH';\n if (value === 'minute') {\n segmentType = 'mm';\n }\n else if (value === 'second') {\n segmentType = 'ss';\n }\n else if (value === 'millisecond') {\n segmentType = 'SSS';\n }\n\n setSegment(segmentType);\n },\n});\n\nconst float = computed<boolean>(() => (get(isOpen) || get(valueSet) || get(searchInputFocused)) && get(isOutlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float))\n return '';\n const resolved = get(fieldLabel);\n return required ? `${resolved} ﹡` : resolved;\n});\n\nconst ui = computed<ReturnType<typeof dateTimePickerStyles>>(() => dateTimePickerStyles({\n filled: variant === 'filled',\n outlined: get(isOutlined),\n float: get(float),\n opened: get(isOpen),\n hovered: get(isHovered),\n dense,\n disabled,\n readonly,\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n}));\n\nconst pasteMessage = computed<string>(() => (get(pasteRejected)\n ? t(keys.pasteUnreadable, 'Could not read a date from the pasted text')\n : ''));\n\n/**\n * The paste message goes first: the details show one message, and it answers\n * what the user just did, which a standing validation error does not.\n */\nconst combinedErrorMessages = computed<string[]>(() => {\n const paste = get(pasteMessage) ? [get(pasteMessage)] : [];\n if (!errorMessages)\n return [...paste, ...get(internalErrorMessages)];\n\n const propErrors = Array.isArray(errorMessages) ? errorMessages : [errorMessages];\n return [...paste, ...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\n/**\n * An entry left incomplete is completed once the user is done with the field,\n * never while they are still filling it in: a half typed hour is not a segment\n * they left out. Leaving the field counts, unless the calendar is open, since\n * then the focus is only moving into it and the menu closing covers that.\n */\nfunction onBlur(): void {\n handleBlur();\n if (!get(isOpen))\n commitPartialTime();\n}\n\nwatch(isOpen, (open) => {\n if (!open && !get(searchInputFocused))\n commitPartialTime();\n});\n\n/**\n * Selects the clicked segment before opening the menu, so the selection is not\n * lost to the DOM changes that opening brings.\n */\nfunction handleInputClick(event: MouseEvent): void {\n handleClick(event);\n if (!get(isOpen)) {\n set(isOpen, true);\n }\n}\n\n/**\n * The menu is teleported to the body, so it never sits next to the field in\n * the tab sequence. Opening it from the keyboard therefore moves focus into\n * the calendar; opening it with the mouse leaves focus in the field, which is\n * what `disable-auto-focus` on the menu is there for.\n */\nconst focusCalendarOnOpen = ref<boolean>(false);\n\n/**\n * Asks for focus to move into the calendar. The menu is teleported and mounts\n * a frame later, so the watcher below waits for the wrapper to appear rather\n * than guessing at a number of ticks.\n */\nfunction focusCalendar(): void {\n set(focusCalendarOnOpen, true);\n}\n\nwatch(menuWrapperRef, (menu) => {\n if (!menu || !get(focusCalendarOnOpen))\n return;\n\n set(focusCalendarOnOpen, false);\n nextTick(() => {\n const el = menu.$el as HTMLElement | undefined;\n el?.querySelector<HTMLButtonElement>('[role=\"gridcell\"][tabindex=\"0\"]')?.focus({ preventScroll: true });\n });\n});\n\n/** Moves focus to the field. Exposed so a consumer can drive it from a ref. */\nfunction focus(): void {\n get(textInput)?.focus({ preventScroll: true });\n}\n\nfunction focusField(): void {\n nextTick(focus);\n}\n\nonMounted(() => {\n if (autofocus && !disabled)\n focusField();\n});\n\n/** Escape inside the calendar closes it and hands focus back to the field. */\nfunction closeFromMenu(): void {\n set(isOpen, false);\n focusField();\n}\n\n/**\n * The menu used to be mouse-only: the activator carried no key handlers, so\n * there was no way to reach the calendar from the keyboard. Alt+ArrowDown\n * opens it and Escape closes it, following the combobox convention, and the\n * append chevron is a real button for anyone who tabs to it instead.\n *\n * Enter and Escape are the other ways to be done with the field, and the keys\n * a consumer tends to close its editor on. Escape reaches that point only once\n * the calendar is already shut, and discards nothing, since the segments stay\n * on screen either way, so it commits what was entered rather than dropping it\n * when the field goes. Both write the value before the key carries on\n * bubbling, so it survives that close.\n */\nfunction onKeyDown(event: KeyboardEvent): void {\n if (disabled || readonly)\n return;\n\n if (event.altKey && event.key === 'ArrowDown') {\n event.preventDefault();\n set(isOpen, true);\n focusCalendar();\n return;\n }\n\n if (event.key === 'Escape' && get(isOpen)) {\n set(isOpen, false);\n return;\n }\n\n if (event.key === 'Enter' || event.key === 'Escape')\n commitPartialTime();\n\n handleKeyDown(event);\n}\n\nfunction arrowClicked(event: MouseEvent): void {\n if (get(isOpen)) {\n set(isOpen, false);\n event.stopPropagation();\n }\n}\n\nconst menuActions = computed<DateTimePickerAction[]>(\n () => actions.filter(action => action !== 'clear' || allowEmpty),\n);\n\nconst anyMenuOpen = computed<boolean>(() => get(isOpen) || get(calendarMenuOpen));\n\nwatch(anyMenuOpen, (value) => {\n set(menuOpen, value);\n});\n\ndefineExpose({\n focus,\n});\n</script>\n\n<template>\n <RuiMenu\n v-model=\"isOpen\"\n v-bind=\"getRootAttrs($attrs, [])\"\n :class=\"ui.wrapper({ class: cn($attrs.class) })\"\n :options=\"MENU_OPTIONS\"\n :dense=\"dense\"\n :hint=\"hint\"\n :disabled=\"disabled\"\n :success-messages=\"successMessages\"\n :error-messages=\"combinedErrorMessages\"\n :close-on-content-click=\"false\"\n :show-details=\"!hideDetails\"\n :persistent=\"calendarMenuOpen\"\n full-width\n disable-auto-focus\n >\n <template #activator=\"{ attrs, open }\">\n <div\n ref=\"activator\"\n :class=\"ui.activator()\"\n v-bind=\"{\n ...getNonRootAttrs($attrs, ['onClick', 'class']),\n ...(readonly ? {} : attrs),\n }\"\n data-id=\"activator\"\n :aria-invalid=\"hasError\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n @click=\"setInputFocus()\"\n >\n <span\n v-if=\"isOutlined && (searchInputFocused || open || valueSet)\"\n data-id=\"label\"\n :class=\"[\n ui.label(),\n { 'pr-2': !valueSet && !open && isOutlined },\n ]\"\n >\n {{ fieldLabel }}\n <span\n v-if=\"required\"\n data-id=\"required-indicator\"\n :class=\"ui.required()\"\n >\n ﹡\n </span>\n </span>\n\n <span :class=\"ui.iconPrepend()\">\n <RuiIcon\n class=\"text-rui-text-secondary transition\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-calendar-days\"\n />\n </span>\n\n <div :class=\"ui.value()\">\n <input\n ref=\"textInput\"\n :disabled=\"disabled\"\n :value=\"formattedDisplay\"\n class=\"bg-transparent outline-none flex-1 min-w-0\"\n type=\"text\"\n inputmode=\"numeric\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n :placeholder=\"dateFormat\"\n :readonly=\"readonly\"\n :aria-invalid=\"hasError\"\n :aria-label=\"fieldLabel\"\n :aria-required=\"required || undefined\"\n @mousedown=\"handleMouseDown($event)\"\n @focus=\"handleFocus()\"\n @blur=\"onBlur()\"\n @select=\"handleInputSelection($event)\"\n @click.stop=\"handleInputClick($event)\"\n @keydown=\"onKeyDown($event)\"\n @paste=\"handlePaste($event)\"\n @input=\"handleInput($event)\"\n />\n <!-- The details line is not a live region, so a rejected paste is announced from here -->\n <span\n class=\"sr-only\"\n aria-live=\"polite\"\n data-id=\"paste-status\"\n >\n {{ pasteMessage }}\n </span>\n </div>\n\n <RuiButton\n v-if=\"allowEmpty && valueSet && !disabled\"\n variant=\"text\"\n icon\n data-id=\"clear-button\"\n size=\"sm\"\n color=\"error\"\n :aria-label=\"clearLabel\"\n :class=\"[\n ui.clear(),\n anyFocused && '!visible',\n { 'mr-2': !dense },\n ]\"\n @click.stop.prevent=\"clear()\"\n >\n <RuiIcon\n name=\"lu-x\"\n size=\"18\"\n />\n </RuiButton>\n\n <!-- Out of the tab sequence, following the combobox pattern: the button only duplicates\n Alt+ArrowDown on the field, so leaving it in cost two stops to cross one date field\n and four to cross a from/to pair, for nothing a keyboard user could not already do.\n It stays a real button, so it keeps its label and its expanded state for anyone\n reaching it by any other means. -->\n <button\n v-if=\"!disabled && !readonly\"\n type=\"button\"\n tabindex=\"-1\"\n :class=\"ui.iconWrapper()\"\n data-id=\"append\"\n :aria-label=\"toggleLabel\"\n :aria-expanded=\"isOpen\"\n aria-haspopup=\"dialog\"\n @click=\"arrowClicked($event)\"\n >\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </button>\n <span\n v-else\n :class=\"ui.iconWrapper()\"\n data-id=\"append\"\n >\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n </div>\n <fieldset\n v-if=\"isOutlined\"\n :class=\"ui.fieldset()\"\n >\n <legend :class=\"ui.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </template>\n <template #default>\n <RuiDateTimePickerMenu\n ref=\"menuWrapperRef\"\n v-model:selected-date=\"selectedDate\"\n v-model:selected-time=\"selectedTime\"\n v-model:selected-hour=\"modelHour\"\n v-model:selected-minute=\"modelMinute\"\n v-model:selected-second=\"modelSecond\"\n v-model:selected-millisecond=\"modelMillisecond\"\n v-model:time-selection=\"timeSelection\"\n v-model:selected-timezone=\"modelTimezone\"\n v-model:calendar-menu-open=\"calendarMenuOpen\"\n :accuracy=\"accuracy\"\n :max-date=\"maxAllowedDate\"\n :min-date=\"minAllowedDate\"\n :show-timezone=\"showTimezone\"\n :actions=\"menuActions\"\n @keydown.escape=\"closeFromMenu()\"\n @set-now=\"setNow()\"\n @set-today=\"setToday()\"\n @clear=\"clear()\"\n >\n <slot name=\"menu-content\" />\n </RuiDateTimePickerMenu>\n </template>\n </RuiMenu>\n</template>\n"],"mappings":""}
@@ -26,7 +26,12 @@ var _hoisted_2 = [
26
26
  "aria-label",
27
27
  "aria-required"
28
28
  ];
29
- var _hoisted_3 = ["aria-label", "aria-expanded"];
29
+ var _hoisted_3 = {
30
+ class: "sr-only",
31
+ "aria-live": "polite",
32
+ "data-id": "paste-status"
33
+ };
34
+ var _hoisted_4 = ["aria-label", "aria-expanded"];
30
35
  var RuiDateTimePicker_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
31
36
  name: "RuiDateTimePicker",
32
37
  inheritAttrs: false,
@@ -129,7 +134,7 @@ var RuiDateTimePicker_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
129
134
  type: __props.type
130
135
  });
131
136
  const { setValue, getCurrent } = useInputHandler(segmentData, currentValue);
132
- const { clear: clearSegment, getCurrentSegment, handleBlur, handleClick, handleFocus, handleInput, handleInputSelection, handleKeyDown, handleMouseDown, handlePaste, selectFirstSegment, setSegment } = useKeyboardHandler({
137
+ const { clear: clearSegment, getCurrentSegment, handleBlur, handleClick, handleFocus, handleInput, handleInputSelection, handleKeyDown, handleMouseDown, handlePaste, pasteRejected, selectFirstSegment, setSegment } = useKeyboardHandler({
133
138
  accuracy: __props.accuracy,
134
139
  currentValue,
135
140
  cursorPosition,
@@ -139,7 +144,8 @@ var RuiDateTimePicker_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
139
144
  getDateTime,
140
145
  readonly: __props.readonly,
141
146
  setValue,
142
- textInput
147
+ textInput,
148
+ timezone: modelTimezone
143
149
  });
144
150
  const { hasError, hasSuccess } = useFormTextDetail(() => __props.errorMessages, () => __props.successMessages);
145
151
  const isOutlined = computed(() => __props.variant === "outlined");
@@ -244,9 +250,20 @@ var RuiDateTimePicker_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
244
250
  hasError: get$1(hasError),
245
251
  hasSuccess: get$1(hasSuccess) && !get$1(hasError)
246
252
  }));
253
+ const pasteMessage = computed(() => get$1(pasteRejected) ? t(keys.pasteUnreadable, "Could not read a date from the pasted text") : "");
254
+ /**
255
+ * The paste message goes first: the details show one message, and it answers
256
+ * what the user just did, which a standing validation error does not.
257
+ */
247
258
  const combinedErrorMessages = computed(() => {
248
- if (!__props.errorMessages) return get$1(internalErrorMessages);
249
- return [...Array.isArray(__props.errorMessages) ? __props.errorMessages : [__props.errorMessages], ...get$1(internalErrorMessages)];
259
+ const paste = get$1(pasteMessage) ? [get$1(pasteMessage)] : [];
260
+ if (!__props.errorMessages) return [...paste, ...get$1(internalErrorMessages)];
261
+ const propErrors = Array.isArray(__props.errorMessages) ? __props.errorMessages : [__props.errorMessages];
262
+ return [
263
+ ...paste,
264
+ ...propErrors,
265
+ ...get$1(internalErrorMessages)
266
+ ];
250
267
  });
251
268
  function getDisplayValue(digit, padding) {
252
269
  return isDefined(digit) ? get$1(digit).toString().padStart(padding, "0") : void 0;
@@ -433,7 +450,7 @@ var RuiDateTimePicker_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
433
450
  onKeydown: _cache[5] || (_cache[5] = ($event) => onKeyDown($event)),
434
451
  onPaste: _cache[6] || (_cache[6] = ($event) => unref(handlePaste)($event)),
435
452
  onInput: _cache[7] || (_cache[7] = ($event) => unref(handleInput)($event))
436
- }, null, 40, _hoisted_2)], 2),
453
+ }, null, 40, _hoisted_2), createElementVNode("span", _hoisted_3, toDisplayString(unref(pasteMessage)), 1)], 2),
437
454
  __props.allowEmpty && unref(valueSet) && !__props.disabled ? (openBlock(), createBlock(RuiButton_default, {
438
455
  key: 1,
439
456
  variant: "text",
@@ -469,7 +486,7 @@ var RuiDateTimePicker_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
469
486
  class: normalizeClass(unref(ui).icon()),
470
487
  size: __props.dense ? 16 : 24,
471
488
  name: "lu-chevron-down"
472
- }, null, 8, ["class", "size"])], 10, _hoisted_3)) : (openBlock(), createElementBlock("span", {
489
+ }, null, 8, ["class", "size"])], 10, _hoisted_4)) : (openBlock(), createElementBlock("span", {
473
490
  key: 3,
474
491
  class: normalizeClass(unref(ui).iconWrapper()),
475
492
  "data-id": "append"
@@ -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 { ComponentPublicInstance } from 'vue';\nimport type { DateTimePickerAction, DateTimeSegmentType } from '@/components/date-time-picker/types';\nimport type { TimePickerSelection } from '@/components/time-picker/RuiTimePicker.vue';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport { dateTimePickerStyles, type DateTimePickerVariant } from '@/components/date-time-picker/date-time-picker-styles';\nimport RuiDateTimePickerMenu from '@/components/date-time-picker/RuiDateTimePickerMenu.vue';\nimport { useDateTimeSelection } from '@/components/date-time-picker/use-date-time-selection';\nimport { useInputHandler } from '@/components/date-time-picker/use-input-handler';\nimport { useKeyboardHandler } from '@/components/date-time-picker/use-keyboard-handler';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiMenu from '@/components/overlays/menu/RuiMenu.vue';\nimport { type FloatingOptions, Placement } from '@/composables/floating';\nimport { useRuiI8n } from '@/composables/use-rui-i18n';\nimport { RUI_I18N_KEYS } from '@/i18n/keys';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { getNonRootAttrs, getRootAttrs } from '@/utils/helpers';\nimport { cn } from '@/utils/tv';\n\ntype DateFormat = 'year-first' | 'month-first' | 'day-first';\n\ntype DateTimeModelType = 'date' | 'epoch-ms' | 'epoch';\n\ntype ModelValueType<T extends DateTimeModelType> = T extends 'date'\n ? Date | undefined\n : T extends 'epoch-ms'\n ? number | undefined\n : T extends 'epoch'\n ? number | undefined\n : Date | number | undefined;\n\nexport interface RuiDateTimePickerProps {\n minDate?: Date | number;\n maxDate?: Date | number | 'now';\n format?: DateFormat;\n type?: DateTimeModelType;\n accuracy?: 'minute' | 'second' | 'millisecond';\n disabled?: boolean;\n allowEmpty?: boolean;\n readonly?: boolean;\n dense?: boolean;\n label?: string;\n variant?: DateTimePickerVariant;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hideDetails?: boolean;\n required?: boolean;\n /**\n * Renders the timezone selector in the menu. Off by default: the value is\n * emitted as a date or an epoch, so the picked timezone does not survive a\n * round trip and most consumers work in local time.\n */\n showTimezone?: boolean;\n /**\n * Actions rendered in the menu footer. `clear` is only rendered when the\n * picker is `allowEmpty`. Pass an empty array to drop the footer entirely.\n */\n actions?: DateTimePickerAction[];\n /**\n * Focuses the field once it is mounted. The native attribute is ignored for\n * an input inserted into an already loaded document, which is the usual case\n * for a picker revealed by an editor or a dialog.\n */\n autofocus?: boolean;\n /**\n * Accepts an entry that stops short of the full format, filling the segments\n * it never reached from one end of the day: `start` gives a bare date\n * 00:00:00.000 and `end` gives it 23:59:59.999. Meant for a range, where the\n * two bounds want opposite ends. Left unset, an entry missing its time is not\n * a value and nothing is emitted.\n *\n * The fill runs when the user is done with the field - leaving it, pressing\n * enter, or closing the calendar - and is written back into the segments, so\n * what it decided on is on screen.\n *\n * Spelled out rather than written as `PartialTimeMode`: a consumer that hands\n * this whole interface to its own `defineProps` needs every member resolvable\n * by the SFC compiler, which does not follow the type into the package.\n */\n partialTime?: 'start' | 'end';\n}\n\ndefineOptions({\n name: 'RuiDateTimePicker',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<ModelValueType<DateTimeModelType>>({ required: true });\nconst menuOpen = defineModel<boolean>('menuOpen', { default: false });\n\nconst {\n disabled = false,\n readonly = false,\n allowEmpty = false,\n dense = false,\n type = 'epoch-ms',\n hideDetails = false,\n label,\n variant = 'default',\n hint,\n maxDate,\n minDate,\n format = 'day-first',\n accuracy = 'minute',\n errorMessages = [],\n successMessages = [],\n required = false,\n showTimezone = false,\n actions = ['now'],\n autofocus = false,\n partialTime,\n} = defineProps<RuiDateTimePickerProps>();\n\ndefineSlots<{\n 'menu-content': () => any;\n}>();\n\nconst MENU_OPTIONS: FloatingOptions = { placement: Placement.bottomStart };\n\nconst baseFormats: Record<DateFormat, string> = {\n 'day-first': 'DD/MM/YYYY HH:mm',\n 'month-first': 'MM/DD/YYYY HH:mm',\n 'year-first': 'YYYY/MM/DD HH:mm',\n};\n\nconst isOpen = ref<boolean>(false);\nconst isHovered = ref<boolean>(false);\nconst cursorPosition = ref<number>(0);\nconst currentValue = ref<number>();\n\nconst { t } = useRuiI8n();\n\nconst keys = RUI_I18N_KEYS.dateTimePicker;\n\nconst fieldLabel = computed<string>(() => label ?? t(keys.label, 'Pick a date'));\nconst clearLabel = computed<string>(() => t(keys.clearValue, 'Clear the date'));\nconst toggleLabel = computed<string>(() => (get(isOpen)\n ? t(keys.closeCalendar, 'Close the calendar')\n : t(keys.openCalendar, 'Open the calendar')));\n\nconst textInput = useTemplateRef<HTMLInputElement>('textInput');\nconst activator = useTemplateRef<HTMLDivElement>('activator');\nconst menuWrapperRef = useTemplateRef<ComponentPublicInstance>('menuWrapperRef');\nconst calendarMenuOpen = ref<boolean>(false);\n\nconst { focused: activatorFocusedWithin } = useFocusWithin(activator);\nconst { focused: menuWrapperFocusedWithin } = useFocusWithin(menuWrapperRef);\nconst { focused: searchInputFocused } = useFocus(textInput);\n\nconst anyFocused = computed<boolean>(() => get(activatorFocusedWithin) || get(menuWrapperFocusedWithin));\n\nconst dateFormat = computed<string>(() => {\n const fmt = baseFormats[format];\n if (accuracy === 'second') {\n return fmt.replace('HH:mm', 'HH:mm:ss');\n }\n else if (accuracy === 'millisecond') {\n return fmt.replace('HH:mm', 'HH:mm:ss.SSS');\n }\n return fmt;\n});\n\nconst {\n clear: clearSelection,\n commitPartialTime,\n getDateTime,\n internalErrorMessages,\n maxAllowedDate,\n minAllowedDate,\n segmentData,\n selectedDate,\n modelDay,\n modelHour,\n modelMillisecond,\n modelMinute,\n modelMonth,\n modelSecond,\n selectedTime,\n modelTimezone,\n modelYear,\n setNow,\n setToday,\n valueSet,\n} = useDateTimeSelection({\n accuracy,\n allowEmpty,\n dateFormat,\n maxDate,\n minDate,\n modelValue,\n partialTime,\n type,\n});\n\nconst { setValue, getCurrent } = useInputHandler(segmentData, currentValue);\n\nconst {\n clear: clearSegment,\n getCurrentSegment,\n handleBlur,\n handleClick,\n handleFocus,\n handleInput,\n handleInputSelection,\n handleKeyDown,\n handleMouseDown,\n handlePaste,\n selectFirstSegment,\n setSegment,\n} = useKeyboardHandler({\n accuracy,\n currentValue,\n cursorPosition,\n dateFormat,\n disabled,\n getCurrent,\n getDateTime,\n readonly,\n setValue,\n textInput,\n});\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst isOutlined = computed<boolean>(() => variant === 'outlined');\n\n/** True once any segment holds a digit, even a partially typed date. */\nconst anySegmentSet = computed<boolean>(() => [\n modelYear,\n modelMonth,\n modelDay,\n modelHour,\n modelMinute,\n modelSecond,\n modelMillisecond,\n].some(segment => isDefined(segment)));\n\n/**\n * The field's text.\n *\n * An untouched field shows its format through the placeholder rather than\n * holding the tokens as its value, where a screen reader reads them as content\n * and select-all copies them. The tokens stay while the field is focused: that\n * is when the segment machinery highlights them, and collapsing the value\n * mid-edit would blank the field under the cursor. The guard is \"no segment\n * set\" rather than `valueSet`, so blurring a half-typed date keeps what was\n * entered on screen.\n */\nconst formattedDisplay = computed<string>(() => {\n if (!get(anySegmentSet) && !get(searchInputFocused)) {\n return '';\n }\n\n let result = get(dateFormat);\n\n const replacements = [\n { pattern: 'YYYY', value: getDisplayValue(modelYear, 4) },\n { pattern: 'MM', value: getDisplayValue(modelMonth, 2) },\n { pattern: 'DD', value: getDisplayValue(modelDay, 2) },\n { pattern: 'HH', value: getDisplayValue(modelHour, 2) },\n { pattern: 'mm', value: getDisplayValue(modelMinute, 2) },\n { pattern: 'ss', value: getDisplayValue(modelSecond, 2) },\n { pattern: 'SSS', value: getDisplayValue(modelMillisecond, 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\n/**\n * An untouched field holds no value until focus expands the format tokens into\n * it. That patch overwrites the input's value, and writing a value drops any\n * selection with it, so the highlight `handleFocus` just set is gone by the\n * time the tokens are on screen and the caret ends up after them. Re-select\n * the first segment once the tokens have actually landed.\n */\nwatch(formattedDisplay, (value, previous) => {\n if (previous === '' && value !== '' && get(searchInputFocused))\n selectFirstSegment();\n}, { flush: 'post' });\n\nconst timeSelection = computed<TimePickerSelection>({\n get() {\n const type = getCurrentSegment()?.type;\n\n if (type === 'mm') {\n return 'minute';\n }\n else if (type === 'ss') {\n return 'second';\n }\n else if (type === 'SSS') {\n return 'millisecond';\n }\n return 'hour';\n },\n set(value: TimePickerSelection) {\n let segmentType: DateTimeSegmentType = 'HH';\n if (value === 'minute') {\n segmentType = 'mm';\n }\n else if (value === 'second') {\n segmentType = 'ss';\n }\n else if (value === 'millisecond') {\n segmentType = 'SSS';\n }\n\n setSegment(segmentType);\n },\n});\n\nconst float = computed<boolean>(() => (get(isOpen) || get(valueSet) || get(searchInputFocused)) && get(isOutlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float))\n return '';\n const resolved = get(fieldLabel);\n return required ? `${resolved} ﹡` : resolved;\n});\n\nconst ui = computed<ReturnType<typeof dateTimePickerStyles>>(() => dateTimePickerStyles({\n filled: variant === 'filled',\n outlined: get(isOutlined),\n float: get(float),\n opened: get(isOpen),\n hovered: get(isHovered),\n dense,\n disabled,\n readonly,\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n}));\n\nconst combinedErrorMessages = computed<string[]>(() => {\n if (!errorMessages)\n return get(internalErrorMessages);\n\n const propErrors = Array.isArray(errorMessages) ? errorMessages : [errorMessages];\n return [...propErrors, ...get(internalErrorMessages)];\n});\n\nfunction getDisplayValue(digit: Ref<number | undefined>, padding: number): string | undefined {\n return isDefined(digit) ? get(digit).toString().padStart(padding, '0') : undefined;\n}\n\nasync function setInputFocus(): Promise<void> {\n await nextTick(() => {\n set(searchInputFocused, true);\n });\n}\n\nfunction clear(segmentType?: string): void {\n if (!segmentType) {\n clearSelection();\n set(currentValue, undefined);\n return;\n }\n\n clearSegment(segmentType);\n}\n\n/**\n * An entry left incomplete is completed once the user is done with the field,\n * never while they are still filling it in: a half typed hour is not a segment\n * they left out. Leaving the field counts, unless the calendar is open, since\n * then the focus is only moving into it and the menu closing covers that.\n */\nfunction onBlur(): void {\n handleBlur();\n if (!get(isOpen))\n commitPartialTime();\n}\n\nwatch(isOpen, (open) => {\n if (!open && !get(searchInputFocused))\n commitPartialTime();\n});\n\n/**\n * Selects the clicked segment before opening the menu, so the selection is not\n * lost to the DOM changes that opening brings.\n */\nfunction handleInputClick(event: MouseEvent): void {\n handleClick(event);\n if (!get(isOpen)) {\n set(isOpen, true);\n }\n}\n\n/**\n * The menu is teleported to the body, so it never sits next to the field in\n * the tab sequence. Opening it from the keyboard therefore moves focus into\n * the calendar; opening it with the mouse leaves focus in the field, which is\n * what `disable-auto-focus` on the menu is there for.\n */\nconst focusCalendarOnOpen = ref<boolean>(false);\n\n/**\n * Asks for focus to move into the calendar. The menu is teleported and mounts\n * a frame later, so the watcher below waits for the wrapper to appear rather\n * than guessing at a number of ticks.\n */\nfunction focusCalendar(): void {\n set(focusCalendarOnOpen, true);\n}\n\nwatch(menuWrapperRef, (menu) => {\n if (!menu || !get(focusCalendarOnOpen))\n return;\n\n set(focusCalendarOnOpen, false);\n nextTick(() => {\n const el = menu.$el as HTMLElement | undefined;\n el?.querySelector<HTMLButtonElement>('[role=\"gridcell\"][tabindex=\"0\"]')?.focus({ preventScroll: true });\n });\n});\n\n/** Moves focus to the field. Exposed so a consumer can drive it from a ref. */\nfunction focus(): void {\n get(textInput)?.focus({ preventScroll: true });\n}\n\nfunction focusField(): void {\n nextTick(focus);\n}\n\nonMounted(() => {\n if (autofocus && !disabled)\n focusField();\n});\n\n/** Escape inside the calendar closes it and hands focus back to the field. */\nfunction closeFromMenu(): void {\n set(isOpen, false);\n focusField();\n}\n\n/**\n * The menu used to be mouse-only: the activator carried no key handlers, so\n * there was no way to reach the calendar from the keyboard. Alt+ArrowDown\n * opens it and Escape closes it, following the combobox convention, and the\n * append chevron is a real button for anyone who tabs to it instead.\n *\n * Enter and Escape are the other ways to be done with the field, and the keys\n * a consumer tends to close its editor on. Escape reaches that point only once\n * the calendar is already shut, and discards nothing, since the segments stay\n * on screen either way, so it commits what was entered rather than dropping it\n * when the field goes. Both write the value before the key carries on\n * bubbling, so it survives that close.\n */\nfunction onKeyDown(event: KeyboardEvent): void {\n if (disabled || readonly)\n return;\n\n if (event.altKey && event.key === 'ArrowDown') {\n event.preventDefault();\n set(isOpen, true);\n focusCalendar();\n return;\n }\n\n if (event.key === 'Escape' && get(isOpen)) {\n set(isOpen, false);\n return;\n }\n\n if (event.key === 'Enter' || event.key === 'Escape')\n commitPartialTime();\n\n handleKeyDown(event);\n}\n\nfunction arrowClicked(event: MouseEvent): void {\n if (get(isOpen)) {\n set(isOpen, false);\n event.stopPropagation();\n }\n}\n\nconst menuActions = computed<DateTimePickerAction[]>(\n () => actions.filter(action => action !== 'clear' || allowEmpty),\n);\n\nconst anyMenuOpen = computed<boolean>(() => get(isOpen) || get(calendarMenuOpen));\n\nwatch(anyMenuOpen, (value) => {\n set(menuOpen, value);\n});\n\ndefineExpose({\n focus,\n});\n</script>\n\n<template>\n <RuiMenu\n v-model=\"isOpen\"\n v-bind=\"getRootAttrs($attrs, [])\"\n :class=\"ui.wrapper({ class: cn($attrs.class) })\"\n :options=\"MENU_OPTIONS\"\n :dense=\"dense\"\n :hint=\"hint\"\n :disabled=\"disabled\"\n :success-messages=\"successMessages\"\n :error-messages=\"combinedErrorMessages\"\n :close-on-content-click=\"false\"\n :show-details=\"!hideDetails\"\n :persistent=\"calendarMenuOpen\"\n full-width\n disable-auto-focus\n >\n <template #activator=\"{ attrs, open }\">\n <div\n ref=\"activator\"\n :class=\"ui.activator()\"\n v-bind=\"{\n ...getNonRootAttrs($attrs, ['onClick', 'class']),\n ...(readonly ? {} : attrs),\n }\"\n data-id=\"activator\"\n :aria-invalid=\"hasError\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n @click=\"setInputFocus()\"\n >\n <span\n v-if=\"isOutlined && (searchInputFocused || open || valueSet)\"\n data-id=\"label\"\n :class=\"[\n ui.label(),\n { 'pr-2': !valueSet && !open && isOutlined },\n ]\"\n >\n {{ fieldLabel }}\n <span\n v-if=\"required\"\n data-id=\"required-indicator\"\n :class=\"ui.required()\"\n >\n ﹡\n </span>\n </span>\n\n <span :class=\"ui.iconPrepend()\">\n <RuiIcon\n class=\"text-rui-text-secondary transition\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-calendar-days\"\n />\n </span>\n\n <div :class=\"ui.value()\">\n <input\n ref=\"textInput\"\n :disabled=\"disabled\"\n :value=\"formattedDisplay\"\n class=\"bg-transparent outline-none flex-1 min-w-0\"\n type=\"text\"\n inputmode=\"numeric\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n :placeholder=\"dateFormat\"\n :readonly=\"readonly\"\n :aria-invalid=\"hasError\"\n :aria-label=\"fieldLabel\"\n :aria-required=\"required || undefined\"\n @mousedown=\"handleMouseDown($event)\"\n @focus=\"handleFocus()\"\n @blur=\"onBlur()\"\n @select=\"handleInputSelection($event)\"\n @click.stop=\"handleInputClick($event)\"\n @keydown=\"onKeyDown($event)\"\n @paste=\"handlePaste($event)\"\n @input=\"handleInput($event)\"\n />\n </div>\n\n <RuiButton\n v-if=\"allowEmpty && valueSet && !disabled\"\n variant=\"text\"\n icon\n data-id=\"clear-button\"\n size=\"sm\"\n color=\"error\"\n :aria-label=\"clearLabel\"\n :class=\"[\n ui.clear(),\n anyFocused && '!visible',\n { 'mr-2': !dense },\n ]\"\n @click.stop.prevent=\"clear()\"\n >\n <RuiIcon\n name=\"lu-x\"\n size=\"18\"\n />\n </RuiButton>\n\n <!-- Out of the tab sequence, following the combobox pattern: the button only duplicates\n Alt+ArrowDown on the field, so leaving it in cost two stops to cross one date field\n and four to cross a from/to pair, for nothing a keyboard user could not already do.\n It stays a real button, so it keeps its label and its expanded state for anyone\n reaching it by any other means. -->\n <button\n v-if=\"!disabled && !readonly\"\n type=\"button\"\n tabindex=\"-1\"\n :class=\"ui.iconWrapper()\"\n data-id=\"append\"\n :aria-label=\"toggleLabel\"\n :aria-expanded=\"isOpen\"\n aria-haspopup=\"dialog\"\n @click=\"arrowClicked($event)\"\n >\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </button>\n <span\n v-else\n :class=\"ui.iconWrapper()\"\n data-id=\"append\"\n >\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n </div>\n <fieldset\n v-if=\"isOutlined\"\n :class=\"ui.fieldset()\"\n >\n <legend :class=\"ui.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </template>\n <template #default>\n <RuiDateTimePickerMenu\n ref=\"menuWrapperRef\"\n v-model:selected-date=\"selectedDate\"\n v-model:selected-time=\"selectedTime\"\n v-model:selected-hour=\"modelHour\"\n v-model:selected-minute=\"modelMinute\"\n v-model:selected-second=\"modelSecond\"\n v-model:selected-millisecond=\"modelMillisecond\"\n v-model:time-selection=\"timeSelection\"\n v-model:selected-timezone=\"modelTimezone\"\n v-model:calendar-menu-open=\"calendarMenuOpen\"\n :accuracy=\"accuracy\"\n :max-date=\"maxAllowedDate\"\n :min-date=\"minAllowedDate\"\n :show-timezone=\"showTimezone\"\n :actions=\"menuActions\"\n @keydown.escape=\"closeFromMenu()\"\n @set-now=\"setNow()\"\n @set-today=\"setToday()\"\n @clear=\"clear()\"\n >\n <slot name=\"menu-content\" />\n </RuiDateTimePickerMenu>\n </template>\n </RuiMenu>\n</template>\n"],"mappings":"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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 { ComponentPublicInstance } from 'vue';\nimport type { DateTimePickerAction, DateTimeSegmentType } from '@/components/date-time-picker/types';\nimport type { TimePickerSelection } from '@/components/time-picker/RuiTimePicker.vue';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport { dateTimePickerStyles, type DateTimePickerVariant } from '@/components/date-time-picker/date-time-picker-styles';\nimport RuiDateTimePickerMenu from '@/components/date-time-picker/RuiDateTimePickerMenu.vue';\nimport { useDateTimeSelection } from '@/components/date-time-picker/use-date-time-selection';\nimport { useInputHandler } from '@/components/date-time-picker/use-input-handler';\nimport { useKeyboardHandler } from '@/components/date-time-picker/use-keyboard-handler';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiMenu from '@/components/overlays/menu/RuiMenu.vue';\nimport { type FloatingOptions, Placement } from '@/composables/floating';\nimport { useRuiI8n } from '@/composables/use-rui-i18n';\nimport { RUI_I18N_KEYS } from '@/i18n/keys';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { getNonRootAttrs, getRootAttrs } from '@/utils/helpers';\nimport { cn } from '@/utils/tv';\n\ntype DateFormat = 'year-first' | 'month-first' | 'day-first';\n\ntype DateTimeModelType = 'date' | 'epoch-ms' | 'epoch';\n\ntype ModelValueType<T extends DateTimeModelType> = T extends 'date'\n ? Date | undefined\n : T extends 'epoch-ms'\n ? number | undefined\n : T extends 'epoch'\n ? number | undefined\n : Date | number | undefined;\n\nexport interface RuiDateTimePickerProps {\n minDate?: Date | number;\n maxDate?: Date | number | 'now';\n format?: DateFormat;\n type?: DateTimeModelType;\n accuracy?: 'minute' | 'second' | 'millisecond';\n disabled?: boolean;\n allowEmpty?: boolean;\n readonly?: boolean;\n dense?: boolean;\n label?: string;\n variant?: DateTimePickerVariant;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hideDetails?: boolean;\n required?: boolean;\n /**\n * Renders the timezone selector in the menu. Off by default: the value is\n * emitted as a date or an epoch, so the picked timezone does not survive a\n * round trip and most consumers work in local time.\n */\n showTimezone?: boolean;\n /**\n * Actions rendered in the menu footer. `clear` is only rendered when the\n * picker is `allowEmpty`. Pass an empty array to drop the footer entirely.\n */\n actions?: DateTimePickerAction[];\n /**\n * Focuses the field once it is mounted. The native attribute is ignored for\n * an input inserted into an already loaded document, which is the usual case\n * for a picker revealed by an editor or a dialog.\n */\n autofocus?: boolean;\n /**\n * Accepts an entry that stops short of the full format, filling the segments\n * it never reached from one end of the day: `start` gives a bare date\n * 00:00:00.000 and `end` gives it 23:59:59.999. Meant for a range, where the\n * two bounds want opposite ends. Left unset, an entry missing its time is not\n * a value and nothing is emitted.\n *\n * The fill runs when the user is done with the field - leaving it, pressing\n * enter, or closing the calendar - and is written back into the segments, so\n * what it decided on is on screen.\n *\n * Spelled out rather than written as `PartialTimeMode`: a consumer that hands\n * this whole interface to its own `defineProps` needs every member resolvable\n * by the SFC compiler, which does not follow the type into the package.\n */\n partialTime?: 'start' | 'end';\n}\n\ndefineOptions({\n name: 'RuiDateTimePicker',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<ModelValueType<DateTimeModelType>>({ required: true });\nconst menuOpen = defineModel<boolean>('menuOpen', { default: false });\n\nconst {\n disabled = false,\n readonly = false,\n allowEmpty = false,\n dense = false,\n type = 'epoch-ms',\n hideDetails = false,\n label,\n variant = 'default',\n hint,\n maxDate,\n minDate,\n format = 'day-first',\n accuracy = 'minute',\n errorMessages = [],\n successMessages = [],\n required = false,\n showTimezone = false,\n actions = ['now'],\n autofocus = false,\n partialTime,\n} = defineProps<RuiDateTimePickerProps>();\n\ndefineSlots<{\n 'menu-content': () => any;\n}>();\n\nconst MENU_OPTIONS: FloatingOptions = { placement: Placement.bottomStart };\n\nconst baseFormats: Record<DateFormat, string> = {\n 'day-first': 'DD/MM/YYYY HH:mm',\n 'month-first': 'MM/DD/YYYY HH:mm',\n 'year-first': 'YYYY/MM/DD HH:mm',\n};\n\nconst isOpen = ref<boolean>(false);\nconst isHovered = ref<boolean>(false);\nconst cursorPosition = ref<number>(0);\nconst currentValue = ref<number>();\n\nconst { t } = useRuiI8n();\n\nconst keys = RUI_I18N_KEYS.dateTimePicker;\n\nconst fieldLabel = computed<string>(() => label ?? t(keys.label, 'Pick a date'));\nconst clearLabel = computed<string>(() => t(keys.clearValue, 'Clear the date'));\nconst toggleLabel = computed<string>(() => (get(isOpen)\n ? t(keys.closeCalendar, 'Close the calendar')\n : t(keys.openCalendar, 'Open the calendar')));\n\nconst textInput = useTemplateRef<HTMLInputElement>('textInput');\nconst activator = useTemplateRef<HTMLDivElement>('activator');\nconst menuWrapperRef = useTemplateRef<ComponentPublicInstance>('menuWrapperRef');\nconst calendarMenuOpen = ref<boolean>(false);\n\nconst { focused: activatorFocusedWithin } = useFocusWithin(activator);\nconst { focused: menuWrapperFocusedWithin } = useFocusWithin(menuWrapperRef);\nconst { focused: searchInputFocused } = useFocus(textInput);\n\nconst anyFocused = computed<boolean>(() => get(activatorFocusedWithin) || get(menuWrapperFocusedWithin));\n\nconst dateFormat = computed<string>(() => {\n const fmt = baseFormats[format];\n if (accuracy === 'second') {\n return fmt.replace('HH:mm', 'HH:mm:ss');\n }\n else if (accuracy === 'millisecond') {\n return fmt.replace('HH:mm', 'HH:mm:ss.SSS');\n }\n return fmt;\n});\n\nconst {\n clear: clearSelection,\n commitPartialTime,\n getDateTime,\n internalErrorMessages,\n maxAllowedDate,\n minAllowedDate,\n segmentData,\n selectedDate,\n modelDay,\n modelHour,\n modelMillisecond,\n modelMinute,\n modelMonth,\n modelSecond,\n selectedTime,\n modelTimezone,\n modelYear,\n setNow,\n setToday,\n valueSet,\n} = useDateTimeSelection({\n accuracy,\n allowEmpty,\n dateFormat,\n maxDate,\n minDate,\n modelValue,\n partialTime,\n type,\n});\n\nconst { setValue, getCurrent } = useInputHandler(segmentData, currentValue);\n\nconst {\n clear: clearSegment,\n getCurrentSegment,\n handleBlur,\n handleClick,\n handleFocus,\n handleInput,\n handleInputSelection,\n handleKeyDown,\n handleMouseDown,\n handlePaste,\n pasteRejected,\n selectFirstSegment,\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 timezone: modelTimezone,\n});\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst isOutlined = computed<boolean>(() => variant === 'outlined');\n\n/** True once any segment holds a digit, even a partially typed date. */\nconst anySegmentSet = computed<boolean>(() => [\n modelYear,\n modelMonth,\n modelDay,\n modelHour,\n modelMinute,\n modelSecond,\n modelMillisecond,\n].some(segment => isDefined(segment)));\n\n/**\n * The field's text.\n *\n * An untouched field shows its format through the placeholder rather than\n * holding the tokens as its value, where a screen reader reads them as content\n * and select-all copies them. The tokens stay while the field is focused: that\n * is when the segment machinery highlights them, and collapsing the value\n * mid-edit would blank the field under the cursor. The guard is \"no segment\n * set\" rather than `valueSet`, so blurring a half-typed date keeps what was\n * entered on screen.\n */\nconst formattedDisplay = computed<string>(() => {\n if (!get(anySegmentSet) && !get(searchInputFocused)) {\n return '';\n }\n\n let result = get(dateFormat);\n\n const replacements = [\n { pattern: 'YYYY', value: getDisplayValue(modelYear, 4) },\n { pattern: 'MM', value: getDisplayValue(modelMonth, 2) },\n { pattern: 'DD', value: getDisplayValue(modelDay, 2) },\n { pattern: 'HH', value: getDisplayValue(modelHour, 2) },\n { pattern: 'mm', value: getDisplayValue(modelMinute, 2) },\n { pattern: 'ss', value: getDisplayValue(modelSecond, 2) },\n { pattern: 'SSS', value: getDisplayValue(modelMillisecond, 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\n/**\n * An untouched field holds no value until focus expands the format tokens into\n * it. That patch overwrites the input's value, and writing a value drops any\n * selection with it, so the highlight `handleFocus` just set is gone by the\n * time the tokens are on screen and the caret ends up after them. Re-select\n * the first segment once the tokens have actually landed.\n */\nwatch(formattedDisplay, (value, previous) => {\n if (previous === '' && value !== '' && get(searchInputFocused))\n selectFirstSegment();\n}, { flush: 'post' });\n\nconst timeSelection = computed<TimePickerSelection>({\n get() {\n const type = getCurrentSegment()?.type;\n\n if (type === 'mm') {\n return 'minute';\n }\n else if (type === 'ss') {\n return 'second';\n }\n else if (type === 'SSS') {\n return 'millisecond';\n }\n return 'hour';\n },\n set(value: TimePickerSelection) {\n let segmentType: DateTimeSegmentType = 'HH';\n if (value === 'minute') {\n segmentType = 'mm';\n }\n else if (value === 'second') {\n segmentType = 'ss';\n }\n else if (value === 'millisecond') {\n segmentType = 'SSS';\n }\n\n setSegment(segmentType);\n },\n});\n\nconst float = computed<boolean>(() => (get(isOpen) || get(valueSet) || get(searchInputFocused)) && get(isOutlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float))\n return '';\n const resolved = get(fieldLabel);\n return required ? `${resolved} ﹡` : resolved;\n});\n\nconst ui = computed<ReturnType<typeof dateTimePickerStyles>>(() => dateTimePickerStyles({\n filled: variant === 'filled',\n outlined: get(isOutlined),\n float: get(float),\n opened: get(isOpen),\n hovered: get(isHovered),\n dense,\n disabled,\n readonly,\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n}));\n\nconst pasteMessage = computed<string>(() => (get(pasteRejected)\n ? t(keys.pasteUnreadable, 'Could not read a date from the pasted text')\n : ''));\n\n/**\n * The paste message goes first: the details show one message, and it answers\n * what the user just did, which a standing validation error does not.\n */\nconst combinedErrorMessages = computed<string[]>(() => {\n const paste = get(pasteMessage) ? [get(pasteMessage)] : [];\n if (!errorMessages)\n return [...paste, ...get(internalErrorMessages)];\n\n const propErrors = Array.isArray(errorMessages) ? errorMessages : [errorMessages];\n return [...paste, ...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\n/**\n * An entry left incomplete is completed once the user is done with the field,\n * never while they are still filling it in: a half typed hour is not a segment\n * they left out. Leaving the field counts, unless the calendar is open, since\n * then the focus is only moving into it and the menu closing covers that.\n */\nfunction onBlur(): void {\n handleBlur();\n if (!get(isOpen))\n commitPartialTime();\n}\n\nwatch(isOpen, (open) => {\n if (!open && !get(searchInputFocused))\n commitPartialTime();\n});\n\n/**\n * Selects the clicked segment before opening the menu, so the selection is not\n * lost to the DOM changes that opening brings.\n */\nfunction handleInputClick(event: MouseEvent): void {\n handleClick(event);\n if (!get(isOpen)) {\n set(isOpen, true);\n }\n}\n\n/**\n * The menu is teleported to the body, so it never sits next to the field in\n * the tab sequence. Opening it from the keyboard therefore moves focus into\n * the calendar; opening it with the mouse leaves focus in the field, which is\n * what `disable-auto-focus` on the menu is there for.\n */\nconst focusCalendarOnOpen = ref<boolean>(false);\n\n/**\n * Asks for focus to move into the calendar. The menu is teleported and mounts\n * a frame later, so the watcher below waits for the wrapper to appear rather\n * than guessing at a number of ticks.\n */\nfunction focusCalendar(): void {\n set(focusCalendarOnOpen, true);\n}\n\nwatch(menuWrapperRef, (menu) => {\n if (!menu || !get(focusCalendarOnOpen))\n return;\n\n set(focusCalendarOnOpen, false);\n nextTick(() => {\n const el = menu.$el as HTMLElement | undefined;\n el?.querySelector<HTMLButtonElement>('[role=\"gridcell\"][tabindex=\"0\"]')?.focus({ preventScroll: true });\n });\n});\n\n/** Moves focus to the field. Exposed so a consumer can drive it from a ref. */\nfunction focus(): void {\n get(textInput)?.focus({ preventScroll: true });\n}\n\nfunction focusField(): void {\n nextTick(focus);\n}\n\nonMounted(() => {\n if (autofocus && !disabled)\n focusField();\n});\n\n/** Escape inside the calendar closes it and hands focus back to the field. */\nfunction closeFromMenu(): void {\n set(isOpen, false);\n focusField();\n}\n\n/**\n * The menu used to be mouse-only: the activator carried no key handlers, so\n * there was no way to reach the calendar from the keyboard. Alt+ArrowDown\n * opens it and Escape closes it, following the combobox convention, and the\n * append chevron is a real button for anyone who tabs to it instead.\n *\n * Enter and Escape are the other ways to be done with the field, and the keys\n * a consumer tends to close its editor on. Escape reaches that point only once\n * the calendar is already shut, and discards nothing, since the segments stay\n * on screen either way, so it commits what was entered rather than dropping it\n * when the field goes. Both write the value before the key carries on\n * bubbling, so it survives that close.\n */\nfunction onKeyDown(event: KeyboardEvent): void {\n if (disabled || readonly)\n return;\n\n if (event.altKey && event.key === 'ArrowDown') {\n event.preventDefault();\n set(isOpen, true);\n focusCalendar();\n return;\n }\n\n if (event.key === 'Escape' && get(isOpen)) {\n set(isOpen, false);\n return;\n }\n\n if (event.key === 'Enter' || event.key === 'Escape')\n commitPartialTime();\n\n handleKeyDown(event);\n}\n\nfunction arrowClicked(event: MouseEvent): void {\n if (get(isOpen)) {\n set(isOpen, false);\n event.stopPropagation();\n }\n}\n\nconst menuActions = computed<DateTimePickerAction[]>(\n () => actions.filter(action => action !== 'clear' || allowEmpty),\n);\n\nconst anyMenuOpen = computed<boolean>(() => get(isOpen) || get(calendarMenuOpen));\n\nwatch(anyMenuOpen, (value) => {\n set(menuOpen, value);\n});\n\ndefineExpose({\n focus,\n});\n</script>\n\n<template>\n <RuiMenu\n v-model=\"isOpen\"\n v-bind=\"getRootAttrs($attrs, [])\"\n :class=\"ui.wrapper({ class: cn($attrs.class) })\"\n :options=\"MENU_OPTIONS\"\n :dense=\"dense\"\n :hint=\"hint\"\n :disabled=\"disabled\"\n :success-messages=\"successMessages\"\n :error-messages=\"combinedErrorMessages\"\n :close-on-content-click=\"false\"\n :show-details=\"!hideDetails\"\n :persistent=\"calendarMenuOpen\"\n full-width\n disable-auto-focus\n >\n <template #activator=\"{ attrs, open }\">\n <div\n ref=\"activator\"\n :class=\"ui.activator()\"\n v-bind=\"{\n ...getNonRootAttrs($attrs, ['onClick', 'class']),\n ...(readonly ? {} : attrs),\n }\"\n data-id=\"activator\"\n :aria-invalid=\"hasError\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n @click=\"setInputFocus()\"\n >\n <span\n v-if=\"isOutlined && (searchInputFocused || open || valueSet)\"\n data-id=\"label\"\n :class=\"[\n ui.label(),\n { 'pr-2': !valueSet && !open && isOutlined },\n ]\"\n >\n {{ fieldLabel }}\n <span\n v-if=\"required\"\n data-id=\"required-indicator\"\n :class=\"ui.required()\"\n >\n ﹡\n </span>\n </span>\n\n <span :class=\"ui.iconPrepend()\">\n <RuiIcon\n class=\"text-rui-text-secondary transition\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-calendar-days\"\n />\n </span>\n\n <div :class=\"ui.value()\">\n <input\n ref=\"textInput\"\n :disabled=\"disabled\"\n :value=\"formattedDisplay\"\n class=\"bg-transparent outline-none flex-1 min-w-0\"\n type=\"text\"\n inputmode=\"numeric\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n :placeholder=\"dateFormat\"\n :readonly=\"readonly\"\n :aria-invalid=\"hasError\"\n :aria-label=\"fieldLabel\"\n :aria-required=\"required || undefined\"\n @mousedown=\"handleMouseDown($event)\"\n @focus=\"handleFocus()\"\n @blur=\"onBlur()\"\n @select=\"handleInputSelection($event)\"\n @click.stop=\"handleInputClick($event)\"\n @keydown=\"onKeyDown($event)\"\n @paste=\"handlePaste($event)\"\n @input=\"handleInput($event)\"\n />\n <!-- The details line is not a live region, so a rejected paste is announced from here -->\n <span\n class=\"sr-only\"\n aria-live=\"polite\"\n data-id=\"paste-status\"\n >\n {{ pasteMessage }}\n </span>\n </div>\n\n <RuiButton\n v-if=\"allowEmpty && valueSet && !disabled\"\n variant=\"text\"\n icon\n data-id=\"clear-button\"\n size=\"sm\"\n color=\"error\"\n :aria-label=\"clearLabel\"\n :class=\"[\n ui.clear(),\n anyFocused && '!visible',\n { 'mr-2': !dense },\n ]\"\n @click.stop.prevent=\"clear()\"\n >\n <RuiIcon\n name=\"lu-x\"\n size=\"18\"\n />\n </RuiButton>\n\n <!-- Out of the tab sequence, following the combobox pattern: the button only duplicates\n Alt+ArrowDown on the field, so leaving it in cost two stops to cross one date field\n and four to cross a from/to pair, for nothing a keyboard user could not already do.\n It stays a real button, so it keeps its label and its expanded state for anyone\n reaching it by any other means. -->\n <button\n v-if=\"!disabled && !readonly\"\n type=\"button\"\n tabindex=\"-1\"\n :class=\"ui.iconWrapper()\"\n data-id=\"append\"\n :aria-label=\"toggleLabel\"\n :aria-expanded=\"isOpen\"\n aria-haspopup=\"dialog\"\n @click=\"arrowClicked($event)\"\n >\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </button>\n <span\n v-else\n :class=\"ui.iconWrapper()\"\n data-id=\"append\"\n >\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n </div>\n <fieldset\n v-if=\"isOutlined\"\n :class=\"ui.fieldset()\"\n >\n <legend :class=\"ui.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </template>\n <template #default>\n <RuiDateTimePickerMenu\n ref=\"menuWrapperRef\"\n v-model:selected-date=\"selectedDate\"\n v-model:selected-time=\"selectedTime\"\n v-model:selected-hour=\"modelHour\"\n v-model:selected-minute=\"modelMinute\"\n v-model:selected-second=\"modelSecond\"\n v-model:selected-millisecond=\"modelMillisecond\"\n v-model:time-selection=\"timeSelection\"\n v-model:selected-timezone=\"modelTimezone\"\n v-model:calendar-menu-open=\"calendarMenuOpen\"\n :accuracy=\"accuracy\"\n :max-date=\"maxAllowedDate\"\n :min-date=\"minAllowedDate\"\n :show-timezone=\"showTimezone\"\n :actions=\"menuActions\"\n @keydown.escape=\"closeFromMenu()\"\n @set-now=\"setNow()\"\n @set-today=\"setToday()\"\n @clear=\"clear()\"\n >\n <slot name=\"menu-content\" />\n </RuiDateTimePickerMenu>\n </template>\n </RuiMenu>\n</template>\n"],"mappings":"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"}
@@ -30,4 +30,19 @@ export declare function resolveBound(bound: Date | number | undefined, epochSeco
30
30
  */
31
31
  export declare function clampToBounds(date: Dayjs, min: Date, max?: Date): Dayjs;
32
32
  export declare function getClickPosition(event: MouseEvent, input: HTMLInputElement, useCaretPosition: boolean): number;
33
- export declare function parseAndSetDateValues(pastedText: string, dateFormat: string, accuracy: TimeAccuracy, setValue: (segment: DateTimeSegmentType, value?: number) => void): void;
33
+ /**
34
+ * Reads a pasted date into the wall-clock time of the field's timezone.
35
+ *
36
+ * Accepts the field's own format and ISO dates, either cut short at any
37
+ * segment, as well as unix timestamps in seconds or milliseconds. A trailing
38
+ * `Z`, numeric offset, `UTC` or `GMT` makes the text an instant that is moved
39
+ * into the timezone. Any other zone name, such as `CEST`, cannot be resolved
40
+ * reliably and is dropped, reading the rest as wall-clock time.
41
+ *
42
+ * @param pastedText - the text from the clipboard
43
+ * @param dateFormat - the field's format
44
+ * @param timezone - the field's timezone, the local one when undefined
45
+ * @returns the date, or undefined when the text is not one
46
+ */
47
+ export declare function parsePastedDate(pastedText: string, dateFormat: string, timezone?: string): Dayjs | undefined;
48
+ export declare function parseAndSetDateValues(pastedText: string, dateFormat: string, accuracy: TimeAccuracy, setValue: (segment: DateTimeSegmentType, value?: number) => void, timezone?: string): boolean;
@@ -62,21 +62,78 @@ function getClickPosition(event, input, useCaretPosition) {
62
62
  if (caretPos) return caretPos.offset;
63
63
  return document.caretRangeFromPoint?.(event.clientX, event.clientY)?.startOffset ?? fallback;
64
64
  }
65
- function parseAndSetDateValues(pastedText, dateFormat, accuracy, setValue) {
66
- try {
67
- const parsedDate = dayjs(pastedText, dateFormat);
68
- if (!parsedDate.isValid()) return;
69
- const date = parsedDate.toDate();
70
- setValue("YYYY", date.getFullYear());
71
- setValue("MM", date.getMonth() + 1);
72
- setValue("DD", date.getDate());
73
- setValue("HH", date.getHours());
74
- setValue("mm", date.getMinutes());
75
- if (includeSeconds(accuracy)) setValue("ss", date.getSeconds());
76
- if (includeMilliseconds(accuracy)) setValue("SSS", date.getMilliseconds());
77
- } catch {}
65
+ var UNIX_SECONDS = /^\d{9,10}$/;
66
+ var UNIX_MILLISECONDS = /^\d{12,13}$/;
67
+ var UTC_OFFSET = /(?:Z|[+-]\d{2}:?\d{2})$/i;
68
+ var UTC_NAME = /\s+(?:UTC|GMT)$/i;
69
+ var ZONE_NAME = /\s+[A-Z]{2,5}$/i;
70
+ var ISO_FORMATS = ["YYYY-MM-DD[T]HH:mm:ss.SSS", "YYYY-MM-DD HH:mm:ss.SSS"];
71
+ /**
72
+ * Lists a format followed by its shorter forms, dropping the millisecond, the
73
+ * second and then the whole time, so a paste may stop short of the field's
74
+ * accuracy.
75
+ *
76
+ * @param format - the full format
77
+ * @returns the format and every shorter form of it
78
+ */
79
+ function withTruncations(format) {
80
+ const truncations = [format];
81
+ for (const suffix of [
82
+ ".SSS",
83
+ ":ss",
84
+ /[\sT[\]]+HH:mm$/
85
+ ]) {
86
+ const last = truncations.at(-1);
87
+ const shorter = last.replace(suffix, "");
88
+ if (shorter !== last) truncations.push(shorter);
89
+ }
90
+ return truncations;
91
+ }
92
+ /**
93
+ * Reads a pasted date into the wall-clock time of the field's timezone.
94
+ *
95
+ * Accepts the field's own format and ISO dates, either cut short at any
96
+ * segment, as well as unix timestamps in seconds or milliseconds. A trailing
97
+ * `Z`, numeric offset, `UTC` or `GMT` makes the text an instant that is moved
98
+ * into the timezone. Any other zone name, such as `CEST`, cannot be resolved
99
+ * reliably and is dropped, reading the rest as wall-clock time.
100
+ *
101
+ * @param pastedText - the text from the clipboard
102
+ * @param dateFormat - the field's format
103
+ * @param timezone - the field's timezone, the local one when undefined
104
+ * @returns the date, or undefined when the text is not one
105
+ */
106
+ function parsePastedDate(pastedText, dateFormat, timezone) {
107
+ const text = pastedText.trim();
108
+ if (UNIX_SECONDS.test(text)) return dayjs.unix(Number(text)).tz(timezone);
109
+ if (UNIX_MILLISECONDS.test(text)) return dayjs(Number(text)).tz(timezone);
110
+ if (UTC_OFFSET.test(text)) {
111
+ const instant = dayjs(text.replace(" ", "T"));
112
+ return instant.isValid() ? instant.tz(timezone) : void 0;
113
+ }
114
+ const formats = [dateFormat, ...ISO_FORMATS].flatMap((format) => withTruncations(format));
115
+ if (UTC_NAME.test(text)) return parseWallClock(text.replace(UTC_NAME, ""), formats, true)?.tz(timezone);
116
+ return parseWallClock(text.replace(ZONE_NAME, ""), formats, false);
117
+ }
118
+ function parseWallClock(text, formats, utc) {
119
+ for (const format of formats) {
120
+ const date = utc ? dayjs.utc(text, format, true) : dayjs(text, format, true);
121
+ if (date.isValid()) return date;
122
+ }
123
+ }
124
+ function parseAndSetDateValues(pastedText, dateFormat, accuracy, setValue, timezone) {
125
+ const date = parsePastedDate(pastedText, dateFormat, timezone);
126
+ if (!date) return false;
127
+ setValue("YYYY", date.year());
128
+ setValue("MM", date.month() + 1);
129
+ setValue("DD", date.date());
130
+ setValue("HH", date.hour());
131
+ setValue("mm", date.minute());
132
+ if (includeSeconds(accuracy)) setValue("ss", date.second());
133
+ if (includeMilliseconds(accuracy)) setValue("SSS", date.millisecond());
134
+ return true;
78
135
  }
79
136
  //#endregion
80
- export { buildDateTime, clampToBounds, getClickPosition, parseAndSetDateValues, resolveBound };
137
+ export { buildDateTime, clampToBounds, getClickPosition, parseAndSetDateValues, parsePastedDate, resolveBound };
81
138
 
82
139
  //# sourceMappingURL=segment-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"segment-utils.js","names":[],"sources":["../../../src/components/date-time-picker/segment-utils.ts"],"sourcesContent":["import type { DateTimeSegmentType } from '@/components/date-time-picker/types';\nimport type { TimeAccuracy } from '@/consts/time-accuracy';\nimport dayjs, { type Dayjs } from 'dayjs';\nimport { includeMilliseconds, includeSeconds } from '@/components/date-time-picker/utils';\n\nimport '@/components/date-time-picker/dayjs-setup';\n\nexport interface SegmentValues {\n year?: number;\n month?: number;\n day?: number;\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n}\n\n/**\n * Applies the entered date segments, keeping the day inside the month the year\n * and month land on.\n *\n * @param base - the date to apply them to\n * @param segments - the segments entered so far\n * @returns the date with its year, month and day set\n */\nfunction applyDateSegments(base: Dayjs, { day, month, year }: SegmentValues): Dayjs {\n let dateTime = base;\n\n if (year !== undefined)\n dateTime = dateTime.year(year);\n\n if (month !== undefined)\n dateTime = dateTime.month(month - 1);\n\n if (day !== undefined)\n dateTime = dateTime.date(Math.min(day, dateTime.daysInMonth()));\n\n return dateTime;\n}\n\n/**\n * Builds a date from the segments that have been entered so far. Segments the\n * accuracy does not expose are zeroed rather than inherited from the clock the\n * base date started at, so a value cannot pick up a stray second.\n */\nexport function buildDateTime(segments: SegmentValues, accuracy: TimeAccuracy, base: Dayjs = dayjs()): Dayjs {\n const { hour, millisecond, minute, second } = segments;\n let dateTime = applyDateSegments(base, segments);\n\n if (hour !== undefined)\n dateTime = dateTime.hour(hour);\n\n if (minute !== undefined)\n dateTime = dateTime.minute(minute);\n\n if (second !== undefined)\n dateTime = dateTime.second(second);\n else if (!includeSeconds(accuracy))\n dateTime = dateTime.second(0);\n\n if (millisecond !== undefined)\n dateTime = dateTime.millisecond(millisecond);\n else if (!includeMilliseconds(accuracy))\n dateTime = dateTime.millisecond(0);\n\n return dateTime;\n}\n\nconst MILLISECONDS = 1000;\n\n/**\n * Turns a `minDate` / `maxDate` prop into a Date. An `epoch` picker states its\n * bounds in whole seconds, so those are widened to milliseconds first.\n */\nexport function resolveBound(bound: Date | number | undefined, epochSeconds: boolean): Date | undefined {\n if (bound === undefined) {\n return undefined;\n }\n\n if (epochSeconds && typeof bound === 'number') {\n return new Date(bound * MILLISECONDS);\n }\n\n return new Date(bound);\n}\n\n/**\n * Pulls a date inside the allowed range. Used by the `now` and `today` actions,\n * which have no partial state: typed input is left to diverge and explain\n * itself through the error message instead, since clamping a keystroke would\n * rewrite a year the moment its first digit landed below the minimum.\n */\nexport function clampToBounds(date: Dayjs, min: Date, max?: Date): Dayjs {\n const lower = dayjs(min);\n if (date.isBefore(lower)) {\n return lower;\n }\n\n if (max && date.isAfter(max)) {\n return dayjs(max);\n }\n\n return date;\n}\n\nexport function getClickPosition(\n event: MouseEvent,\n input: HTMLInputElement,\n useCaretPosition: boolean,\n): number {\n const fallback = input.selectionStart ?? 0;\n if (!useCaretPosition)\n return fallback;\n const caretPos = document.caretPositionFromPoint?.(event.clientX, event.clientY);\n if (caretPos)\n return caretPos.offset;\n return document.caretRangeFromPoint?.(event.clientX, event.clientY)?.startOffset ?? fallback;\n}\n\nexport function parseAndSetDateValues(\n pastedText: string,\n dateFormat: string,\n accuracy: TimeAccuracy,\n setValue: (segment: DateTimeSegmentType, value?: number) => void,\n): void {\n try {\n const parsedDate = dayjs(pastedText, dateFormat);\n\n if (!parsedDate.isValid()) {\n return;\n }\n\n const date = parsedDate.toDate();\n setValue('YYYY', date.getFullYear());\n setValue('MM', date.getMonth() + 1);\n setValue('DD', date.getDate());\n setValue('HH', date.getHours());\n setValue('mm', date.getMinutes());\n if (includeSeconds(accuracy)) {\n setValue('ss', date.getSeconds());\n }\n if (includeMilliseconds(accuracy)) {\n setValue('SSS', date.getMilliseconds());\n }\n }\n catch {\n // Invalid format, ignore paste\n }\n}\n"],"mappings":";;;;;;;;;;;;AAyBA,SAAS,kBAAkB,MAAa,EAAE,KAAK,OAAO,QAA8B;CAClF,IAAI,WAAW;CAEf,IAAI,SAAS,KAAA,GACX,WAAW,SAAS,KAAK,IAAI;CAE/B,IAAI,UAAU,KAAA,GACZ,WAAW,SAAS,MAAM,QAAQ,CAAC;CAErC,IAAI,QAAQ,KAAA,GACV,WAAW,SAAS,KAAK,KAAK,IAAI,KAAK,SAAS,YAAY,CAAC,CAAC;CAEhE,OAAO;AACT;;;;;;AAOA,SAAgB,cAAc,UAAyB,UAAwB,OAAc,MAAM,GAAU;CAC3G,MAAM,EAAE,MAAM,aAAa,QAAQ,WAAW;CAC9C,IAAI,WAAW,kBAAkB,MAAM,QAAQ;CAE/C,IAAI,SAAS,KAAA,GACX,WAAW,SAAS,KAAK,IAAI;CAE/B,IAAI,WAAW,KAAA,GACb,WAAW,SAAS,OAAO,MAAM;CAEnC,IAAI,WAAW,KAAA,GACb,WAAW,SAAS,OAAO,MAAM;MAC9B,IAAI,CAAC,eAAe,QAAQ,GAC/B,WAAW,SAAS,OAAO,CAAC;CAE9B,IAAI,gBAAgB,KAAA,GAClB,WAAW,SAAS,YAAY,WAAW;MACxC,IAAI,CAAC,oBAAoB,QAAQ,GACpC,WAAW,SAAS,YAAY,CAAC;CAEnC,OAAO;AACT;AAEA,IAAM,eAAe;;;;;AAMrB,SAAgB,aAAa,OAAkC,cAAyC;CACtG,IAAI,UAAU,KAAA,GACZ;CAGF,IAAI,gBAAgB,OAAO,UAAU,UACnC,uBAAO,IAAI,KAAK,QAAQ,YAAY;CAGtC,OAAO,IAAI,KAAK,KAAK;AACvB;;;;;;;AAQA,SAAgB,cAAc,MAAa,KAAW,KAAmB;CACvE,MAAM,QAAQ,MAAM,GAAG;CACvB,IAAI,KAAK,SAAS,KAAK,GACrB,OAAO;CAGT,IAAI,OAAO,KAAK,QAAQ,GAAG,GACzB,OAAO,MAAM,GAAG;CAGlB,OAAO;AACT;AAEA,SAAgB,iBACd,OACA,OACA,kBACQ;CACR,MAAM,WAAW,MAAM,kBAAkB;CACzC,IAAI,CAAC,kBACH,OAAO;CACT,MAAM,WAAW,SAAS,yBAAyB,MAAM,SAAS,MAAM,OAAO;CAC/E,IAAI,UACF,OAAO,SAAS;CAClB,OAAO,SAAS,sBAAsB,MAAM,SAAS,MAAM,OAAO,CAAC,EAAE,eAAe;AACtF;AAEA,SAAgB,sBACd,YACA,YACA,UACA,UACM;CACN,IAAI;EACF,MAAM,aAAa,MAAM,YAAY,UAAU;EAE/C,IAAI,CAAC,WAAW,QAAQ,GACtB;EAGF,MAAM,OAAO,WAAW,OAAO;EAC/B,SAAS,QAAQ,KAAK,YAAY,CAAC;EACnC,SAAS,MAAM,KAAK,SAAS,IAAI,CAAC;EAClC,SAAS,MAAM,KAAK,QAAQ,CAAC;EAC7B,SAAS,MAAM,KAAK,SAAS,CAAC;EAC9B,SAAS,MAAM,KAAK,WAAW,CAAC;EAChC,IAAI,eAAe,QAAQ,GACzB,SAAS,MAAM,KAAK,WAAW,CAAC;EAElC,IAAI,oBAAoB,QAAQ,GAC9B,SAAS,OAAO,KAAK,gBAAgB,CAAC;CAE1C,QACM,CAEN;AACF"}
1
+ {"version":3,"file":"segment-utils.js","names":[],"sources":["../../../src/components/date-time-picker/segment-utils.ts"],"sourcesContent":["import type { DateTimeSegmentType } from '@/components/date-time-picker/types';\nimport type { TimeAccuracy } from '@/consts/time-accuracy';\nimport dayjs, { type Dayjs } from 'dayjs';\nimport { includeMilliseconds, includeSeconds } from '@/components/date-time-picker/utils';\n\nimport '@/components/date-time-picker/dayjs-setup';\n\nexport interface SegmentValues {\n year?: number;\n month?: number;\n day?: number;\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n}\n\n/**\n * Applies the entered date segments, keeping the day inside the month the year\n * and month land on.\n *\n * @param base - the date to apply them to\n * @param segments - the segments entered so far\n * @returns the date with its year, month and day set\n */\nfunction applyDateSegments(base: Dayjs, { day, month, year }: SegmentValues): Dayjs {\n let dateTime = base;\n\n if (year !== undefined)\n dateTime = dateTime.year(year);\n\n if (month !== undefined)\n dateTime = dateTime.month(month - 1);\n\n if (day !== undefined)\n dateTime = dateTime.date(Math.min(day, dateTime.daysInMonth()));\n\n return dateTime;\n}\n\n/**\n * Builds a date from the segments that have been entered so far. Segments the\n * accuracy does not expose are zeroed rather than inherited from the clock the\n * base date started at, so a value cannot pick up a stray second.\n */\nexport function buildDateTime(segments: SegmentValues, accuracy: TimeAccuracy, base: Dayjs = dayjs()): Dayjs {\n const { hour, millisecond, minute, second } = segments;\n let dateTime = applyDateSegments(base, segments);\n\n if (hour !== undefined)\n dateTime = dateTime.hour(hour);\n\n if (minute !== undefined)\n dateTime = dateTime.minute(minute);\n\n if (second !== undefined)\n dateTime = dateTime.second(second);\n else if (!includeSeconds(accuracy))\n dateTime = dateTime.second(0);\n\n if (millisecond !== undefined)\n dateTime = dateTime.millisecond(millisecond);\n else if (!includeMilliseconds(accuracy))\n dateTime = dateTime.millisecond(0);\n\n return dateTime;\n}\n\nconst MILLISECONDS = 1000;\n\n/**\n * Turns a `minDate` / `maxDate` prop into a Date. An `epoch` picker states its\n * bounds in whole seconds, so those are widened to milliseconds first.\n */\nexport function resolveBound(bound: Date | number | undefined, epochSeconds: boolean): Date | undefined {\n if (bound === undefined) {\n return undefined;\n }\n\n if (epochSeconds && typeof bound === 'number') {\n return new Date(bound * MILLISECONDS);\n }\n\n return new Date(bound);\n}\n\n/**\n * Pulls a date inside the allowed range. Used by the `now` and `today` actions,\n * which have no partial state: typed input is left to diverge and explain\n * itself through the error message instead, since clamping a keystroke would\n * rewrite a year the moment its first digit landed below the minimum.\n */\nexport function clampToBounds(date: Dayjs, min: Date, max?: Date): Dayjs {\n const lower = dayjs(min);\n if (date.isBefore(lower)) {\n return lower;\n }\n\n if (max && date.isAfter(max)) {\n return dayjs(max);\n }\n\n return date;\n}\n\nexport function getClickPosition(\n event: MouseEvent,\n input: HTMLInputElement,\n useCaretPosition: boolean,\n): number {\n const fallback = input.selectionStart ?? 0;\n if (!useCaretPosition)\n return fallback;\n const caretPos = document.caretPositionFromPoint?.(event.clientX, event.clientY);\n if (caretPos)\n return caretPos.offset;\n return document.caretRangeFromPoint?.(event.clientX, event.clientY)?.startOffset ?? fallback;\n}\n\nconst UNIX_SECONDS = /^\\d{9,10}$/;\nconst UNIX_MILLISECONDS = /^\\d{12,13}$/;\nconst UTC_OFFSET = /(?:Z|[+-]\\d{2}:?\\d{2})$/i;\nconst UTC_NAME = /\\s+(?:UTC|GMT)$/i;\nconst ZONE_NAME = /\\s+[A-Z]{2,5}$/i;\nconst ISO_FORMATS = ['YYYY-MM-DD[T]HH:mm:ss.SSS', 'YYYY-MM-DD HH:mm:ss.SSS'];\n\n/**\n * Lists a format followed by its shorter forms, dropping the millisecond, the\n * second and then the whole time, so a paste may stop short of the field's\n * accuracy.\n *\n * @param format - the full format\n * @returns the format and every shorter form of it\n */\nfunction withTruncations(format: string): string[] {\n const truncations = [format];\n for (const suffix of ['.SSS', ':ss', /[\\sT[\\]]+HH:mm$/]) {\n const last = truncations.at(-1)!;\n const shorter = last.replace(suffix, '');\n if (shorter !== last)\n truncations.push(shorter);\n }\n return truncations;\n}\n\n/**\n * Reads a pasted date into the wall-clock time of the field's timezone.\n *\n * Accepts the field's own format and ISO dates, either cut short at any\n * segment, as well as unix timestamps in seconds or milliseconds. A trailing\n * `Z`, numeric offset, `UTC` or `GMT` makes the text an instant that is moved\n * into the timezone. Any other zone name, such as `CEST`, cannot be resolved\n * reliably and is dropped, reading the rest as wall-clock time.\n *\n * @param pastedText - the text from the clipboard\n * @param dateFormat - the field's format\n * @param timezone - the field's timezone, the local one when undefined\n * @returns the date, or undefined when the text is not one\n */\nexport function parsePastedDate(pastedText: string, dateFormat: string, timezone?: string): Dayjs | undefined {\n const text = pastedText.trim();\n\n if (UNIX_SECONDS.test(text))\n return dayjs.unix(Number(text)).tz(timezone);\n if (UNIX_MILLISECONDS.test(text))\n return dayjs(Number(text)).tz(timezone);\n\n if (UTC_OFFSET.test(text)) {\n const instant = dayjs(text.replace(' ', 'T'));\n return instant.isValid() ? instant.tz(timezone) : undefined;\n }\n\n const formats = [dateFormat, ...ISO_FORMATS].flatMap(format => withTruncations(format));\n if (UTC_NAME.test(text))\n return parseWallClock(text.replace(UTC_NAME, ''), formats, true)?.tz(timezone);\n return parseWallClock(text.replace(ZONE_NAME, ''), formats, false);\n}\n\nfunction parseWallClock(text: string, formats: string[], utc: boolean): Dayjs | undefined {\n for (const format of formats) {\n const date = utc ? dayjs.utc(text, format, true) : dayjs(text, format, true);\n if (date.isValid())\n return date;\n }\n return undefined;\n}\n\nexport function parseAndSetDateValues(\n pastedText: string,\n dateFormat: string,\n accuracy: TimeAccuracy,\n setValue: (segment: DateTimeSegmentType, value?: number) => void,\n timezone?: string,\n): boolean {\n const date = parsePastedDate(pastedText, dateFormat, timezone);\n if (!date)\n return false;\n\n setValue('YYYY', date.year());\n setValue('MM', date.month() + 1);\n setValue('DD', date.date());\n setValue('HH', date.hour());\n setValue('mm', date.minute());\n if (includeSeconds(accuracy))\n setValue('ss', date.second());\n if (includeMilliseconds(accuracy))\n setValue('SSS', date.millisecond());\n return true;\n}\n"],"mappings":"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"}
@@ -14,6 +14,7 @@ interface KeyboardHandlerOptions {
14
14
  disabled: boolean;
15
15
  readonly: boolean;
16
16
  accuracy: TimeAccuracy;
17
+ timezone: Readonly<Ref<string | undefined>>;
17
18
  }
18
19
  export declare function useKeyboardHandler(options: KeyboardHandlerOptions): {
19
20
  clear: (segmentType?: string) => void;
@@ -26,6 +27,7 @@ export declare function useKeyboardHandler(options: KeyboardHandlerOptions): {
26
27
  handleKeyDown: (event: KeyboardEvent) => void;
27
28
  handleMouseDown: (event: MouseEvent) => void;
28
29
  handlePaste: (event: ClipboardEvent) => void;
30
+ pasteRejected: Readonly<ShallowRef<boolean>>;
29
31
  selectFirstSegment: () => void;
30
32
  setSegment: (segmentType: DateTimeSegmentType) => void;
31
33
  };
@@ -2,12 +2,13 @@ import { assert } from "../../utils/assert.js";
2
2
  import { getClickPosition, parseAndSetDateValues } from "./segment-utils.js";
3
3
  import { SEGMENT_CONFIG, SEGMENT_METHODS } from "./segment-config.js";
4
4
  import { isDateTimeSegmentType } from "./types.js";
5
- import { computed, nextTick } from "vue";
5
+ import { computed, nextTick, shallowReadonly, shallowRef } from "vue";
6
6
  import { isDefined } from "@vueuse/core";
7
7
  import { get as get$1, set as set$1 } from "@vueuse/shared";
8
8
  //#region src/components/date-time-picker/use-keyboard-handler.ts
9
9
  function useKeyboardHandler(options) {
10
- const { accuracy, currentValue, cursorPosition, dateFormat, disabled, getCurrent, getDateTime, readonly, setValue, textInput } = options;
10
+ const { accuracy, currentValue, cursorPosition, dateFormat, disabled, getCurrent, getDateTime, readonly, setValue, textInput, timezone } = options;
11
+ const pasteRejected = shallowRef(false);
11
12
  const formatSegments = computed(() => {
12
13
  return get$1(dateFormat).split(/([\s./:])/g).filter(Boolean);
13
14
  });
@@ -160,6 +161,7 @@ function useKeyboardHandler(options) {
160
161
  }
161
162
  function handleKeyDown(event) {
162
163
  if (disabled || readonly) return;
164
+ set$1(pasteRejected, false);
163
165
  if (event.ctrlKey || event.metaKey || event.altKey) return;
164
166
  if (dispatchKey(event, event.key)) event.preventDefault();
165
167
  }
@@ -220,18 +222,22 @@ function useKeyboardHandler(options) {
220
222
  function handleBlur() {
221
223
  set$1(cursorPosition, 0);
222
224
  set$1(currentValue, void 0);
225
+ set$1(pasteRejected, false);
223
226
  clickedSegment = void 0;
224
227
  }
228
+ /** Text that is no date raises `pasteRejected` until the next key, paste, input or blur. */
225
229
  function handlePaste(event) {
226
230
  if (disabled || readonly) return;
227
231
  if (!(event.target instanceof HTMLInputElement)) return;
228
232
  event.preventDefault();
229
233
  const pastedText = event.clipboardData?.getData("text");
230
234
  if (!pastedText) return;
231
- parseAndSetDateValues(pastedText, get$1(dateFormat), accuracy, setValue);
235
+ const parsed = parseAndSetDateValues(pastedText, get$1(dateFormat), accuracy, setValue, get$1(timezone));
236
+ set$1(pasteRejected, !parsed);
232
237
  }
233
238
  function handleInput(event) {
234
239
  if (disabled || readonly) return;
240
+ set$1(pasteRejected, false);
235
241
  if (!(event.target instanceof HTMLInputElement)) return;
236
242
  const inputText = event.target.value;
237
243
  if (!inputText) return;
@@ -248,6 +254,7 @@ function useKeyboardHandler(options) {
248
254
  handleKeyDown,
249
255
  handleMouseDown,
250
256
  handlePaste,
257
+ pasteRejected: shallowReadonly(pasteRejected),
251
258
  selectFirstSegment,
252
259
  setSegment
253
260
  };
@@ -1 +1 @@
1
- {"version":3,"file":"use-keyboard-handler.js","names":[],"sources":["../../../src/components/date-time-picker/use-keyboard-handler.ts"],"sourcesContent":["import type { Dayjs } from 'dayjs';\nimport type { Ref, ShallowRef } from 'vue';\nimport type { TimeAccuracy } from '@/consts/time-accuracy';\nimport { type Segment, SEGMENT_CONFIG, SEGMENT_METHODS } from '@/components/date-time-picker/segment-config';\nimport { getClickPosition, parseAndSetDateValues } from '@/components/date-time-picker/segment-utils';\nimport {\n type DateTimeSegmentType,\n isDateTimeSegmentType,\n} from '@/components/date-time-picker/types';\nimport { assert } from '@/utils/assert';\n\ninterface KeyboardHandlerOptions {\n dateFormat: Ref<string>;\n cursorPosition: Ref<number>;\n currentValue: Ref<number | undefined>;\n textInput: Readonly<ShallowRef<HTMLInputElement | null>> | Ref<HTMLInputElement | undefined>;\n setValue: (segment: DateTimeSegmentType, value?: number) => void;\n getCurrent: (segment: DateTimeSegmentType) => number | undefined;\n getDateTime: () => Dayjs;\n disabled: boolean;\n readonly: boolean;\n accuracy: TimeAccuracy;\n}\n\nexport function useKeyboardHandler(options: KeyboardHandlerOptions) {\n const {\n accuracy,\n currentValue,\n cursorPosition,\n dateFormat,\n disabled,\n getCurrent,\n getDateTime,\n readonly,\n setValue,\n textInput,\n } = options;\n\n const formatSegments = computed<string[]>(() => {\n const format = get(dateFormat);\n return format.split(/([\\s./:])/g).filter(Boolean);\n });\n\n const segmentPositions = computed<Segment[]>(() => {\n const segments = get(formatSegments);\n const positions: Segment[] = [];\n let currentPosition = 0;\n\n segments.forEach((segment) => {\n if (!/[\\s./:]/.test(segment)) {\n assert(isDateTimeSegmentType(segment), `Invalid date format segment: ${segment}`);\n positions.push({\n end: currentPosition + segment.length,\n start: currentPosition,\n type: segment,\n });\n }\n currentPosition += segment.length;\n });\n\n return positions;\n });\n\n function getCurrentSegment(position: number = get(cursorPosition)) {\n const segments = get(segmentPositions);\n return segments.find(segment => position >= segment.start && position <= segment.end);\n }\n\n function setCursorPosition(segment: Segment): void {\n set(cursorPosition, segment.end);\n\n nextTick(() => {\n if (isDefined(textInput)) {\n const input = get(textInput);\n input.setSelectionRange(segment.start, segment.end);\n input.focus();\n }\n });\n }\n\n function setSegment(segmentType: DateTimeSegmentType): void {\n const segments = get(segmentPositions).find(segment => segment.type === segmentType);\n if (segments) {\n set(currentValue, undefined);\n setCursorPosition(segments);\n }\n }\n\n function clear(segmentType?: string): void {\n const currentSegment = getCurrentSegment();\n if (!segmentType && !currentSegment) {\n return;\n }\n\n const typeToUse = segmentType ?? currentSegment?.type;\n if (!typeToUse) {\n return;\n }\n\n setValue(typeToUse as DateTimeSegmentType, undefined);\n\n if (currentSegment) {\n setCursorPosition(currentSegment);\n }\n }\n\n function changeSegmentValue(increment: boolean): void {\n if (!isDefined(textInput))\n return;\n const currentSegment = getCurrentSegment();\n if (!currentSegment)\n return;\n const segmentType = currentSegment.type;\n const method = SEGMENT_METHODS[segmentType];\n const selectedDate = getDateTime();\n const updatedDate = selectedDate.set(method, selectedDate.get(method) + (increment ? 1 : -1));\n if (updatedDate.year() >= 1970) {\n setValue(\n segmentType,\n segmentType === 'MM' ? updatedDate.get(method) + 1 : updatedDate.get(method),\n );\n setCursorPosition(currentSegment);\n }\n }\n\n function navigateSegments(key: string) {\n const position = get(cursorPosition);\n const positions = get(segmentPositions);\n const currentSegmentIndex = positions.findIndex(\n segment => position >= segment.start && position <= segment.end,\n );\n\n if (currentSegmentIndex === -1) {\n return;\n }\n\n set(currentValue, undefined);\n\n let nextSegmentIndex: number;\n if (key === 'ArrowRight') {\n nextSegmentIndex = currentSegmentIndex + 1;\n if (nextSegmentIndex < positions.length) {\n const nextSegment = positions[nextSegmentIndex];\n assert(nextSegment);\n setCursorPosition(nextSegment);\n }\n }\n else {\n nextSegmentIndex = currentSegmentIndex - 1;\n if (nextSegmentIndex >= 0) {\n const nextSegment = positions[nextSegmentIndex];\n assert(nextSegment);\n setCursorPosition(nextSegment);\n }\n }\n }\n\n /**\n * Adds a typed digit to the segment under the caret, moving on to the next\n * segment once this one can hold no more.\n *\n * @param segment - the segment the caret sits in\n * @param segmentType - that segment's type, which states its value range\n * @param digit - the digit that was typed\n */\n function applyDigit(segment: Segment, segmentType: DateTimeSegmentType, digit: string): void {\n const config = SEGMENT_CONFIG[segmentType];\n const value = get(currentValue) ?? '';\n const combinedStr = `${value}${digit}`;\n const combinedValue = parseInt(combinedStr);\n const maxLength = segmentType.length;\n const minValue = config.minValue ?? 0;\n\n if (combinedValue >= minValue && combinedValue <= config.maxValue) {\n setValue(segmentType, combinedValue);\n setCursorPosition(segment);\n }\n else if (combinedValue < minValue && combinedStr.length < maxLength) {\n // Track digit even if below minValue, so next digit can combine (e.g., \"0\" allows \"01\")\n set(currentValue, combinedValue);\n }\n\n const willExceedMax = parseInt(`${combinedStr}0`) > config.maxValue;\n // Use string length of typed digits, not parsed number length (e.g., \"01\" has length 2)\n if (willExceedMax || combinedStr.length >= maxLength)\n navigateSegments('ArrowRight');\n }\n\n function handleDigitPressed(event: KeyboardEvent, digit: string): void {\n if (!(event.target instanceof HTMLInputElement))\n return;\n\n const currentSegment = getCurrentSegment(event.target.selectionStart ?? 0);\n if (!currentSegment || isNaN(parseInt(digit)))\n return;\n\n const segmentType = currentSegment.type;\n if (!isDateTimeSegmentType(segmentType))\n return;\n\n applyDigit(currentSegment, segmentType, digit);\n }\n\n function handleKeyboardNavigation(event: KeyboardEvent): void {\n if (event.key === 'ArrowUp')\n changeSegmentValue(true);\n else if (event.key === 'ArrowDown')\n changeSegmentValue(false);\n }\n\n function handleBackspace(event: KeyboardEvent): void {\n if (!(event.target instanceof HTMLInputElement))\n return;\n const currentSegment = getCurrentSegment(event.target.selectionStart ?? 0);\n if (!currentSegment)\n return;\n const value = get(currentValue) ?? getCurrent(currentSegment.type) ?? '';\n const updatedValue = value.toString().slice(0, -1);\n setValue(currentSegment.type, updatedValue.length === 0 ? undefined : parseInt(updatedValue));\n setCursorPosition(currentSegment);\n }\n\n function onInputDeletePressed(event: KeyboardEvent): void {\n if (!(event.target instanceof HTMLInputElement))\n return;\n const segment = getCurrentSegment(event.target.selectionStart ?? 0);\n if (segment) {\n clear(segment.type);\n setCursorPosition(segment);\n }\n }\n\n /**\n * Runs the picker's own handling for a key.\n *\n * @param event - the keydown being handled\n * @param key - the key that was pressed\n * @returns whether the picker handled it, which decides if the browser's own\n * handling is kept: Tab moves out of the field, Escape closes the menu and\n * Enter submits the surrounding form.\n */\n function dispatchKey(event: KeyboardEvent, key: string): boolean {\n if (key === 'ArrowRight' || key === 'ArrowLeft')\n navigateSegments(key);\n else if (key === 'ArrowUp' || key === 'ArrowDown')\n handleKeyboardNavigation(event);\n else if (key === 'Backspace')\n handleBackspace(event);\n else if (key === 'Delete')\n onInputDeletePressed(event);\n else if (/^\\d$/.test(key))\n handleDigitPressed(event, key);\n else\n return false;\n\n return true;\n }\n\n function handleKeyDown(event: KeyboardEvent): void {\n if (disabled || readonly)\n return;\n\n // Modifier combos belong to the browser: swallowing them made copy and paste impossible\n if (event.ctrlKey || event.metaKey || event.altKey)\n return;\n\n if (dispatchKey(event, event.key))\n event.preventDefault();\n }\n\n // Track the segment that was clicked, so handleFocus can restore it after DOM updates\n let clickedSegment: Segment | undefined;\n\n /**\n * Captures the clicked segment on mousedown, which fires before focus.\n *\n * The in-progress digit buffer is reset because switching segments must not\n * carry typed digits over, otherwise a leftover digit from another segment\n * combines with the next keystroke (type \"1\" in HH, click mm, type \"3\" and\n * mm would become 13 instead of 3).\n */\n function handleMouseDown(event: MouseEvent): void {\n if (disabled || readonly || !(event.target instanceof HTMLInputElement))\n return;\n const currentSegment = getCurrentSegment(getClickPosition(event, event.target, true));\n if (currentSegment) {\n set(currentValue, undefined);\n clickedSegment = currentSegment;\n }\n }\n\n function handleClick(event: MouseEvent): void {\n if (disabled || readonly || !(event.target instanceof HTMLInputElement))\n return;\n const currentSegment = getCurrentSegment(getClickPosition(event, event.target, true));\n if (currentSegment) {\n set(currentValue, undefined);\n clickedSegment = currentSegment;\n set(cursorPosition, currentSegment.end);\n event.target.setSelectionRange(currentSegment.start, currentSegment.end);\n }\n }\n\n function handleInputSelection(event: Event): void {\n const target = event.target as HTMLInputElement;\n set(cursorPosition, target.selectionStart ?? 0);\n }\n\n function selectFirstSegment(): void {\n const firstSegment = get(segmentPositions)[0];\n if (firstSegment)\n setCursorPosition(firstSegment);\n }\n\n /**\n * Restores the selection a click asked for, which opening the menu and the\n * DOM updates that follow can lose. With no click to restore, focus selects\n * the first segment, unless the user is already selecting text themselves.\n */\n function handleFocus(): void {\n if (clickedSegment) {\n setCursorPosition(clickedSegment);\n clickedSegment = undefined;\n return;\n }\n\n const input = get(textInput);\n if (input && input.selectionStart !== input.selectionEnd)\n return;\n selectFirstSegment();\n }\n\n /**\n * Forgets the cursor position and the clicked segment, so re-focusing on\n * another segment does not blink through the one left behind.\n */\n function handleBlur(): void {\n set(cursorPosition, 0);\n set(currentValue, undefined);\n clickedSegment = undefined;\n }\n\n function handlePaste(event: ClipboardEvent): void {\n if (disabled || readonly)\n return;\n\n if (!(event.target instanceof HTMLInputElement)) {\n return;\n }\n\n event.preventDefault();\n\n const pastedText = event.clipboardData?.getData('text');\n if (!pastedText) {\n return;\n }\n parseAndSetDateValues(pastedText, get(dateFormat), accuracy, setValue);\n }\n\n function handleInput(event: Event): void {\n if (disabled || readonly)\n return;\n\n if (!(event.target instanceof HTMLInputElement)) {\n return;\n }\n\n const inputText = event.target.value;\n if (!inputText) {\n return;\n }\n\n parseAndSetDateValues(inputText, get(dateFormat), accuracy, setValue);\n }\n\n return {\n clear,\n getCurrentSegment,\n handleBlur,\n handleClick,\n handleFocus,\n handleInput,\n handleInputSelection,\n handleKeyDown,\n handleMouseDown,\n handlePaste,\n selectFirstSegment,\n setSegment,\n };\n}\n"],"mappings":";;;;;;;;AAwBA,SAAgB,mBAAmB,SAAiC;CAClE,MAAM,EACJ,UACA,cACA,gBACA,YACA,UACA,YACA,aACA,UACA,UACA,cACE;CAEJ,MAAM,iBAAiB,eAAyB;EAE9C,OADe,MAAI,UACZ,CAAA,CAAO,MAAM,YAAY,CAAC,CAAC,OAAO,OAAO;CAClD,CAAC;CAED,MAAM,mBAAmB,eAA0B;EACjD,MAAM,WAAW,MAAI,cAAc;EACnC,MAAM,YAAuB,CAAC;EAC9B,IAAI,kBAAkB;EAEtB,SAAS,SAAS,YAAY;GAC5B,IAAI,CAAC,UAAU,KAAK,OAAO,GAAG;IAC5B,OAAO,sBAAsB,OAAO,GAAG,gCAAgC,SAAS;IAChF,UAAU,KAAK;KACb,KAAK,kBAAkB,QAAQ;KAC/B,OAAO;KACP,MAAM;IACR,CAAC;GACH;GACA,mBAAmB,QAAQ;EAC7B,CAAC;EAED,OAAO;CACT,CAAC;CAED,SAAS,kBAAkB,WAAmB,MAAI,cAAc,GAAG;EAEjE,OADiB,MAAI,gBACd,CAAA,CAAS,MAAK,YAAW,YAAY,QAAQ,SAAS,YAAY,QAAQ,GAAG;CACtF;CAEA,SAAS,kBAAkB,SAAwB;EACjD,MAAI,gBAAgB,QAAQ,GAAG;EAE/B,eAAe;GACb,IAAI,UAAU,SAAS,GAAG;IACxB,MAAM,QAAQ,MAAI,SAAS;IAC3B,MAAM,kBAAkB,QAAQ,OAAO,QAAQ,GAAG;IAClD,MAAM,MAAM;GACd;EACF,CAAC;CACH;CAEA,SAAS,WAAW,aAAwC;EAC1D,MAAM,WAAW,MAAI,gBAAgB,CAAC,CAAC,MAAK,YAAW,QAAQ,SAAS,WAAW;EACnF,IAAI,UAAU;GACZ,MAAI,cAAc,KAAA,CAAS;GAC3B,kBAAkB,QAAQ;EAC5B;CACF;CAEA,SAAS,MAAM,aAA4B;EACzC,MAAM,iBAAiB,kBAAkB;EACzC,IAAI,CAAC,eAAe,CAAC,gBACnB;EAGF,MAAM,YAAY,eAAe,gBAAgB;EACjD,IAAI,CAAC,WACH;EAGF,SAAS,WAAkC,KAAA,CAAS;EAEpD,IAAI,gBACF,kBAAkB,cAAc;CAEpC;CAEA,SAAS,mBAAmB,WAA0B;EACpD,IAAI,CAAC,UAAU,SAAS,GACtB;EACF,MAAM,iBAAiB,kBAAkB;EACzC,IAAI,CAAC,gBACH;EACF,MAAM,cAAc,eAAe;EACnC,MAAM,SAAS,gBAAgB;EAC/B,MAAM,eAAe,YAAY;EACjC,MAAM,cAAc,aAAa,IAAI,QAAQ,aAAa,IAAI,MAAM,KAAK,YAAY,IAAI,GAAG;EAC5F,IAAI,YAAY,KAAK,KAAK,MAAM;GAC9B,SACE,aACA,gBAAgB,OAAO,YAAY,IAAI,MAAM,IAAI,IAAI,YAAY,IAAI,MAAM,CAC7E;GACA,kBAAkB,cAAc;EAClC;CACF;CAEA,SAAS,iBAAiB,KAAa;EACrC,MAAM,WAAW,MAAI,cAAc;EACnC,MAAM,YAAY,MAAI,gBAAgB;EACtC,MAAM,sBAAsB,UAAU,WACpC,YAAW,YAAY,QAAQ,SAAS,YAAY,QAAQ,GAC9D;EAEA,IAAI,wBAAwB,IAC1B;EAGF,MAAI,cAAc,KAAA,CAAS;EAE3B,IAAI;EACJ,IAAI,QAAQ,cAAc;GACxB,mBAAmB,sBAAsB;GACzC,IAAI,mBAAmB,UAAU,QAAQ;IACvC,MAAM,cAAc,UAAU;IAC9B,OAAO,WAAW;IAClB,kBAAkB,WAAW;GAC/B;EACF,OACK;GACH,mBAAmB,sBAAsB;GACzC,IAAI,oBAAoB,GAAG;IACzB,MAAM,cAAc,UAAU;IAC9B,OAAO,WAAW;IAClB,kBAAkB,WAAW;GAC/B;EACF;CACF;;;;;;;;;CAUA,SAAS,WAAW,SAAkB,aAAkC,OAAqB;EAC3F,MAAM,SAAS,eAAe;EAE9B,MAAM,cAAc,GADN,MAAI,YAAY,KAAK,KACJ;EAC/B,MAAM,gBAAgB,SAAS,WAAW;EAC1C,MAAM,YAAY,YAAY;EAC9B,MAAM,WAAW,OAAO,YAAY;EAEpC,IAAI,iBAAiB,YAAY,iBAAiB,OAAO,UAAU;GACjE,SAAS,aAAa,aAAa;GACnC,kBAAkB,OAAO;EAC3B,OACK,IAAI,gBAAgB,YAAY,YAAY,SAAS,WAExD,MAAI,cAAc,aAAa;EAKjC,IAFsB,SAAS,GAAG,YAAY,EAAE,IAAI,OAAO,YAEtC,YAAY,UAAU,WACzC,iBAAiB,YAAY;CACjC;CAEA,SAAS,mBAAmB,OAAsB,OAAqB;EACrE,IAAI,EAAE,MAAM,kBAAkB,mBAC5B;EAEF,MAAM,iBAAiB,kBAAkB,MAAM,OAAO,kBAAkB,CAAC;EACzE,IAAI,CAAC,kBAAkB,MAAM,SAAS,KAAK,CAAC,GAC1C;EAEF,MAAM,cAAc,eAAe;EACnC,IAAI,CAAC,sBAAsB,WAAW,GACpC;EAEF,WAAW,gBAAgB,aAAa,KAAK;CAC/C;CAEA,SAAS,yBAAyB,OAA4B;EAC5D,IAAI,MAAM,QAAQ,WAChB,mBAAmB,IAAI;OACpB,IAAI,MAAM,QAAQ,aACrB,mBAAmB,KAAK;CAC5B;CAEA,SAAS,gBAAgB,OAA4B;EACnD,IAAI,EAAE,MAAM,kBAAkB,mBAC5B;EACF,MAAM,iBAAiB,kBAAkB,MAAM,OAAO,kBAAkB,CAAC;EACzE,IAAI,CAAC,gBACH;EAEF,MAAM,gBADQ,MAAI,YAAY,KAAK,WAAW,eAAe,IAAI,KAAK,GAAA,CAC3C,SAAS,CAAC,CAAC,MAAM,GAAG,EAAE;EACjD,SAAS,eAAe,MAAM,aAAa,WAAW,IAAI,KAAA,IAAY,SAAS,YAAY,CAAC;EAC5F,kBAAkB,cAAc;CAClC;CAEA,SAAS,qBAAqB,OAA4B;EACxD,IAAI,EAAE,MAAM,kBAAkB,mBAC5B;EACF,MAAM,UAAU,kBAAkB,MAAM,OAAO,kBAAkB,CAAC;EAClE,IAAI,SAAS;GACX,MAAM,QAAQ,IAAI;GAClB,kBAAkB,OAAO;EAC3B;CACF;;;;;;;;;;CAWA,SAAS,YAAY,OAAsB,KAAsB;EAC/D,IAAI,QAAQ,gBAAgB,QAAQ,aAClC,iBAAiB,GAAG;OACjB,IAAI,QAAQ,aAAa,QAAQ,aACpC,yBAAyB,KAAK;OAC3B,IAAI,QAAQ,aACf,gBAAgB,KAAK;OAClB,IAAI,QAAQ,UACf,qBAAqB,KAAK;OACvB,IAAI,OAAO,KAAK,GAAG,GACtB,mBAAmB,OAAO,GAAG;OAE7B,OAAO;EAET,OAAO;CACT;CAEA,SAAS,cAAc,OAA4B;EACjD,IAAI,YAAY,UACd;EAGF,IAAI,MAAM,WAAW,MAAM,WAAW,MAAM,QAC1C;EAEF,IAAI,YAAY,OAAO,MAAM,GAAG,GAC9B,MAAM,eAAe;CACzB;CAGA,IAAI;;;;;;;;;CAUJ,SAAS,gBAAgB,OAAyB;EAChD,IAAI,YAAY,YAAY,EAAE,MAAM,kBAAkB,mBACpD;EACF,MAAM,iBAAiB,kBAAkB,iBAAiB,OAAO,MAAM,QAAQ,IAAI,CAAC;EACpF,IAAI,gBAAgB;GAClB,MAAI,cAAc,KAAA,CAAS;GAC3B,iBAAiB;EACnB;CACF;CAEA,SAAS,YAAY,OAAyB;EAC5C,IAAI,YAAY,YAAY,EAAE,MAAM,kBAAkB,mBACpD;EACF,MAAM,iBAAiB,kBAAkB,iBAAiB,OAAO,MAAM,QAAQ,IAAI,CAAC;EACpF,IAAI,gBAAgB;GAClB,MAAI,cAAc,KAAA,CAAS;GAC3B,iBAAiB;GACjB,MAAI,gBAAgB,eAAe,GAAG;GACtC,MAAM,OAAO,kBAAkB,eAAe,OAAO,eAAe,GAAG;EACzE;CACF;CAEA,SAAS,qBAAqB,OAAoB;EAChD,MAAM,SAAS,MAAM;EACrB,MAAI,gBAAgB,OAAO,kBAAkB,CAAC;CAChD;CAEA,SAAS,qBAA2B;EAClC,MAAM,eAAe,MAAI,gBAAgB,CAAC,CAAC;EAC3C,IAAI,cACF,kBAAkB,YAAY;CAClC;;;;;;CAOA,SAAS,cAAoB;EAC3B,IAAI,gBAAgB;GAClB,kBAAkB,cAAc;GAChC,iBAAiB,KAAA;GACjB;EACF;EAEA,MAAM,QAAQ,MAAI,SAAS;EAC3B,IAAI,SAAS,MAAM,mBAAmB,MAAM,cAC1C;EACF,mBAAmB;CACrB;;;;;CAMA,SAAS,aAAmB;EAC1B,MAAI,gBAAgB,CAAC;EACrB,MAAI,cAAc,KAAA,CAAS;EAC3B,iBAAiB,KAAA;CACnB;CAEA,SAAS,YAAY,OAA6B;EAChD,IAAI,YAAY,UACd;EAEF,IAAI,EAAE,MAAM,kBAAkB,mBAC5B;EAGF,MAAM,eAAe;EAErB,MAAM,aAAa,MAAM,eAAe,QAAQ,MAAM;EACtD,IAAI,CAAC,YACH;EAEF,sBAAsB,YAAY,MAAI,UAAU,GAAG,UAAU,QAAQ;CACvE;CAEA,SAAS,YAAY,OAAoB;EACvC,IAAI,YAAY,UACd;EAEF,IAAI,EAAE,MAAM,kBAAkB,mBAC5B;EAGF,MAAM,YAAY,MAAM,OAAO;EAC/B,IAAI,CAAC,WACH;EAGF,sBAAsB,WAAW,MAAI,UAAU,GAAG,UAAU,QAAQ;CACtE;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
1
+ {"version":3,"file":"use-keyboard-handler.js","names":[],"sources":["../../../src/components/date-time-picker/use-keyboard-handler.ts"],"sourcesContent":["import type { Dayjs } from 'dayjs';\nimport type { Ref, ShallowRef } from 'vue';\nimport type { TimeAccuracy } from '@/consts/time-accuracy';\nimport { type Segment, SEGMENT_CONFIG, SEGMENT_METHODS } from '@/components/date-time-picker/segment-config';\nimport { getClickPosition, parseAndSetDateValues } from '@/components/date-time-picker/segment-utils';\nimport {\n type DateTimeSegmentType,\n isDateTimeSegmentType,\n} from '@/components/date-time-picker/types';\nimport { assert } from '@/utils/assert';\n\ninterface KeyboardHandlerOptions {\n dateFormat: Ref<string>;\n cursorPosition: Ref<number>;\n currentValue: Ref<number | undefined>;\n textInput: Readonly<ShallowRef<HTMLInputElement | null>> | Ref<HTMLInputElement | undefined>;\n setValue: (segment: DateTimeSegmentType, value?: number) => void;\n getCurrent: (segment: DateTimeSegmentType) => number | undefined;\n getDateTime: () => Dayjs;\n disabled: boolean;\n readonly: boolean;\n accuracy: TimeAccuracy;\n timezone: Readonly<Ref<string | undefined>>;\n}\n\nexport function useKeyboardHandler(options: KeyboardHandlerOptions) {\n const {\n accuracy,\n currentValue,\n cursorPosition,\n dateFormat,\n disabled,\n getCurrent,\n getDateTime,\n readonly,\n setValue,\n textInput,\n timezone,\n } = options;\n\n const pasteRejected = shallowRef<boolean>(false);\n\n const formatSegments = computed<string[]>(() => {\n const format = get(dateFormat);\n return format.split(/([\\s./:])/g).filter(Boolean);\n });\n\n const segmentPositions = computed<Segment[]>(() => {\n const segments = get(formatSegments);\n const positions: Segment[] = [];\n let currentPosition = 0;\n\n segments.forEach((segment) => {\n if (!/[\\s./:]/.test(segment)) {\n assert(isDateTimeSegmentType(segment), `Invalid date format segment: ${segment}`);\n positions.push({\n end: currentPosition + segment.length,\n start: currentPosition,\n type: segment,\n });\n }\n currentPosition += segment.length;\n });\n\n return positions;\n });\n\n function getCurrentSegment(position: number = get(cursorPosition)) {\n const segments = get(segmentPositions);\n return segments.find(segment => position >= segment.start && position <= segment.end);\n }\n\n function setCursorPosition(segment: Segment): void {\n set(cursorPosition, segment.end);\n\n nextTick(() => {\n if (isDefined(textInput)) {\n const input = get(textInput);\n input.setSelectionRange(segment.start, segment.end);\n input.focus();\n }\n });\n }\n\n function setSegment(segmentType: DateTimeSegmentType): void {\n const segments = get(segmentPositions).find(segment => segment.type === segmentType);\n if (segments) {\n set(currentValue, undefined);\n setCursorPosition(segments);\n }\n }\n\n function clear(segmentType?: string): void {\n const currentSegment = getCurrentSegment();\n if (!segmentType && !currentSegment) {\n return;\n }\n\n const typeToUse = segmentType ?? currentSegment?.type;\n if (!typeToUse) {\n return;\n }\n\n setValue(typeToUse as DateTimeSegmentType, undefined);\n\n if (currentSegment) {\n setCursorPosition(currentSegment);\n }\n }\n\n function changeSegmentValue(increment: boolean): void {\n if (!isDefined(textInput))\n return;\n const currentSegment = getCurrentSegment();\n if (!currentSegment)\n return;\n const segmentType = currentSegment.type;\n const method = SEGMENT_METHODS[segmentType];\n const selectedDate = getDateTime();\n const updatedDate = selectedDate.set(method, selectedDate.get(method) + (increment ? 1 : -1));\n if (updatedDate.year() >= 1970) {\n setValue(\n segmentType,\n segmentType === 'MM' ? updatedDate.get(method) + 1 : updatedDate.get(method),\n );\n setCursorPosition(currentSegment);\n }\n }\n\n function navigateSegments(key: string) {\n const position = get(cursorPosition);\n const positions = get(segmentPositions);\n const currentSegmentIndex = positions.findIndex(\n segment => position >= segment.start && position <= segment.end,\n );\n\n if (currentSegmentIndex === -1) {\n return;\n }\n\n set(currentValue, undefined);\n\n let nextSegmentIndex: number;\n if (key === 'ArrowRight') {\n nextSegmentIndex = currentSegmentIndex + 1;\n if (nextSegmentIndex < positions.length) {\n const nextSegment = positions[nextSegmentIndex];\n assert(nextSegment);\n setCursorPosition(nextSegment);\n }\n }\n else {\n nextSegmentIndex = currentSegmentIndex - 1;\n if (nextSegmentIndex >= 0) {\n const nextSegment = positions[nextSegmentIndex];\n assert(nextSegment);\n setCursorPosition(nextSegment);\n }\n }\n }\n\n /**\n * Adds a typed digit to the segment under the caret, moving on to the next\n * segment once this one can hold no more.\n *\n * @param segment - the segment the caret sits in\n * @param segmentType - that segment's type, which states its value range\n * @param digit - the digit that was typed\n */\n function applyDigit(segment: Segment, segmentType: DateTimeSegmentType, digit: string): void {\n const config = SEGMENT_CONFIG[segmentType];\n const value = get(currentValue) ?? '';\n const combinedStr = `${value}${digit}`;\n const combinedValue = parseInt(combinedStr);\n const maxLength = segmentType.length;\n const minValue = config.minValue ?? 0;\n\n if (combinedValue >= minValue && combinedValue <= config.maxValue) {\n setValue(segmentType, combinedValue);\n setCursorPosition(segment);\n }\n else if (combinedValue < minValue && combinedStr.length < maxLength) {\n // Track digit even if below minValue, so next digit can combine (e.g., \"0\" allows \"01\")\n set(currentValue, combinedValue);\n }\n\n const willExceedMax = parseInt(`${combinedStr}0`) > config.maxValue;\n // Use string length of typed digits, not parsed number length (e.g., \"01\" has length 2)\n if (willExceedMax || combinedStr.length >= maxLength)\n navigateSegments('ArrowRight');\n }\n\n function handleDigitPressed(event: KeyboardEvent, digit: string): void {\n if (!(event.target instanceof HTMLInputElement))\n return;\n\n const currentSegment = getCurrentSegment(event.target.selectionStart ?? 0);\n if (!currentSegment || isNaN(parseInt(digit)))\n return;\n\n const segmentType = currentSegment.type;\n if (!isDateTimeSegmentType(segmentType))\n return;\n\n applyDigit(currentSegment, segmentType, digit);\n }\n\n function handleKeyboardNavigation(event: KeyboardEvent): void {\n if (event.key === 'ArrowUp')\n changeSegmentValue(true);\n else if (event.key === 'ArrowDown')\n changeSegmentValue(false);\n }\n\n function handleBackspace(event: KeyboardEvent): void {\n if (!(event.target instanceof HTMLInputElement))\n return;\n const currentSegment = getCurrentSegment(event.target.selectionStart ?? 0);\n if (!currentSegment)\n return;\n const value = get(currentValue) ?? getCurrent(currentSegment.type) ?? '';\n const updatedValue = value.toString().slice(0, -1);\n setValue(currentSegment.type, updatedValue.length === 0 ? undefined : parseInt(updatedValue));\n setCursorPosition(currentSegment);\n }\n\n function onInputDeletePressed(event: KeyboardEvent): void {\n if (!(event.target instanceof HTMLInputElement))\n return;\n const segment = getCurrentSegment(event.target.selectionStart ?? 0);\n if (segment) {\n clear(segment.type);\n setCursorPosition(segment);\n }\n }\n\n /**\n * Runs the picker's own handling for a key.\n *\n * @param event - the keydown being handled\n * @param key - the key that was pressed\n * @returns whether the picker handled it, which decides if the browser's own\n * handling is kept: Tab moves out of the field, Escape closes the menu and\n * Enter submits the surrounding form.\n */\n function dispatchKey(event: KeyboardEvent, key: string): boolean {\n if (key === 'ArrowRight' || key === 'ArrowLeft')\n navigateSegments(key);\n else if (key === 'ArrowUp' || key === 'ArrowDown')\n handleKeyboardNavigation(event);\n else if (key === 'Backspace')\n handleBackspace(event);\n else if (key === 'Delete')\n onInputDeletePressed(event);\n else if (/^\\d$/.test(key))\n handleDigitPressed(event, key);\n else\n return false;\n\n return true;\n }\n\n function handleKeyDown(event: KeyboardEvent): void {\n if (disabled || readonly)\n return;\n\n set(pasteRejected, false);\n // Modifier combos belong to the browser: swallowing them made copy and paste impossible\n if (event.ctrlKey || event.metaKey || event.altKey)\n return;\n\n if (dispatchKey(event, event.key))\n event.preventDefault();\n }\n\n // Track the segment that was clicked, so handleFocus can restore it after DOM updates\n let clickedSegment: Segment | undefined;\n\n /**\n * Captures the clicked segment on mousedown, which fires before focus.\n *\n * The in-progress digit buffer is reset because switching segments must not\n * carry typed digits over, otherwise a leftover digit from another segment\n * combines with the next keystroke (type \"1\" in HH, click mm, type \"3\" and\n * mm would become 13 instead of 3).\n */\n function handleMouseDown(event: MouseEvent): void {\n if (disabled || readonly || !(event.target instanceof HTMLInputElement))\n return;\n const currentSegment = getCurrentSegment(getClickPosition(event, event.target, true));\n if (currentSegment) {\n set(currentValue, undefined);\n clickedSegment = currentSegment;\n }\n }\n\n function handleClick(event: MouseEvent): void {\n if (disabled || readonly || !(event.target instanceof HTMLInputElement))\n return;\n const currentSegment = getCurrentSegment(getClickPosition(event, event.target, true));\n if (currentSegment) {\n set(currentValue, undefined);\n clickedSegment = currentSegment;\n set(cursorPosition, currentSegment.end);\n event.target.setSelectionRange(currentSegment.start, currentSegment.end);\n }\n }\n\n function handleInputSelection(event: Event): void {\n const target = event.target as HTMLInputElement;\n set(cursorPosition, target.selectionStart ?? 0);\n }\n\n function selectFirstSegment(): void {\n const firstSegment = get(segmentPositions)[0];\n if (firstSegment)\n setCursorPosition(firstSegment);\n }\n\n /**\n * Restores the selection a click asked for, which opening the menu and the\n * DOM updates that follow can lose. With no click to restore, focus selects\n * the first segment, unless the user is already selecting text themselves.\n */\n function handleFocus(): void {\n if (clickedSegment) {\n setCursorPosition(clickedSegment);\n clickedSegment = undefined;\n return;\n }\n\n const input = get(textInput);\n if (input && input.selectionStart !== input.selectionEnd)\n return;\n selectFirstSegment();\n }\n\n /**\n * Forgets the cursor position and the clicked segment, so re-focusing on\n * another segment does not blink through the one left behind.\n */\n function handleBlur(): void {\n set(cursorPosition, 0);\n set(currentValue, undefined);\n set(pasteRejected, false);\n clickedSegment = undefined;\n }\n\n /** Text that is no date raises `pasteRejected` until the next key, paste, input or blur. */\n function handlePaste(event: ClipboardEvent): void {\n if (disabled || readonly)\n return;\n\n if (!(event.target instanceof HTMLInputElement)) {\n return;\n }\n\n event.preventDefault();\n\n const pastedText = event.clipboardData?.getData('text');\n if (!pastedText) {\n return;\n }\n const parsed = parseAndSetDateValues(pastedText, get(dateFormat), accuracy, setValue, get(timezone));\n set(pasteRejected, !parsed);\n }\n\n function handleInput(event: Event): void {\n if (disabled || readonly)\n return;\n set(pasteRejected, false);\n\n if (!(event.target instanceof HTMLInputElement)) {\n return;\n }\n\n const inputText = event.target.value;\n if (!inputText) {\n return;\n }\n\n parseAndSetDateValues(inputText, get(dateFormat), accuracy, setValue);\n }\n\n return {\n clear,\n getCurrentSegment,\n handleBlur,\n handleClick,\n handleFocus,\n handleInput,\n handleInputSelection,\n handleKeyDown,\n handleMouseDown,\n handlePaste,\n pasteRejected: shallowReadonly(pasteRejected),\n selectFirstSegment,\n setSegment,\n };\n}\n"],"mappings":"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"}
@@ -9,6 +9,7 @@ export declare const RUI_I18N_KEYS: {
9
9
  readonly label: "rui.date_time_picker.label";
10
10
  readonly now: "rui.date_time_picker.now";
11
11
  readonly openCalendar: "rui.date_time_picker.open_calendar";
12
+ readonly pasteUnreadable: "rui.date_time_picker.paste_unreadable";
12
13
  readonly today: "rui.date_time_picker.today";
13
14
  };
14
15
  readonly timezoneSelect: {
package/dist/i18n/keys.js CHANGED
@@ -10,6 +10,7 @@ var RUI_I18N_KEYS = {
10
10
  label: "rui.date_time_picker.label",
11
11
  now: "rui.date_time_picker.now",
12
12
  openCalendar: "rui.date_time_picker.open_calendar",
13
+ pasteUnreadable: "rui.date_time_picker.paste_unreadable",
13
14
  today: "rui.date_time_picker.today"
14
15
  },
15
16
  timezoneSelect: { label: "rui.timezone_select.label" }
@@ -1 +1 @@
1
- {"version":3,"file":"keys.js","names":[],"sources":["../../src/i18n/keys.ts"],"sourcesContent":["export const RUI_I18N_KEYS = {\n dateTimePicker: {\n clear: 'rui.date_time_picker.clear',\n clearValue: 'rui.date_time_picker.clear_value',\n closeCalendar: 'rui.date_time_picker.close_calendar',\n dateAfterMax: 'rui.date_time_picker.date_after_max',\n dateBeforeMin: 'rui.date_time_picker.date_before_min',\n dateInFuture: 'rui.date_time_picker.date_in_future',\n label: 'rui.date_time_picker.label',\n now: 'rui.date_time_picker.now',\n openCalendar: 'rui.date_time_picker.open_calendar',\n today: 'rui.date_time_picker.today',\n },\n timezoneSelect: {\n label: 'rui.timezone_select.label',\n },\n} as const;\n\nfunction flattenKeys(obj: Record<string, any>): string[] {\n return Object.values(obj).flatMap(value =>\n typeof value === 'string' ? [value] : flattenKeys(value),\n );\n}\n\n/**\n * Retrieves all translation keys available in the library.\n *\n * @returns the flattened translation keys\n */\nexport function translationKeys(): string[] {\n return flattenKeys(RUI_I18N_KEYS);\n}\n"],"mappings":";AAAA,IAAa,gBAAgB;CAC3B,gBAAgB;EACd,OAAO;EACP,YAAY;EACZ,eAAe;EACf,cAAc;EACd,eAAe;EACf,cAAc;EACd,OAAO;EACP,KAAK;EACL,cAAc;EACd,OAAO;CACT;CACA,gBAAgB,EACd,OAAO,4BACT;AACF;AAEA,SAAS,YAAY,KAAoC;CACvD,OAAO,OAAO,OAAO,GAAG,CAAC,CAAC,SAAQ,UAChC,OAAO,UAAU,WAAW,CAAC,KAAK,IAAI,YAAY,KAAK,CACzD;AACF;;;;;;AAOA,SAAgB,kBAA4B;CAC1C,OAAO,YAAY,aAAa;AAClC"}
1
+ {"version":3,"file":"keys.js","names":[],"sources":["../../src/i18n/keys.ts"],"sourcesContent":["export const RUI_I18N_KEYS = {\n dateTimePicker: {\n clear: 'rui.date_time_picker.clear',\n clearValue: 'rui.date_time_picker.clear_value',\n closeCalendar: 'rui.date_time_picker.close_calendar',\n dateAfterMax: 'rui.date_time_picker.date_after_max',\n dateBeforeMin: 'rui.date_time_picker.date_before_min',\n dateInFuture: 'rui.date_time_picker.date_in_future',\n label: 'rui.date_time_picker.label',\n now: 'rui.date_time_picker.now',\n openCalendar: 'rui.date_time_picker.open_calendar',\n pasteUnreadable: 'rui.date_time_picker.paste_unreadable',\n today: 'rui.date_time_picker.today',\n },\n timezoneSelect: {\n label: 'rui.timezone_select.label',\n },\n} as const;\n\nfunction flattenKeys(obj: Record<string, any>): string[] {\n return Object.values(obj).flatMap(value =>\n typeof value === 'string' ? [value] : flattenKeys(value),\n );\n}\n\n/**\n * Retrieves all translation keys available in the library.\n *\n * @returns the flattened translation keys\n */\nexport function translationKeys(): string[] {\n return flattenKeys(RUI_I18N_KEYS);\n}\n"],"mappings":";AAAA,IAAa,gBAAgB;CAC3B,gBAAgB;EACd,OAAO;EACP,YAAY;EACZ,eAAe;EACf,cAAc;EACd,eAAe;EACf,cAAc;EACd,OAAO;EACP,KAAK;EACL,cAAc;EACd,iBAAiB;EACjB,OAAO;CACT;CACA,gBAAgB,EACd,OAAO,4BACT;AACF;AAEA,SAAS,YAAY,KAAoC;CACvD,OAAO,OAAO,OAAO,GAAG,CAAC,CAAC,SAAQ,UAChC,OAAO,UAAU,WAAW,CAAC,KAAK,IAAI,YAAY,KAAK,CACzD;AACF;;;;;;AAOA,SAAgB,kBAA4B;CAC1C,OAAO,YAAY,aAAa;AAClC"}
package/dist/style.css CHANGED
@@ -929,6 +929,17 @@ html[data-theme="dark"], html.dark {
929
929
  max-width: 1536px;
930
930
  }
931
931
  }
932
+ .sr-only {
933
+ position: absolute;
934
+ width: 1px;
935
+ height: 1px;
936
+ padding: 0;
937
+ margin: -1px;
938
+ overflow: hidden;
939
+ clip: rect(0, 0, 0, 0);
940
+ white-space: nowrap;
941
+ border-width: 0;
942
+ }
932
943
  .pointer-events-none {
933
944
  pointer-events: none;
934
945
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "http://json.schemastore.org/web-types",
3
3
  "framework": "vue",
4
4
  "name": "@rotki/ui-library",
5
- "version": "2.25.0",
5
+ "version": "2.25.1",
6
6
  "js-types-syntax": "typescript",
7
7
  "description-markup": "markdown",
8
8
  "contributions": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rotki/ui-library",
3
- "version": "2.25.0",
3
+ "version": "2.25.1",
4
4
  "description": "A vue design system and component library for rotki",
5
5
  "type": "module",
6
6
  "keywords": [