UNPKG

@zag-js/date-picker

Version:

Core logic for the date-picker widget implemented as a state machine

916 lines (915 loc) • 35.2 kB
// src/date-picker.connect.ts import { DateFormatter, isEqualDay, isEqualMonth, isEqualYear, isSameDay, isToday, isWeekend, toCalendarDateTime } from "@internationalized/date"; import { constrainValue, ensureValidCharacters, getDateRangePreset, getDayFormatter, getDaysInWeek, getDecadeRange, getLocaleSeparator, getMonthDays, getMonthFormatter, getMonthNames, getTodayDate, getUnitDuration, getWeekDays, getWeekOfYear, getDefaultYearRange, getYearsRange, isDateOutsideRange, isDateUnavailable, isValidCharacter } from "@zag-js/date-utils"; import { ariaAttr, dataAttr, getEventKey, getNativeEvent, isComposingEvent } from "@zag-js/dom-query"; import { getPlacementSide, getPlacementStyles } from "@zag-js/popper"; import { chunk, isValueWithinRange } from "@zag-js/utils"; import { parts } from "./date-picker.anatomy.mjs"; import * as dom from "./date-picker.dom.mjs"; import { adjustStartAndEndDate, defaultTranslations, getInputPlaceholder, getRoleDescription, isDateWithinRange } from "./date-picker.utils.mjs"; function connect(service, normalize) { const { state, context, prop, send, computed, scope } = service; const startValue = context.get("startValue"); const endValue = computed("endValue"); const selectedValue = context.get("value"); const focusedValue = context.get("focusedValue"); const hoveredValue = context.get("hoveredValue"); const hoveredRangeValue = hoveredValue ? adjustStartAndEndDate([selectedValue[0], hoveredValue]) : []; const disabled = Boolean(prop("disabled")); const readOnly = Boolean(prop("readOnly")); const invalid = Boolean(prop("invalid")); const interactive = computed("isInteractive"); const empty = selectedValue.length === 0; const min = prop("min"); const max = prop("max"); const locale = prop("locale"); const timeZone = prop("timeZone"); const startOfWeek = prop("startOfWeek"); const focused = state.matches("focused"); const open = state.matches("open"); const isRangePicker = prop("selectionMode") === "range"; const isMultiPicker = prop("selectionMode") === "multiple"; const isDateUnavailableFn = prop("isDateUnavailable"); const maxSelectedDates = prop("maxSelectedDates"); const isMaxSelected = isMultiPicker && maxSelectedDates != null && selectedValue.length >= maxSelectedDates; const currentPlacement = context.get("currentPlacement"); const currentPlacementSide = currentPlacement ? getPlacementSide(currentPlacement) : void 0; const popperStyles = getPlacementStyles({ ...prop("positioning"), placement: currentPlacement }); const separator = getLocaleSeparator(locale); const translations = { ...defaultTranslations, ...prop("translations") }; function getMonthWeeks(from = startValue) { const numOfWeeks = prop("fixedWeeks") ? 6 : void 0; return getMonthDays(from, locale, numOfWeeks, startOfWeek); } function getMonths(props = {}) { const { format } = props; return getMonthNames(locale, format, focusedValue).map((label, index) => { const value = index + 1; const dateValue = focusedValue.set({ month: value }); const disabled2 = isDateOutsideRange(dateValue, min, max); return { label, value, disabled: disabled2 }; }); } function getYears() { const defaultRange = getDefaultYearRange(focusedValue, min, max); const range = getYearsRange(defaultRange); return range.map((year) => ({ label: year.toString(), value: year, disabled: !isValueWithinRange(year, min?.year, max?.year) })); } function isUnavailable(date) { return isDateUnavailable(date, isDateUnavailableFn, locale, min, max); } function focusMonth(month) { const date = startValue ?? getTodayDate(timeZone, focusedValue.calendar); send({ type: "FOCUS.SET", value: date.set({ month }) }); } function focusYear(year) { const date = startValue ?? getTodayDate(timeZone, focusedValue.calendar); send({ type: "FOCUS.SET", value: date.set({ year }) }); } function getYearTableCellState(props) { const { value, disabled: disabled2 } = props; const dateValue = focusedValue.set({ year: value }); const decadeYears = getDecadeRange(startValue.year, { strict: true }); const isOutsideVisibleRange = !decadeYears.includes(value); const isWithinMinMax = isValueWithinRange(value, min?.year, max?.year); const isInSelectedRange = isRangePicker && isDateWithinRange(dateValue, selectedValue); const isFirstInSelectedRange = isRangePicker && selectedValue[0] && isEqualYear(dateValue, selectedValue[0]); const isLastInSelectedRange = isRangePicker && selectedValue[1] && isEqualYear(dateValue, selectedValue[1]); const hasHoveredRange = isRangePicker && hoveredRangeValue.length > 0; const isInHoveredRange = hasHoveredRange && isDateWithinRange(dateValue, hoveredRangeValue); const isFirstInHoveredRange = hasHoveredRange && hoveredRangeValue[0] && isEqualYear(dateValue, hoveredRangeValue[0]); const isLastInHoveredRange = hasHoveredRange && hoveredRangeValue[1] && isEqualYear(dateValue, hoveredRangeValue[1]); const cellState = { focused: focusedValue.year === props.value, selectable: !isOutsideVisibleRange && isWithinMinMax, outsideRange: isOutsideVisibleRange, selected: !!selectedValue.find((date) => date && date.year === value), valueText: value.toString(), inRange: isInSelectedRange || isInHoveredRange, firstInRange: !!isFirstInSelectedRange, lastInRange: !!isLastInSelectedRange, inHoveredRange: !!isInHoveredRange, firstInHoveredRange: !!isFirstInHoveredRange, lastInHoveredRange: !!isLastInHoveredRange, value: dateValue, get disabled() { return disabled2 || !cellState.selectable; } }; return cellState; } function getMonthTableCellState(props) { const { value, disabled: disabled2 } = props; const dateValue = focusedValue.set({ month: value }); const formatter = getMonthFormatter(locale, timeZone, focusedValue); const isInSelectedRange = isRangePicker && isDateWithinRange(dateValue, selectedValue); const isFirstInSelectedRange = isRangePicker && selectedValue[0] && isEqualMonth(dateValue, selectedValue[0]); const isLastInSelectedRange = isRangePicker && selectedValue[1] && isEqualMonth(dateValue, selectedValue[1]); const hasHoveredRange = isRangePicker && hoveredRangeValue.length > 0; const isInHoveredRange = hasHoveredRange && isDateWithinRange(dateValue, hoveredRangeValue); const isFirstInHoveredRange = hasHoveredRange && hoveredRangeValue[0] && isEqualMonth(dateValue, hoveredRangeValue[0]); const isLastInHoveredRange = hasHoveredRange && hoveredRangeValue[1] && isEqualMonth(dateValue, hoveredRangeValue[1]); const cellState = { focused: focusedValue.month === props.value, selectable: !isDateOutsideRange(dateValue, min, max), selected: !!selectedValue.find((date) => date && date.month === value && date.year === focusedValue.year), valueText: formatter.format(dateValue.toDate(timeZone)), inRange: isInSelectedRange || isInHoveredRange, firstInRange: !!isFirstInSelectedRange, lastInRange: !!isLastInSelectedRange, inHoveredRange: !!isInHoveredRange, firstInHoveredRange: !!isFirstInHoveredRange, lastInHoveredRange: !!isLastInHoveredRange, outsideRange: false, value: dateValue, get disabled() { return disabled2 || !cellState.selectable; } }; return cellState; } function getDayTableCellState(props) { const { value, disabled: disabled2, visibleRange = computed("visibleRange") } = props; const formatter = getDayFormatter(locale, timeZone, focusedValue); const unitDuration = getUnitDuration(computed("visibleDuration")); const outsideDaySelectable = prop("outsideDaySelectable"); const end = visibleRange.start.add(unitDuration).subtract({ days: 1 }); const isOutsideRange = isDateOutsideRange(value, visibleRange.start, end); const isInSelectedRange = isRangePicker && isDateWithinRange(value, selectedValue); const isFirstInSelectedRange = isRangePicker && selectedValue[0] && isSameDay(value, selectedValue[0]); const isLastInSelectedRange = isRangePicker && selectedValue[1] && isSameDay(value, selectedValue[1]); const hasHoveredRange = isRangePicker && hoveredRangeValue.length > 0; const isInHoveredRange = hasHoveredRange && isDateWithinRange(value, hoveredRangeValue); const isFirstInHoveredRange = hasHoveredRange && hoveredRangeValue[0] && isSameDay(value, hoveredRangeValue[0]); const isLastInHoveredRange = hasHoveredRange && hoveredRangeValue[1] && isSameDay(value, hoveredRangeValue[1]); const isSelected = selectedValue.some((date) => date != null && isSameDay(value, date)); const cellState = { invalid: isDateOutsideRange(value, min, max), disabled: disabled2 || !outsideDaySelectable && isOutsideRange || isDateOutsideRange(value, min, max) || // Disable unselected dates when max is reached in multiple selection mode isMaxSelected && !isSelected, selected: isSelected, unavailable: isDateUnavailable(value, isDateUnavailableFn, locale, min, max) && !disabled2, outsideRange: isOutsideRange, today: isToday(value, timeZone), weekend: isWeekend(value, locale), value, valueText: formatter.format(value.toDate(timeZone)), get focused() { return focusedValue != null && isSameDay(value, focusedValue) && (!cellState.outsideRange || outsideDaySelectable); }, get selectable() { return !cellState.disabled && !cellState.unavailable; }, // Range states inRange: isInSelectedRange || isInHoveredRange, firstInRange: isFirstInSelectedRange, lastInRange: isLastInSelectedRange, // Preview range states inHoveredRange: isInHoveredRange, firstInHoveredRange: isFirstInHoveredRange, lastInHoveredRange: isLastInHoveredRange }; return cellState; } function getTableId2(props) { const { view = "day", id } = props; return [view, id].filter(Boolean).join(" "); } return { focused, open, disabled, invalid, readOnly, inline: !!prop("inline"), numOfMonths: prop("numOfMonths"), showWeekNumbers: !!prop("showWeekNumbers"), selectionMode: prop("selectionMode"), maxSelectedDates, isMaxSelected, view: context.get("view"), getRangePresetValue(preset) { return getDateRangePreset(preset, locale, timeZone); }, getWeekNumber(week) { const firstDay = week[0]; return firstDay ? getWeekOfYear(firstDay, locale) : 0; }, getDaysInWeek(week, from = startValue) { return getDaysInWeek(week, from, locale, startOfWeek); }, getOffset(duration) { const from = startValue.add(duration); const end = endValue.add(duration); const formatter = getMonthFormatter(locale, timeZone, focusedValue); return { visibleRange: { start: from, end }, weeks: getMonthWeeks(from), visibleRangeText: { start: formatter.format(from.toDate(timeZone)), end: formatter.format(end.toDate(timeZone)) } }; }, getMonthWeeks, isUnavailable, weeks: getMonthWeeks(), weekDays: getWeekDays(startValue, startOfWeek, timeZone, locale), visibleRangeText: computed("visibleRangeText"), value: selectedValue, valueAsDate: selectedValue.filter((date) => date != null).map((date) => date.toDate(timeZone)), valueAsString: computed("valueAsString"), focusedValue, focusedValueAsDate: focusedValue?.toDate(timeZone), focusedValueAsString: prop("format")(focusedValue, { locale, timeZone }), visibleRange: computed("visibleRange"), selectToday() { const value = constrainValue(getTodayDate(timeZone, focusedValue.calendar), min, max); send({ type: "VALUE.SET", value: [value] }); }, setValue(values) { const computedValue = values.map((date) => constrainValue(date, min, max)); send({ type: "VALUE.SET", value: computedValue }); }, setTime(time, index = 0) { const values = Array.from(selectedValue); let dateValue = values[index]; if (!dateValue) return; if (!("hour" in dateValue)) { dateValue = toCalendarDateTime(dateValue); } dateValue = dateValue.set({ hour: time.hour ?? ("hour" in dateValue ? dateValue.hour : 0), minute: time.minute ?? ("minute" in dateValue ? dateValue.minute : 0), second: time.second ?? ("second" in dateValue ? dateValue.second : 0), millisecond: time.millisecond ?? ("millisecond" in dateValue ? dateValue.millisecond : 0) }); values[index] = constrainValue(dateValue, min, max); send({ type: "VALUE.SET", value: values }); }, clearValue(options = {}) { const { focus = true } = options; send({ type: "VALUE.CLEAR", focus }); }, setFocusedValue(value) { send({ type: "FOCUS.SET", value }); }, setOpen(nextOpen) { if (prop("inline")) return; const open2 = state.matches("open"); if (open2 === nextOpen) return; send({ type: nextOpen ? "OPEN" : "CLOSE" }); }, focusMonth, focusYear, getYears, getMonths, getYearsGrid(props = {}) { const { columns = 1 } = props; const years = getDecadeRange(startValue.year, { strict: true }).map((year) => ({ label: year.toString(), value: year, disabled: !isValueWithinRange(year, min?.year, max?.year) })); return chunk(years, columns); }, getDecade() { const years = getDecadeRange(startValue.year, { strict: true }); return { start: years.at(0), end: years.at(-1) }; }, getMonthsGrid(props = {}) { const { columns = 1, format } = props; return chunk(getMonths({ format }), columns); }, format(value, opts = { month: "long", year: "numeric" }) { return new DateFormatter(locale, { ...opts, calendar: value.calendar.identifier }).format(value.toDate(timeZone)); }, setView(view) { send({ type: "VIEW.SET", view }); }, goToNext() { send({ type: "GOTO.NEXT", view: context.get("view") }); }, goToPrev() { send({ type: "GOTO.PREV", view: context.get("view") }); }, getRootProps() { return normalize.element({ ...parts.root.attrs, dir: prop("dir"), id: dom.getRootId(scope), "data-state": open ? "open" : "closed", "data-disabled": dataAttr(disabled), "data-readonly": dataAttr(readOnly), "data-empty": dataAttr(empty) }); }, getLabelProps(props = {}) { const { index = 0 } = props; return normalize.label({ ...parts.label.attrs, id: dom.getLabelId(scope, index), dir: prop("dir"), htmlFor: dom.getInputId(scope, index), "data-state": open ? "open" : "closed", "data-index": index, "data-disabled": dataAttr(disabled), "data-readonly": dataAttr(readOnly) }); }, getControlProps() { return normalize.element({ ...parts.control.attrs, dir: prop("dir"), id: dom.getControlId(scope), "data-disabled": dataAttr(disabled), "data-placeholder-shown": dataAttr(empty) }); }, getRangeTextProps() { return normalize.element({ ...parts.rangeText.attrs, dir: prop("dir") }); }, getContentProps() { return normalize.element({ ...parts.content.attrs, hidden: !open, dir: prop("dir"), "data-state": open ? "open" : "closed", "data-placement": currentPlacement, "data-side": currentPlacementSide, "data-inline": dataAttr(prop("inline")), id: dom.getContentId(scope), tabIndex: -1, role: "application", "aria-roledescription": "datepicker", "aria-label": translations.content }); }, getTableProps(props = {}) { const { view = "day", columns = view === "day" ? 7 : 4 } = props; const uid = getTableId2(props); return normalize.element({ ...parts.table.attrs, role: "grid", "data-columns": columns, "aria-roledescription": getRoleDescription(view), id: dom.getTableId(scope, uid), "aria-readonly": ariaAttr(readOnly), "aria-disabled": ariaAttr(disabled), "aria-multiselectable": ariaAttr(prop("selectionMode") !== "single"), "data-view": view, dir: prop("dir"), tabIndex: -1, onKeyDown(event) { if (event.defaultPrevented) return; const keyMap = { Enter() { if (view === "day" && isUnavailable(focusedValue)) return; if (view === "month") { const cellState = getMonthTableCellState({ value: focusedValue.month }); if (!cellState.selectable) return; } if (view === "year") { const cellState = getYearTableCellState({ value: focusedValue.year }); if (!cellState.selectable) return; } send({ type: "TABLE.ENTER", view, columns, focus: true }); }, ArrowLeft() { send({ type: "TABLE.ARROW_LEFT", view, columns, focus: true }); }, ArrowRight() { send({ type: "TABLE.ARROW_RIGHT", view, columns, focus: true }); }, ArrowUp() { send({ type: "TABLE.ARROW_UP", view, columns, focus: true }); }, ArrowDown() { send({ type: "TABLE.ARROW_DOWN", view, columns, focus: true }); }, PageUp(event2) { send({ type: "TABLE.PAGE_UP", larger: event2.shiftKey, view, columns, focus: true }); }, PageDown(event2) { send({ type: "TABLE.PAGE_DOWN", larger: event2.shiftKey, view, columns, focus: true }); }, Home() { send({ type: "TABLE.HOME", view, columns, focus: true }); }, End() { send({ type: "TABLE.END", view, columns, focus: true }); } }; const exec = keyMap[getEventKey(event, { dir: prop("dir") })]; if (exec) { exec(event); event.preventDefault(); event.stopPropagation(); } }, onPointerLeave() { send({ type: "TABLE.POINTER_LEAVE" }); }, onPointerDown() { send({ type: "TABLE.POINTER_DOWN", view }); }, onPointerUp() { send({ type: "TABLE.POINTER_UP", view }); } }); }, getTableHeadProps(props = {}) { const { view = "day" } = props; return normalize.element({ ...parts.tableHead.attrs, "aria-hidden": true, dir: prop("dir"), "data-view": view, "data-disabled": dataAttr(disabled) }); }, getTableHeaderProps(props = {}) { const { view = "day" } = props; return normalize.element({ ...parts.tableHeader.attrs, dir: prop("dir"), "data-view": view, "data-disabled": dataAttr(disabled) }); }, getTableBodyProps(props = {}) { const { view = "day" } = props; return normalize.element({ ...parts.tableBody.attrs, "data-view": view, "data-disabled": dataAttr(disabled) }); }, getTableRowProps(props = {}) { const { view = "day" } = props; return normalize.element({ ...parts.tableRow.attrs, "aria-disabled": ariaAttr(disabled), "data-disabled": dataAttr(disabled), "data-view": view }); }, getWeekNumberHeaderCellProps(props = {}) { const { view = "day" } = props; return normalize.element({ ...parts.tableCell.attrs, scope: "col", "aria-label": translations.weekColumnHeader, "data-view": view, "data-type": "week-number", "data-disabled": dataAttr(disabled) }); }, getWeekNumberCellProps(props) { const { weekIndex, week } = props; const weekNumber = week[0] ? getWeekOfYear(week[0], locale) : 0; return normalize.element({ ...parts.tableCell.attrs, role: "rowheader", "aria-label": translations.weekNumberCell?.(weekNumber), "data-view": "day", "data-week-index": weekIndex, "data-type": "week-number", "data-disabled": dataAttr(disabled) }); }, getDayTableCellState, getDayTableCellProps(props) { const { value } = props; const cellState = getDayTableCellState(props); return normalize.element({ ...parts.tableCell.attrs, role: "gridcell", "aria-disabled": ariaAttr(!cellState.selectable), "aria-selected": cellState.selected || cellState.inRange, "aria-invalid": ariaAttr(cellState.invalid), "aria-current": cellState.today ? "date" : void 0, "data-value": value.toString() }); }, getDayTableCellTriggerProps(props) { const { value } = props; const cellState = getDayTableCellState(props); return normalize.element({ ...parts.tableCellTrigger.attrs, id: dom.getCellTriggerId(scope, value.toString()), role: "button", dir: prop("dir"), tabIndex: cellState.focused ? 0 : -1, "aria-label": translations.dayCell(cellState), "aria-disabled": ariaAttr(!cellState.selectable), "aria-invalid": ariaAttr(cellState.invalid), "data-disabled": dataAttr(!cellState.selectable), "data-selectable": dataAttr(cellState.selectable), "data-selected": dataAttr(cellState.selected), "data-value": value.toString(), "data-view": "day", "data-today": dataAttr(cellState.today), "data-focus": dataAttr(cellState.focused), "data-unavailable": dataAttr(cellState.unavailable), "data-range-start": dataAttr(cellState.firstInRange), "data-range-end": dataAttr(cellState.lastInRange), "data-in-range": dataAttr(cellState.inRange), "data-outside-range": dataAttr(cellState.outsideRange), "data-weekend": dataAttr(cellState.weekend), "data-in-hover-range": dataAttr(cellState.inHoveredRange), "data-hover-range-start": dataAttr(cellState.firstInHoveredRange), "data-hover-range-end": dataAttr(cellState.lastInHoveredRange), onClick(event) { if (event.defaultPrevented) return; if (!cellState.selectable) return; send({ type: "CELL.CLICK", cell: "day", value }); }, onPointerMove: isRangePicker ? (event) => { if (event.pointerType === "touch") return; if (!cellState.selectable) return; const focus = !scope.isActiveElement(event.currentTarget); if (hoveredValue && isEqualDay(value, hoveredValue)) return; send({ type: "CELL.POINTER_MOVE", cell: "day", value, focus, outsideRange: cellState.outsideRange }); } : void 0 }); }, getMonthTableCellState, getMonthTableCellProps(props) { const { value, columns } = props; const cellState = getMonthTableCellState(props); return normalize.element({ ...parts.tableCell.attrs, dir: prop("dir"), colSpan: columns, role: "gridcell", "aria-selected": ariaAttr(cellState.selected || cellState.inRange), "data-selected": dataAttr(cellState.selected), "aria-disabled": ariaAttr(!cellState.selectable), "data-value": value }); }, getMonthTableCellTriggerProps(props) { const { value } = props; const cellState = getMonthTableCellState(props); return normalize.element({ ...parts.tableCellTrigger.attrs, id: dom.getCellTriggerId(scope, value.toString()), role: "button", dir: prop("dir"), tabIndex: cellState.focused ? 0 : -1, "aria-label": cellState.valueText, "aria-disabled": ariaAttr(!cellState.selectable), "data-disabled": dataAttr(!cellState.selectable), "data-selectable": dataAttr(cellState.selectable), "data-selected": dataAttr(cellState.selected), "data-value": value, "data-view": "month", "data-focus": dataAttr(cellState.focused), "data-outside-range": dataAttr(cellState.outsideRange), "data-range-start": dataAttr(cellState.firstInRange), "data-range-end": dataAttr(cellState.lastInRange), "data-in-range": dataAttr(cellState.inRange), "data-in-hover-range": dataAttr(cellState.inHoveredRange), "data-hover-range-start": dataAttr(cellState.firstInHoveredRange), "data-hover-range-end": dataAttr(cellState.lastInHoveredRange), onClick(event) { if (event.defaultPrevented) return; if (!cellState.selectable) return; send({ type: "CELL.CLICK", cell: "month", value }); }, onPointerMove: isRangePicker ? (event) => { if (event.pointerType === "touch") return; if (!cellState.selectable) return; const focus = !scope.isActiveElement(event.currentTarget); if (hoveredValue && cellState.value && isEqualMonth(cellState.value, hoveredValue)) return; send({ type: "CELL.POINTER_MOVE", cell: "month", value: cellState.value, focus }); } : void 0 }); }, getYearTableCellState, getYearTableCellProps(props) { const { value, columns } = props; const cellState = getYearTableCellState(props); return normalize.element({ ...parts.tableCell.attrs, dir: prop("dir"), colSpan: columns, role: "gridcell", "aria-selected": ariaAttr(cellState.selected || cellState.inRange), "data-selected": dataAttr(cellState.selected), "aria-disabled": ariaAttr(!cellState.selectable), "data-value": value }); }, getYearTableCellTriggerProps(props) { const { value } = props; const cellState = getYearTableCellState(props); return normalize.element({ ...parts.tableCellTrigger.attrs, id: dom.getCellTriggerId(scope, value.toString()), role: "button", dir: prop("dir"), tabIndex: cellState.focused ? 0 : -1, "aria-label": cellState.valueText, "aria-disabled": ariaAttr(!cellState.selectable), "data-disabled": dataAttr(!cellState.selectable), "data-selectable": dataAttr(cellState.selectable), "data-selected": dataAttr(cellState.selected), "data-value": value, "data-view": "year", "data-focus": dataAttr(cellState.focused), "data-outside-range": dataAttr(cellState.outsideRange), "data-range-start": dataAttr(cellState.firstInRange), "data-range-end": dataAttr(cellState.lastInRange), "data-in-range": dataAttr(cellState.inRange), "data-in-hover-range": dataAttr(cellState.inHoveredRange), "data-hover-range-start": dataAttr(cellState.firstInHoveredRange), "data-hover-range-end": dataAttr(cellState.lastInHoveredRange), onClick(event) { if (event.defaultPrevented) return; if (!cellState.selectable) return; send({ type: "CELL.CLICK", cell: "year", value }); }, onPointerMove: isRangePicker ? (event) => { if (event.pointerType === "touch") return; if (!cellState.selectable) return; const focus = !scope.isActiveElement(event.currentTarget); if (hoveredValue && cellState.value && isEqualYear(cellState.value, hoveredValue)) return; send({ type: "CELL.POINTER_MOVE", cell: "year", value: cellState.value, focus }); } : void 0 }); }, getNextTriggerProps(props = {}) { const { view = "day" } = props; const isDisabled = disabled || !computed("isNextVisibleRangeValid"); return normalize.button({ ...parts.nextTrigger.attrs, dir: prop("dir"), id: dom.getNextTriggerId(scope, view), type: "button", "aria-label": translations.nextTrigger(view), disabled: isDisabled, "data-disabled": dataAttr(isDisabled), onClick(event) { if (event.defaultPrevented) return; send({ type: "GOTO.NEXT", view }); } }); }, getPrevTriggerProps(props = {}) { const { view = "day" } = props; const isDisabled = disabled || !computed("isPrevVisibleRangeValid"); return normalize.button({ ...parts.prevTrigger.attrs, dir: prop("dir"), id: dom.getPrevTriggerId(scope, view), type: "button", "aria-label": translations.prevTrigger(view), disabled: isDisabled, "data-disabled": dataAttr(isDisabled), onClick(event) { if (event.defaultPrevented) return; send({ type: "GOTO.PREV", view }); } }); }, getClearTriggerProps() { return normalize.button({ ...parts.clearTrigger.attrs, id: dom.getClearTriggerId(scope), dir: prop("dir"), type: "button", "aria-label": translations.clearTrigger, hidden: !selectedValue.length, onClick(event) { if (event.defaultPrevented) return; send({ type: "VALUE.CLEAR" }); } }); }, getTriggerProps() { return normalize.button({ ...parts.trigger.attrs, id: dom.getTriggerId(scope), dir: prop("dir"), type: "button", "data-placement": currentPlacement, "data-side": currentPlacementSide, "aria-label": translations.trigger(open), "aria-controls": dom.getContentId(scope), "aria-expanded": open, "data-state": open ? "open" : "closed", "data-placeholder-shown": dataAttr(empty), "aria-haspopup": "grid", disabled, onClick(event) { if (event.defaultPrevented) return; if (!interactive) return; send({ type: "TRIGGER.CLICK" }); } }); }, getViewProps(props = {}) { const { view = "day" } = props; return normalize.element({ ...parts.view.attrs, "data-view": view, hidden: context.get("view") !== view }); }, getViewTriggerProps(props = {}) { const { view = "day" } = props; return normalize.button({ ...parts.viewTrigger.attrs, "data-view": view, dir: prop("dir"), id: dom.getViewTriggerId(scope, view), type: "button", disabled, "aria-label": translations.viewTrigger(view), onClick(event) { if (event.defaultPrevented) return; if (!interactive) return; send({ type: "VIEW.TOGGLE", src: "viewTrigger" }); } }); }, getViewControlProps(props = {}) { const { view = "day" } = props; return normalize.element({ ...parts.viewControl.attrs, "data-view": view, dir: prop("dir") }); }, getInputProps(props = {}) { const { index = 0, fixOnBlur = true } = props; return normalize.input({ ...parts.input.attrs, id: dom.getInputId(scope, index), autoComplete: "off", autoCorrect: "off", spellCheck: "false", dir: prop("dir"), name: prop("name"), "data-index": index, "data-state": open ? "open" : "closed", "data-placeholder-shown": dataAttr(empty), readOnly, disabled, required: prop("required"), "aria-invalid": ariaAttr(invalid), "data-invalid": dataAttr(invalid), placeholder: prop("placeholder") || getInputPlaceholder(locale), defaultValue: computed("valueAsString")[index], onBeforeInput(event) { const { data } = getNativeEvent(event); if (!isValidCharacter(data, separator)) { event.preventDefault(); } }, onClick(event) { if (event.defaultPrevented) return; if (!prop("openOnClick")) return; if (!interactive) return; send({ type: "OPEN", src: "input.click" }); }, onFocus() { send({ type: "INPUT.FOCUS", index }); }, onBlur(event) { const value = event.currentTarget.value.trim(); send({ type: "INPUT.BLUR", value, index, fixOnBlur }); }, onKeyDown(event) { if (event.defaultPrevented) return; if (!interactive) return; const keyMap = { Enter(event2) { if (isComposingEvent(event2)) return; if (isUnavailable(focusedValue)) return; if (event2.currentTarget.value.trim() === "") return; send({ type: "INPUT.ENTER", value: event2.currentTarget.value, index }); } }; const exec = keyMap[event.key]; if (exec) { exec(event); event.preventDefault(); } }, onInput(event) { const value = event.currentTarget.value; send({ type: "INPUT.CHANGE", value: ensureValidCharacters(value, separator), index }); } }); }, getMonthSelectProps() { return normalize.select({ ...parts.monthSelect.attrs, id: dom.getMonthSelectId(scope), "aria-label": translations.monthSelect, disabled, dir: prop("dir"), defaultValue: startValue.month, onChange(event) { focusMonth(Number(event.currentTarget.value)); } }); }, getYearSelectProps() { return normalize.select({ ...parts.yearSelect.attrs, id: dom.getYearSelectId(scope), disabled, "aria-label": translations.yearSelect, dir: prop("dir"), defaultValue: startValue.year, onChange(event) { focusYear(Number(event.currentTarget.value)); } }); }, getPositionerProps() { return normalize.element({ id: dom.getPositionerId(scope), ...parts.positioner.attrs, dir: prop("dir"), style: popperStyles.floating }); }, getPresetTriggerProps(props) { const value = Array.isArray(props.value) ? props.value : getDateRangePreset(props.value, locale, timeZone); const valueAsString = value.filter((item) => item != null).map((item) => item.toDate(timeZone).toDateString()); return normalize.button({ ...parts.presetTrigger.attrs, "aria-label": translations.presetTrigger(valueAsString), type: "button", onClick(event) { if (event.defaultPrevented) return; send({ type: "PRESET.CLICK", value }); } }); } }; } export { connect };