/** * The `auro-calendar` component renders the calendar month(s) shown inside the datepicker dropdown and manages date selection, range preview, and keyboard navigation. * @event auroCalendar-dateSelected - Notifies that a date has been selected in the calendar. * @event auroCalendar-monthChanged - Notifies that the visible calendar month(s) have changed. * @event narrow-changedProperties - Notifies that the calendar's narrow (mobile) layout state has changed. */ export class AuroCalendar extends RangeDatepicker { static get styles(): import("lit").CSSResult[]; static get properties(): { /** * The last month that may be displayed in the calendar. */ calendarEndMonth: { type: StringConstructor; attribute: string; reflect: boolean; }; /** * The first month that may be displayed in the calendar. */ calendarStartMonth: { type: StringConstructor; attribute: string; reflect: boolean; }; /** * The date that determines the currently visible month. */ centralDate: { type: StringConstructor; attribute: string; reflect: boolean; }; /** * The starting date of the selected range. */ dateFrom: { type: StringConstructor; attribute: string; }; /** * The ending date of the selected range. */ dateTo: { type: StringConstructor; attribute: string; }; /** * Dropdown element that contains the calendar. * @private * @type {HTMLElement} */ dropdown: HTMLElement; /** * Flag indicating if the calendar is in fullscreen mode. */ isFullscreen: { type: BooleanConstructor; attribute: string; reflect: boolean; }; /** * If declared, make bib.fullscreen.headline in HeadingDisplay. * Otherwise, Heading 600. */ largeFullscreenHeadline: { type: BooleanConstructor; attribute: string; reflect: boolean; }; /** * Maximum date. All dates after will be disabled. */ maxDate: { type: StringConstructor; attribute: string; reflect: boolean; }; /** * Minimum date. All dates before will be disabled. */ minDate: { type: StringConstructor; attribute: string; reflect: boolean; }; /** * Mobile breakpoint for responsive design. * @private */ mobileBreakpoint: { type: NumberConstructor; attribute: string; reflect: boolean; }; /** * If true, the month will be displayed before the year in the calendar header. * Passed to AuroCalendarMonth via utilitesCalendarRender. * @private */ monthFirst: { type: BooleanConstructor; attribute: string; }; /** * Number of calendars to render. * @private */ numCalendars: { type: NumberConstructor; attribute: string; }; /** * Flag indicating if the calendar is visible. */ visible: { type: BooleanConstructor; reflect: boolean; }; /** * BCP 47 locale tag (such as `en-US`) for calendar localization. * as wc-range-datepicker expects a `locale` prop, we use `localeCode` to avoid conflicts and pass the locale down to calendar-month elements. */ localeCode: { type: StringConstructor; attribute: string; }; /** * Names of all 12 months. When omitted, names are derived from `localeCode`. * @type {string[]} */ monthNames: string[]; }; /** * Per-class flag that gates the `disabledDays` deprecation warning so it * fires exactly once per page no matter how many calendars or rebuild * cycles encounter the legacy array. * @private */ private static _warnedDisabledDaysDeprecation; /** * @private */ private util; /** * @private */ private utilCal; /** * @private */ private utilCalRender; calendarStartDate: any; calendarEndDate: any; centralDate: any; showPrevMonthBtn: boolean; showNextMonthBtn: boolean; visible: boolean; largeFullscreenHeadline: boolean; isFullscreen: boolean; /** * The date of the currently active cell (Unix timestamp). * Only one cell across the entire calendar has tabindex="0" at a time. * @private */ private activeCellDate; /** * Cached reference to the active cell host. Set by setActiveCell and * refreshed by scrollToActiveCell when its cache check misses (stale * or missing). Lets scrollToActiveCell skip a full cell scan on each * arrow key. * @private */ private _activeCell; /** * Whether the #calendarGrid wrapper currently has focus. * Used to determine whether the visualFocus ring should be shown. * @private */ private _gridHasFocus; /** * @private */ private firstMonthRenderable; /** * @private */ private calendarRangeMonths; /** * @deprecated Use `auro-datepicker.blackoutDates` (an array of * `YYYY-MM-DD` ISO strings) instead. This legacy array of Unix * timestamps is still honored for backward compatibility but emits a * one-time `console.debug` the first time a non-empty value is observed. * Support will be removed in a future major release. * @private */ /** * @private * @type {number[]} */ private _disabledDays; /** * @private */ private numCalendars; /** * Captured light-DOM slot nodes, keyed by slot name, forwarded into the bib. * @private * @type {Record} */ private slots; /** * @private */ private bibtemplateTag; /** * @private */ private buttonTag; dropdown: any; /** * Unique instance ID for the live region element. * @private */ private _calendarInstanceId; /** @returns {Date|undefined} */ get centralDateObject(): Date | undefined; /** @returns {Date|undefined} */ get minDateObject(): Date | undefined; /** @returns {Date|undefined} */ get maxDateObject(): Date | undefined; /** * Updates the month and year when the user navigates to the previous calendar month. * @private * @param {Object} [options] - Optional settings. * @param {boolean} [options.skipActiveUpdate=false] - When true, skip the active cell * recomputation. Used by arrow key handlers that manage the active cell themselves. * @returns {void} */ private handlePrevMonth; /** * Updates the month and year when the user navigates to the next calendar month. * @private * @param {Object} [options] - Optional settings. * @param {boolean} [options.skipActiveUpdate=false] - When true, skip the active cell * recomputation. Used by arrow key handlers that manage the active cell themselves. * @returns {void} */ private handleNextMonth; /** * Announces the current month and year via the live region after navigation. * @private * @returns {void} */ private announceMonthChange; _focusAnnounceTimer: any; /** * Updates the active cell after month navigation (prev/next buttons). * Always moves the active cell to the first enabled date in the newly * visible months so that tabbing back to the grid lands on an enabled cell. * @private * @returns {void} */ private updateActiveCellForVisibleMonth; /** * Schedules `callback` two animation frames out, giving the child * `auro-formkit-calendar-month` and `auro-formkit-calendar-cell` elements * a full render-and-paint cycle to settle before the callback reads or * mutates DOM. * * Why two frames, not one: * 1. Lit batches property updates and renders in a microtask, so frame N * schedules the render but the new DOM may not be painted yet. * 2. Cells re-cache `_cachedButton` inside their own `updateComplete.then`, * which also lands a tick later. Reading buttons from frame N+1 * (after both renders + cache refresh have flushed) reliably hits the * new month's cells. * * Used by every code path that calls `handleNextMonth`/`handlePrevMonth` * and then needs to inspect the freshly-rendered cells (cross-month * keyboard nav, boundary events, `updateActiveCellForVisibleMonth`). * Do NOT collapse to a single rAF — it intermittently lands before * `_cachedButton` is refreshed, which silently breaks focus restoration * and `setActiveCell` lookups. * @private * @param {() => void} callback - Runs once after the month re-render and * the cells' button caches have refreshed. * @returns {void} */ private _afterMonthRender; /** * Renders all of the auro-calendar-months HTML. * @private * @returns {Object} Returns the auro-calendar-months HTML. */ private renderAllCalendars; firstRenderedMonth: any; /** * Focuses the close button inside the calendar's bibtemplate. * Used by datepicker to set initial focus when the fullscreen dialog opens. * @returns {void} */ focusCloseButton(): void; /** * Request the calendar be scrolled to a given date. * @param {String} date - The date to scroll into view. * @returns {void} */ scrollMonthIntoView(date: string): void; /** * Gets all rendered month components. * @private * @returns {Array} Array of auro-formkit-calendar-month elements. */ private getMonthComponents; /** * Picks the focusable cell whose date is closest to targetTs. Used as a * fallback after a month-boundary nav when the exact target date isn't * focusable — typically because the month re-render lagged or the date * was filtered out by isOutOfRange. When two cells are equidistant, the * navigation direction breaks the tie so the user moves the way they * pressed (forward → later cell, backward → earlier cell). * @private * @param {Array} cells - Focusable cells from getAllFocusableCells. * @param {Number} targetTs - Desired Unix timestamp (seconds). * @param {'next'|'prev'} direction - Navigation direction. * @returns {Object|null} The nearest cell, or null when cells is empty. */ private pickNearestCell; /** * Gets all focusable cells across all rendered months. * @private * @returns {Array} Array of auro-formkit-calendar-cell elements. */ private getAllFocusableCells; /** * Sets the active cell across all months. Only one cell has tabindex="0" at a time. * Uses imperative DOM manipulation — no Lit re-render triggered. DOM focus * stays on the grid wrapper; the live region (see getOrCreateLiveRegion) * is what announces the active cell to assistive tech. * @param {Number} date - Unix timestamp of the cell to activate. * @returns {void} */ setActiveCell(date: number): void; /** * Focuses the calendar grid wrapper and sets the active cell. * DOM focus stays on the grid wrapper; the aria-live region * tells the screen reader which cell is "active". * @returns {void} */ focusActiveCell(): void; /** * Shows the activeCell ring when the grid gains focus. * @private * @param {FocusEvent} [event] - The focusin event. * @returns {void} */ private handleGridFocusIn; /** * Hides the activeCell ring when the grid loses focus. * @private * @returns {void} */ private handleGridFocusOut; /** * Returns a memoized Set of blackout timestamps (seconds) drawn from both * the legacy `disabledDays` array and the datepicker's ISO `blackoutDates`. * * The cache invalidates on **reference identity** — only when the * consumer reassigns the array (`el.blackoutDates = [...]`), matching * Lit's own reactivity semantics for array properties. In-place mutations * on the existing array (`push`, `splice`, index assignment) will NOT * invalidate the cache and the new entries will be silently ignored. * Consumers must reassign to update — see the JSDoc on * `auro-datepicker.blackoutDates` for the recommended pattern. * * A shallow-equality tier was considered but rejected: it would run * O(N) work on every cell render (this method is called per-cell via * `isBlackout()`) and still wouldn't catch same-length value swaps, * offering a false sense of safety. * @private * @returns {Set} */ private _getBlackoutSet; _blackoutSet: Set | undefined; _cachedBlackoutDisabledDays: any; _cachedBlackoutDates: any; /** * One-time `console.debug` directing consumers from the legacy * `disabledDays` Unix-timestamp API to the ISO `blackoutDates` API. Fires * the first time `_getBlackoutSet` rebuilds from a non-empty * `disabledDays`; subsequent calls (on this or any other AuroCalendar * instance on the page) are silent. * @private * @returns {void} */ private _warnDisabledDaysDeprecated; /** * Computes the initial active date from data properties alone — no DOM required. * Priority: * 1. Selected date (dateFrom) if within range * 2. Today's date if not disabled (in-range and not blackout) * 3. First future non-disabled date (scans day-by-day from today up to 1 year) * 4. First previous non-disabled date (scans day-by-day from today up to 1 year) * 5. First enabled date in finite [min, max] range * 5b. First enabled date scanning forward from finite min (unbounded max) * 5c. First enabled date scanning backward from finite max (unbounded min) * 6. First in-range date (even if blackout) so focus can land somewhere * 7. Undefined — no valid target. * * @private * @param {Object} [options] - Optional settings. * @param {boolean} [options.skipDateFrom=false] - When true, skip the selected-date * shortcut (step 1). Used after month navigation so the active cell lands in the * newly visible month instead of jumping back to the selected date's month. * @returns {Number|undefined} Unix timestamp (seconds) of the date to activate, or undefined. */ private computeActiveDate; /** * Checks if a target date (unix seconds) is within the configured [min, max] range. * Returns false if the date falls outside the range, preventing navigation * to months where all dates are disabled. * @private * @param {Number} targetTs - Unix timestamp in seconds. * @returns {Boolean} True if the date is within range. */ private isDateInRange; /** * Handles arrow key navigation on the calendar grid wrapper. * Focus stays on the grid wrapper; only the visual active-cell indicator * changes. The live region announces the new active cell. * @private * @param {KeyboardEvent} event - The keyboard event. * @returns {void} */ private handleGridKeyDown; /** * Handles cross-month boundary navigation events from month components. * @private * @param {CustomEvent} event - The boundary event with direction and source date info. * @returns {void} */ private handleMonthBoundary; /** * Handles cell activation events from month components. * @private * @param {CustomEvent} event - The activation event with target date. * @returns {void} */ private handleCellActivate; /** * Handles focus events from calendar cells. * Updates the live region with an SR announcement and triggers * the imperative range preview if applicable. * @private * @param {CustomEvent} event - The calendar-cell-focused event. * @returns {void} */ private handleCellFocused; /** * Builds a full SR announcement string for a focused cell date. * Includes the localized date, range position, popover content, * and blackout status. * @private * @param {Number} date - Unix timestamp (seconds) of the focused cell. * @returns {String} The announcement string. */ private buildFocusAnnouncement; /** * Determines the range position label for a given date. * @private * @param {Number} date - Unix timestamp (seconds). * @returns {String|null} The range position label, or null. */ private getRangePositionLabel; /** * Checks whether a given date is a blackout date. Delegates to the * memoized `_getBlackoutSet` so the YYYY-MM-DD parsing and the * legacy/ISO merge rules live in exactly one place (see `blackoutUtils.js`). * @private * @param {Number} dateTs - Unix timestamp (seconds). * @returns {Boolean} True if the date is blacked out. */ private isDateBlackout; /** * Updates the range preview classes imperatively across all cells. * Only active when in range mode with dateFrom set and dateTo not yet set. * @private * @param {Number} hoveredDate - Unix timestamp of the hovered/focused date. * @returns {void} */ private updateRangePreview; /** * Clears range preview classes from all cells. * @private * @param {Object} [options] - Optional settings. * @param {boolean} [options.force=false] - When true, clears classes even * when both dateFrom and dateTo are set. Used by month nav handlers to * strip the imperative-only `lastHoveredDate` before the re-render. * The other two classes (`inRange`, `rangeDepartDate`) are classMap- * managed and get stripped as a side effect here; because classMap * remembers what it last emitted and does not diff against the actual * DOM, the following month re-render will NOT re-add them on its own. * Nav handlers must schedule `refreshCommittedRangeClasses` (via * `scheduleCommittedRangeClassRefresh`) to resync them. * @returns {void} */ private clearRangePreview; /** * Re-applies the committed-range classes across every focusable cell * after a month navigation. classMap in the cell tracks its own * previous state: once `clearRangePreview({ force: true })` strips * `inRange`/`rangeDepartDate` imperatively before the re-render, * classMap's next diff sees the same class-value it emitted before and * produces no delta, leaving the DOM without the classes even though a * full range is committed. Re-applying imperatively resyncs the two * months' cells with `dateFrom`/`dateTo`. * * Iterates `getAllFocusableCells()` — out-of-range cells (blocked by * `min`/`max`) can never carry range classes anyway, so skipping them * is correct and cheaper than a whole-grid walk. * * The cell's `applyCommittedRangeClasses` reuses the same * `isInRange`/`isDepartDate`/`isReturnDate` helpers `renderCellButton` * uses, so we don't parse dateFrom/dateTo here — the helpers already * normalize their inputs (midnight-truncation, string→int) internally. * @private * @returns {void} */ private refreshCommittedRangeClasses; /** * Schedules `refreshCommittedRangeClasses` to run after the month * re-render has flushed and the cells' button caches have refreshed. * Both `handlePrevMonth` and `handleNextMonth` need this exact call * shape; keeping it in one place prevents them from drifting apart. * * Bails synchronously when a full committed range isn't set — otherwise * every prev/next click in single-date mode (or before the user picks * both dates in range mode) pays for an unused double-rAF hop. * @private * @returns {void} */ private scheduleCommittedRangeClassRefresh; /** * Overrides the base class handler to prevent setting `this.hoveredDate` * as a reactive property. Instead, handles the range preview imperatively. * @private * @param {CustomEvent} event - The hovered-date-changed event from a month. * @returns {void} */ private hoveredDateChanged; /** * Scrolls the calendar so the active cell is visible. * * Walks the flat tree (rendered, slot-aware) outward from the active * cell's button and calls `scrollBy` on every vertically-scrollable * ancestor by whatever delta still separates the cell from that * ancestor's viewport. Native `scrollIntoView` is not used because the * cell sits inside multiple nested scroll containers (the dropdown bib's * ``, the bibtemplate's `#bodyContainer`) and the algorithm only * scrolls one of them on its own, leaving the cell short of the * viewport in mobile fullscreen. * * Uses `behavior: 'auto'` (the spec's universally-supported non-animated * value) so each `scrollBy` resolves synchronously and the next * iteration's `getBoundingClientRect` reads post-scroll positions * accurately. This also satisfies `prefers-reduced-motion` users — the * scroll containers do not set CSS `scroll-behavior: smooth`, so `auto` * is effectively instant. * * The active cell is looked up from the cache populated by * `setActiveCell`. On a cache miss (stale or absent) the cache is * refreshed from a single full scan so subsequent calls stay on the * fast path. * @private * @returns {void} */ private scrollToActiveCell; /** * Returns (and lazily creates) an aria-live region inside the dropdown's * element. This placement is critical for two reasons: * * 1. Inside the dialog's accessible scope — dialog.showModal() makes * everything outside the top-layer dialog inert, and desktop modal * mode uses _setPageInert() on document.body siblings. A live region * on document.body would be invisible to screen readers in both cases. * * 2. Not nested in shadow DOM — Chrome inconsistently observes aria-live * mutations inside shadow DOM across machines and versions. The dialog * element is only one shadow root deep (the dropdown bib's shadow DOM), * which Chrome handles reliably. The calendar's own shadow DOM (nested * inside the bib via slotting) is two+ levels deep and unreliable. * * @private * @returns {HTMLElement} The live region element. */ private getOrCreateLiveRegion; _liveRegion: any; _announceRafId: any; /** * Announces a date selection or focus change via the live region. * Uses requestAnimationFrame to ensure the clear and set happen in * separate rendering frames — Chrome may coalesce synchronous or * microtask-deferred mutations into a single accessibility tree update. * @private * @param {String} dateStr - The localized date string to announce. * @returns {void} */ private announceSelection; /** * Writes `dateStr` to the live region. If the dropdown's dialog hasn't * mounted yet (so getOrCreateLiveRegion can't attach), retries on the * next animation frame up to MAX_LIVE_REGION_RETRIES instead of silently * dropping the announcement. The retry uses the same `_announceRafId` * the double-rAF below uses, so a newer announceSelection call (or * disconnectedCallback) cancels any in-flight retry. * @private * @param {String} dateStr - The localized date string to announce. * @param {Number} attempts - Number of prior retry attempts. * @returns {void} */ private _deliverAnnouncement; /** * Debounced version of announceSelection for focus navigation. * Uses the assertive live region with a 150ms debounce so only the * final cell after rapid arrow-key traversal is announced. We * originally tried aria-live="polite" here, but VoiceOver treats * polite as "wait until idle" — which never happens during active * keyboard navigation — so the announcements were silently dropped. * * This is a documented deviation from WCAG 2.1 SC 4.1.3, which * prefers `polite` for status messages. See the "Documented * Deviation" section in components/datepicker/docs/pages/accessibility.md. * @private * @param {String} dateStr - The localized date string to announce. * @returns {void} */ private announceFocusDebounced; /** * Formats a Unix timestamp (seconds) as a localized date string for SR announcements. * @private * @param {String|Number} timestamp - Unix timestamp in seconds. * @returns {String} Localized date string. */ private formatAnnouncementDate; injectSlot(slotName: any, nodes: any): void; render(): import("lit-html").TemplateResult; } import { RangeDatepicker } from './vendor/wc-range-datepicker/range-datepicker.js';