diff --git a/packages/primevue/src/datepicker/DatePicker.vue b/packages/primevue/src/datepicker/DatePicker.vue index 5cc8220165..37027ad1ed 100755 --- a/packages/primevue/src/datepicker/DatePicker.vue +++ b/packages/primevue/src/datepicker/DatePicker.vue @@ -37,7 +37,9 @@ :pt="ptm('pcInputText')" /> - + - +
@@ -217,8 +230,15 @@ - {{ m.value }} - +
- {{ y.value }}
- +
@@ -621,25 +639,25 @@ export default { queryMatches: false, queryOrientation: null, focusedDateIndex: 0, - rawValue: null + rawValue: null, + suppressFocusOpen: false }; }, watch: { modelValue: { immediate: true, handler(newValue) { - this.updateCurrentMetaData(); this.rawValue = typeof newValue === 'string' ? this.parseValue(newValue) : newValue; + this.updateCurrentMetaData(); + if (!this.typeUpdate && !this.inline && this.input) { this.input.value = this.formatValue(this.rawValue); } this.typeUpdate = false; - if (this.$refs.clearIcon?.$el?.style) { - this.$refs.clearIcon.$el.style.display = isEmpty(newValue) ? 'none' : 'block'; - } + this.updateClearIconVisibility(!isEmpty(newValue)); } }, showTime() { @@ -693,9 +711,7 @@ export default { } else { this.input.value = this.inputFieldValue; - if (this.$refs.clearIcon?.$el?.style) { - this.$refs.clearIcon.$el.style.display = !this.$filled ? 'none' : 'block'; - } + this.updateClearIconVisibility(this.$filled); } }, updated() { @@ -1222,7 +1238,7 @@ export default { return; } - find(this.overlay, 'table td span:not([data-p-disabled="true"])').forEach((cell) => (cell.tabIndex = -1)); + find(this.overlay, 'table td[role="gridcell"]:not([data-p-disabled="true"])').forEach((cell) => (cell.tabIndex = -1)); if (event) { event.currentTarget.focus(); @@ -1245,12 +1261,14 @@ export default { } if (this.isSingleSelection() && (!this.showTime || this.hideOnDateTimeSelect)) { - if (this.input) { - this.input.focus(); - } - setTimeout(() => { this.overlayVisible = false; + + // Return keyboard focus to the trigger input after panel closes. + if (this.input) { + this.suppressFocusOpen = true; + setTimeout(() => this.input?.focus(), 0); + } }, 150); } }, @@ -2186,14 +2204,13 @@ export default { }, onDateCellKeydown(event, date, groupIndex) { event.preventDefault(); - const cellContent = event.currentTarget; - const cell = cellContent.parentElement; + const cell = event.currentTarget; const cellIndex = getIndex(cell); switch (event.code) { case 'ArrowDown': { - cellContent.tabIndex = '-1'; + cell.tabIndex = '-1'; let nextRow = cell.parentElement.nextElementSibling; @@ -2203,13 +2220,13 @@ export default { const nextTableRows = tableRows.slice(tableRowIndex + 1); let hasNextFocusableDate = nextTableRows.find((el) => { - let focusCell = el.children[cellIndex].children[0]; + let focusCell = el.children[cellIndex]; return !getAttribute(focusCell, 'data-p-disabled'); }); if (hasNextFocusableDate) { - let focusCell = hasNextFocusableDate.children[cellIndex].children[0]; + let focusCell = hasNextFocusableDate.children[cellIndex]; focusCell.tabIndex = '0'; focusCell.focus(); @@ -2227,7 +2244,7 @@ export default { } case 'ArrowUp': { - cellContent.tabIndex = '-1'; + cell.tabIndex = '-1'; if (event.altKey) { this.overlayVisible = false; @@ -2241,13 +2258,13 @@ export default { const prevTableRows = tableRows.slice(0, tableRowIndex).reverse(); let hasNextFocusableDate = prevTableRows.find((el) => { - let focusCell = el.children[cellIndex].children[0]; + let focusCell = el.children[cellIndex]; return !getAttribute(focusCell, 'data-p-disabled'); }); if (hasNextFocusableDate) { - let focusCell = hasNextFocusableDate.children[cellIndex].children[0]; + let focusCell = hasNextFocusableDate.children[cellIndex]; focusCell.tabIndex = '0'; focusCell.focus(); @@ -2266,7 +2283,7 @@ export default { } case 'ArrowLeft': { - cellContent.tabIndex = '-1'; + cell.tabIndex = '-1'; let prevCell = cell.previousElementSibling; if (prevCell) { @@ -2274,13 +2291,13 @@ export default { const prevCells = cells.slice(0, cellIndex).reverse(); let hasNextFocusableDate = prevCells.find((el) => { - let focusCell = el.children[0]; + let focusCell = el; return !getAttribute(focusCell, 'data-p-disabled'); }); if (hasNextFocusableDate) { - let focusCell = hasNextFocusableDate.children[0]; + let focusCell = hasNextFocusableDate; focusCell.tabIndex = '0'; focusCell.focus(); @@ -2296,20 +2313,20 @@ export default { } case 'ArrowRight': { - cellContent.tabIndex = '-1'; + cell.tabIndex = '-1'; let nextCell = cell.nextElementSibling; if (nextCell) { const cells = Array.from(cell.parentElement.children); const nextCells = cells.slice(cellIndex + 1); let hasNextFocusableDate = nextCells.find((el) => { - let focusCell = el.children[0]; + let focusCell = el; return !getAttribute(focusCell, 'data-p-disabled'); }); if (hasNextFocusableDate) { - let focusCell = hasNextFocusableDate.children[0]; + let focusCell = hasNextFocusableDate; focusCell.tabIndex = '0'; focusCell.focus(); @@ -2348,9 +2365,9 @@ export default { } case 'Home': { - cellContent.tabIndex = '-1'; + cell.tabIndex = '-1'; let currentRow = cell.parentElement; - let focusCell = currentRow.children[0].children[0]; + let focusCell = currentRow.children[0]; if (getAttribute(focusCell, 'data-p-disabled')) { this.navigateToMonth(event, true, groupIndex); @@ -2364,9 +2381,9 @@ export default { } case 'End': { - cellContent.tabIndex = '-1'; + cell.tabIndex = '-1'; let currentRow = cell.parentElement; - let focusCell = currentRow.children[currentRow.children.length - 1].children[0]; + let focusCell = currentRow.children[currentRow.children.length - 1]; if (getAttribute(focusCell, 'data-p-disabled')) { this.navigateToMonth(event, false, groupIndex); @@ -2380,7 +2397,7 @@ export default { } case 'PageUp': { - cellContent.tabIndex = '-1'; + cell.tabIndex = '-1'; if (event.shiftKey) { this.navigationState = { backward: true }; this.navBackward(event); @@ -2391,7 +2408,7 @@ export default { } case 'PageDown': { - cellContent.tabIndex = '-1'; + cell.tabIndex = '-1'; if (event.shiftKey) { this.navigationState = { backward: false }; this.navForward(event); @@ -2413,7 +2430,7 @@ export default { this.navBackward(event); } else { let prevMonthContainer = this.overlay.children[groupIndex - 1]; - let cells = find(prevMonthContainer, 'table td span:not([data-p-disabled="true"]):not([data-p-ink="true"])'); + let cells = find(prevMonthContainer, 'table td[role="gridcell"]:not([data-p-disabled="true"])'); let focusCell = cells[cells.length - 1]; focusCell.tabIndex = '0'; @@ -2425,7 +2442,7 @@ export default { this.navForward(event); } else { let nextMonthContainer = this.overlay.children[groupIndex + 1]; - let focusCell = findSingle(nextMonthContainer, 'table td span:not([data-p-disabled="true"]):not([data-p-ink="true"])'); + let focusCell = findSingle(nextMonthContainer, 'table td[role="gridcell"]:not([data-p-disabled="true"])'); focusCell.tabIndex = '0'; focusCell.focus(); @@ -2641,7 +2658,7 @@ export default { } else if (this.currentView === 'year') { cells = find(this.overlay, '[data-pc-section="yearview"] [data-pc-section="year"]:not([data-p-disabled="true"])'); } else { - cells = find(this.overlay, 'table td span:not([data-p-disabled="true"]):not([data-p-ink="true"])'); + cells = find(this.overlay, 'table td[role="gridcell"]:not([data-p-disabled="true"])'); } if (cells && cells.length > 0) { @@ -2653,7 +2670,7 @@ export default { } else if (this.currentView === 'year') { cell = findSingle(this.overlay, '[data-pc-section="yearview"] [data-pc-section="year"]:not([data-p-disabled="true"])'); } else { - cell = findSingle(this.overlay, 'table td span:not([data-p-disabled="true"]):not([data-p-ink="true"])'); + cell = findSingle(this.overlay, 'table td[role="gridcell"]:not([data-p-disabled="true"])'); } } @@ -2684,19 +2701,21 @@ export default { cells.forEach((cell) => (cell.tabIndex = -1)); cell = selectedCell || cells[0]; } else { - cell = findSingle(this.overlay, 'span[data-p-selected="true"]'); + let cells = find(this.overlay, 'table td[role="gridcell"]'); + + cells.forEach((gridCell) => (gridCell.tabIndex = -1)); + cell = findSingle(this.overlay, 'td[role="gridcell"][data-p-selected="true"]'); if (!cell) { - let todayCell = findSingle(this.overlay, 'td[data-p-today="true"] span:not([data-p-disabled="true"]):not([data-p-ink="true"])'); + let todayCell = findSingle(this.overlay, 'td[role="gridcell"][data-p-today="true"]:not([data-p-disabled="true"])'); if (todayCell) cell = todayCell; - else cell = findSingle(this.overlay, '.p-datepicker-calendar td span:not([data-p-disabled="true"]):not([data-p-ink="true"])'); + else cell = findSingle(this.overlay, 'table td[role="gridcell"]:not([data-p-disabled="true"])'); } } if (cell) { cell.tabIndex = '0'; - this.preventFocus = false; } }, @@ -2718,12 +2737,16 @@ export default { if (this.timeOnly) { focusableElements[0].focus(); } else { - let elementIndex = focusableElements.findIndex((el) => el.tagName === 'SPAN'); + let elementIndex = focusableElements.findIndex((el) => el.tagName === 'TD'); if (elementIndex === -1) { elementIndex = focusableElements.findIndex((el) => el.tagName === 'BUTTON'); } + if (elementIndex === -1) { + elementIndex = focusableElements.findIndex((el) => el.tagName === 'SPAN'); + } + if (elementIndex !== -1) { focusableElements[elementIndex].focus(); } else { @@ -2757,14 +2780,22 @@ export default { this.$emit('keydown', event); }, + getClearIconElement() { + return this.$refs.clearIcon?.$el || this.$refs.clearIcon; + }, + updateClearIconVisibility(visible) { + const clearIconEl = this.getClearIconElement(); + + if (clearIconEl?.style) { + clearIconEl.style.display = visible ? 'block' : 'none'; + } + }, onInput(event) { try { this.selectionStart = this.input.selectionStart; this.selectionEnd = this.input.selectionEnd; - if (this.$refs.clearIcon?.$el?.style) { - this.$refs.clearIcon.$el.style.display = isEmpty(event.target.value) ? 'none' : 'block'; - } + this.updateClearIconVisibility(!isEmpty(event.target.value)); let value = this.parseValue(event.target.value); @@ -2785,7 +2816,9 @@ export default { } }, onFocus(event) { - if (this.showOnFocus && this.isEnabled()) { + if (this.suppressFocusOpen) { + this.suppressFocusOpen = false; + } else if (this.showOnFocus && this.isEnabled()) { this.overlayVisible = true; } @@ -2799,9 +2832,7 @@ export default { this.focused = false; event.target.value = this.formatValue(this.rawValue); - if (this.$refs.clearIcon?.$el?.style) { - this.$refs.clearIcon.$el.style.display = isEmpty(event.target.value) ? 'none' : 'block'; - } + this.updateClearIconVisibility(!isEmpty(event.target.value)); }, onKeyDown(event) { if (event.code === 'ArrowDown' && this.overlay) { @@ -2823,18 +2854,25 @@ export default { this.overlayVisible = false; } } else if (event.code === 'Enter') { + let handled = false; + if (this.manualInput && event.target.value !== null && event.target.value?.trim() !== '') { try { let value = this.parseValue(event.target.value); if (this.isValidSelection(value)) { this.overlayVisible = false; + handled = true; } } catch (err) { /* NoOp */ } } + if (handled) { + event.preventDefault(); + } + this.$emit('keydown', event); } }, @@ -2853,12 +2891,33 @@ export default { getMonthName(index) { return this.$primevue.config.locale.monthNames[index]; }, + getCalendarAriaLabel(month) { + return `${this.getMonthName(month.month)} ${month.year} calendar`; + }, + getDateAriaLabel(dateMeta) { + const date = new Date(dateMeta.year, dateMeta.month, dateMeta.day); + const dayName = this.$primevue.config.locale.dayNames[date.getDay()]; + const monthName = this.$primevue.config.locale.monthNames[dateMeta.month]; + + return `${dayName}, ${monthName} ${dateMeta.day}, ${dateMeta.year}`; + }, + getMonthButtonAriaLabel(month) { + return `${this.$primevue.config.locale.chooseMonth}: ${this.getMonthName(month.month)} ${month.year}`; + }, + getYearButtonAriaLabel(month) { + return `${this.$primevue.config.locale.chooseYear}: ${this.getYear(month)}`; + }, getYear(month) { return this.currentView === 'month' ? this.currentYear : month.year; }, onClearClick() { this.updateModel(null); this.overlayVisible = false; + + if (this.input) { + this.suppressFocusOpen = true; + setTimeout(() => this.input?.focus(), 0); + } }, onOverlayClick(event) { event.stopPropagation(); @@ -2957,14 +3016,18 @@ export default { propValue = propValue[0]; } else { const start = this.parseValueForComparison(propValue[0]); - let lastVisibleMonth = new Date(start.getFullYear(), start.getMonth() + this.numberOfMonths, 1); + const end = this.parseValueForComparison(propValue[1]); - if (propValue[1] < lastVisibleMonth) { - propValue = propValue[0]; + if (this.showTime) { + propValue = end; } else { - const end = this.parseValueForComparison(propValue[1]); + let lastVisibleMonth = new Date(start.getFullYear(), start.getMonth() + this.numberOfMonths, 1); - propValue = new Date(end.getFullYear(), end.getMonth() - this.numberOfMonths + 1, 1); + if (propValue[1] < lastVisibleMonth) { + propValue = propValue[0]; + } else { + propValue = new Date(end.getFullYear(), end.getMonth() - this.numberOfMonths + 1, 1); + } } } } else if (this.isMultipleSelection()) { diff --git a/packages/primevue/src/datepicker/style/DatePickerStyle.js b/packages/primevue/src/datepicker/style/DatePickerStyle.js index ae5249a0d8..dba076ddb8 100644 --- a/packages/primevue/src/datepicker/style/DatePickerStyle.js +++ b/packages/primevue/src/datepicker/style/DatePickerStyle.js @@ -5,6 +5,21 @@ const inlineStyles = { root: ({ props }) => ({ position: props.appendTo === 'self' || props.showClear ? 'relative' : undefined }) }; +const css = () => ` +.p-datepicker-day-cell:focus, +.p-datepicker-day-cell:focus-visible { + outline: none; +} + +.p-datepicker-day-cell:focus-visible .p-datepicker-day { + outline-width: var(--p-datepicker-date-focus-ring-width); + outline-style: var(--p-datepicker-date-focus-ring-style); + outline-color: var(--p-datepicker-date-focus-ring-color); + outline-offset: var(--p-datepicker-date-focus-ring-offset); + box-shadow: var(--p-datepicker-date-focus-ring-shadow); +} +`; + const classes = { root: ({ instance, state }) => [ 'p-datepicker p-component p-inputwrapper', @@ -101,6 +116,7 @@ const classes = { export default BaseStyle.extend({ name: 'datepicker', + css, style, classes, inlineStyles