Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | import { LightningElement } from 'lwc';
import { setToMidnight } from 'sds/utils';
export default class DatePicker extends LightningElement {
static shadowSupportMode = 'native';
_calendarData; // Track the calendar data
_selectedDateValue = ''; // Track the selected date value
_eventListenersAdded = false; // Flag to ensure event listeners are added only once
_focusTrapActive = false; // Flag to track if the focus trap is active
_popoverOpen = false; // Flag to track if the popover is open
_directInputChange = false; // Flag to track direct input changes
// Lifecycle hooks
renderedCallback() {
// Add event listeners after every render to ensure elements are present
if (!this._eventListenersAdded) {
this._addEventListeners();
this._eventListenersAdded = true;
}
}
// Private methods
_addEventListeners() {
// Calendar/input event listeners
this.template.addEventListener('calendardataready', this.handleCalendarData.bind(this));
this.template.addEventListener('dateselected', this.handleDateSelected.bind(this));
this.template.addEventListener('blur', this.handleInputDateBlur.bind(this));
// Focustrap event listeners
this.template.addEventListener('focusin', this.handleFocusIn.bind(this));
this.template.addEventListener('popoverclosed', this.handlePopoverClosed.bind(this));
// Keydown event listener on the input
this.template
.querySelector('sds-input-date')
?.shadowRoot.querySelector('input')
?.addEventListener('keydown', this.handleInputKeyDown.bind(this));
}
// Event handlers
/**
* Updates the calendar data with the received event detail.
*
* @param {Event} event - The event containing the updated calendar data.
* @return {void}
*/
handleCalendarData(event) {
this._calendarData = event.detail;
}
/**
* Handles the event when a date is selected.
*
* @param {Event} event - The event object containing the selected date.
* @return {void}
*/
handleDateSelected(event) {
this._directInputChange = false; // Reset the flag when a date is selected from the calendar
const isoDate = event.detail.selectedDate;
this.updateInputDate(isoDate);
}
/**
* Handles keydown events on the input element.
*
* @param {KeyboardEvent} event - The keydown event
* @return {void}
*/
handleInputKeyDown(event) {
if (event.key === 'ArrowDown') {
// Set the central date to the current date
this._centralDate = setToMidnight(new Date());
// Open the popover manager
const popoverManager = this.template.querySelector('sds-popover-manager');
if (popoverManager) {
popoverManager.open();
this._popoverOpen = true;
// Determine which date to use: selected date or central date
const dateToUse = this._selectedDateValue ? new Date(this._selectedDateValue) : this._centralDate;
const dateString = dateToUse.toString(); // Full date string
const tdSelector = `td[data-date^="${dateString}"]`; // Match the start of the full date string
// Find the calendar and the specific date element
const calendar = this.template.querySelector('sds-calendar');
if (calendar) {
const calendarShadowRoot = calendar.shadowRoot;
const dateTd = calendarShadowRoot.querySelector(tdSelector);
if (dateTd) {
// Focus on the date element
dateTd.focus();
// Call the activate focus trap and set focus on the date element
const focusTrapManager = this.template.querySelector('sds-focus-trap-manager');
if (focusTrapManager) {
focusTrapManager.activateFocusTrap(null, dateTd);
}
}
}
}
}
}
/**
* Handles the blur event of the input date.
*
* @param {Event} event - The blur event.
* @return {void}
*/
handleInputDateBlur(event) {
this._directInputChange = true; // Set the flag to indicate a direct input change
const isoDate = event.detail.isoDate;
this.updateCalendarDate(isoDate);
}
/**
* Handles the focus in event on the calendar element.
*
* @param {Event} event - The focus in event.
* @return {void}
*/
handleFocusIn(event) {
if (event.target.matches('sds-calendar')) {
this.updateFocusTrapStatus(true, event); // Activate focus trap on calendar focus
this._popoverOpen = true;
}
}
/**
* Formats a date to "day month, year" format using the InputDate API.
* The InputDate API is used to format the date in the input field (e.g. month with long format)
*
* @param {string} isoDate - The ISO date string.
* @return {string} - The formatted date.
*/
formatDate(isoDate) {
const inputDateElement = this.template.querySelector('sds-input-date');
if (inputDateElement) {
// Manually parse the ISO date string. Do not set to midnight or else the formatted date will be incorrect
const [year, month, day] = isoDate.split('-').map((part) => parseInt(part, 10));
return inputDateElement._formatDate(day.toString(), month.toString(), year.toString());
}
return '';
}
/**
* Handles the event when the popover is closed.
*
* @param {Event} event - The event object.
* @return {void}
*/
handlePopoverClosed(event) {
// Check if the popover was open before handling the close event
if (!this._popoverOpen) {
return;
}
// Mark the popover as closed
this._popoverOpen = false;
// Deactivate focus trap on popover close
this.updateFocusTrapStatus(false, event);
// Only update the input date if the change did not come directly from the input field
if (this._directInputChange) {
this._directInputChange = false; // Reset the flag immediately
return;
}
if (this._selectedDateValue) {
const formattedDate = this.formatDate(this._selectedDateValue);
this.updateInputDate(formattedDate);
}
}
// Private methods
/**
* Updates the input date with the given date.
*
* @param {string} date - The date to update the input date with.
* @return {void}
*/
updateInputDate(date) {
const inputDateElement = this.template.querySelector('sds-input-date');
if (inputDateElement) {
// Update value, isodate, and dateObject to ensure date in sync for all cases (tab, input change, calendar selection)
inputDateElement.value = date;
inputDateElement.isoDate = new Date(date).toISOString().split('T')[0]; // Ensure the isoDate is still in ISO format
inputDateElement.validateAndSetDate(date); // Call validateAndSetDate, which will use _handleValidity to update dateObject
}
this._selectedDateValue = date; // Update the tracked selected date value
}
/**
* Updates the selected date of the calendar with the provided ISO date.
*
* @param {string} isoDate - The ISO date to set as the selected date.
* @return {void}
*/
updateCalendarDate(isoDate) {
const calendarElement = this.template.querySelector('sds-calendar');
if (calendarElement) {
calendarElement.updateSelectedDate(isoDate);
} else {
console.error('datepicker updateCalendarDate sds-calendar element not found');
}
}
/**
* Updates the focus trap status based on the provided value.
*
* @param {boolean} isActive - Indicates whether the focus trap should be activated or deactivated.
* @param {Event} event - The event that triggered the update.
* @return {void}
*/
updateFocusTrapStatus(isActive, event) {
const focusTrapManager = this.template.querySelector('sds-focus-trap-manager');
if (focusTrapManager) {
if (isActive && !focusTrapManager.focusTrapActive) {
focusTrapManager.activateFocusTrap(event);
} else if (!isActive && focusTrapManager.focusTrapActive) {
focusTrapManager.deactivateFocusTrap(event);
}
}
}
}
|