@trimble-oss/moduswebcomponents
Version:
Modus Web Components is a modern, accessible UI library built with Stencil JS that provides reusable web components following Trimble's Modus design system. This updated version focuses on improved flexibility, enhanced theming options, comprehensive cust
896 lines (891 loc) • 52.4 kB
JavaScript
import { p as proxyCustomElement, H, d as createEvent, h, c as Host } from './p-X1tirp06.js';
import { i as inheritAriaAttributes } from './p-VPqXjOQn.js';
import { d as defineCustomElement$6 } from './p-CZDjVvLY.js';
import { d as defineCustomElement$5 } from './p-s6LDESOI.js';
import { d as defineCustomElement$4 } from './p-kjsS48iG.js';
import { d as defineCustomElement$3 } from './p-DtlnhsZ5.js';
import { d as defineCustomElement$2 } from './p-D2LdPRKQ.js';
import { c as createPopper } from './p-BfP9ezJQ.js';
const convertPropsToClasses = ({ bordered, feedback, readOnly, size, }) => {
let classes = '';
if (bordered) {
classes = `${classes} modus-wc-input-bordered`;
}
if (feedback) {
classes = `${classes} modus-wc-input--${feedback.level}`;
}
if (readOnly) {
classes = `${classes} modus-wc-date-input--readonly`;
}
if (size) {
classes = `${classes} modus-wc-input-${size}`;
}
return classes.trim();
};
class DatePickerCalendar {
constructor(firstDayOfWeek = 0) {
this.currentDate = new Date();
this.currentMonthDates = [];
this.firstDayOfWeek = 0; // Default to Sunday
this.firstDayOfWeek = firstDayOfWeek;
const today = new Date();
this.gotoDate(today.getFullYear(), today.getMonth());
}
get selectedYear() {
return this.currentDate.getFullYear();
}
get selectedMonth() {
return this.currentDate.getMonth();
}
get dates() {
return this.currentMonthDates;
}
addMonthOffset(offset) {
this.gotoDate(this.currentDate.getFullYear(), this.currentDate.getMonth() + offset);
return this;
}
gotoDate(year, month) {
this.currentDate = new Date(year, month, 1);
this.calculateDates();
}
getDaysOfWeek(locale, firstDayOfWeek = 0) {
/**
* Nov 1st, 2020 starts on a Sunday,
* assumes weeks start on Sunday,
* but is configurable via `firstDayOfWeek`.
*/
const intl = new Intl.DateTimeFormat(locale, { weekday: 'short' });
const startDate = new Date('11/01/2020');
const daysOfWeek = [];
/**
* For each day of the week,
* get the day name.
*/
for (let i = firstDayOfWeek; i < firstDayOfWeek + 7; i++) {
const currentDate = new Date(startDate);
currentDate.setDate(currentDate.getDate() + i);
const d = intl.format(currentDate);
daysOfWeek.push(d.toUpperCase().startsWith('SA') ? d : d.slice(0, 2));
}
return daysOfWeek;
}
/**
* Get ISO week number for a given date
* ISO 8601: Week 1 is the week with the year's first Thursday
*/
getWeekNumber(date, weekStart = 1) {
function isoWeek(d) {
const temp = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
const day = temp.getUTCDay() || 7;
temp.setUTCDate(temp.getUTCDate() + 4 - day);
const yearStart = new Date(Date.UTC(temp.getUTCFullYear(), 0, 1));
return Math.ceil(((temp.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
}
// --- FIND START OF WEEK ---
const day = date.getDay();
const diff = (day - weekStart + 7) % 7;
const weekStartDate = new Date(date);
weekStartDate.setDate(date.getDate() - diff);
// --- COLLECT ALL WEEK NUMBERS ---
const weekCounts = {};
for (let i = 0; i < 7; i++) {
const d = new Date(weekStartDate);
d.setDate(weekStartDate.getDate() + i);
const w = isoWeek(d);
weekCounts[w] = (weekCounts[w] || 0) + 1;
}
// --- PICK WEEK WITH MOST OCCURRENCES ---
let majorityWeek = 0;
let max = 0;
for (const [weekStr, count] of Object.entries(weekCounts)) {
const week = Number(weekStr);
if (count > max) {
max = count;
majorityWeek = week;
}
}
// Return double-digit week number
return majorityWeek.toString().padStart(2, '0');
}
calculateDates() {
const dates = [];
const year = this.currentDate.getFullYear();
const month = this.currentDate.getMonth();
// Get first day of current month
const firstDayOfMonth = new Date(year, month, 1);
const dayOfWeek = firstDayOfMonth.getDay(); // 0 = Sunday, 1 = Monday, etc.
// Simple calculation: if month starts on Wednesday (3) and we want Monday (1) as first day,
// we need to show 2 days from previous month (3 - 1 = 2)
// But if month starts on Sunday (0) and we want Monday (1) as first day,
// we need to show 6 days from previous month (0 - 1 = -1, so we add 7: 6)
let daysToSubtract = dayOfWeek - this.firstDayOfWeek;
if (daysToSubtract < 0) {
daysToSubtract += 7;
}
// Start from the previous month's dates to fill the first week
const startDate = new Date(year, month, 1 - daysToSubtract);
// Generate 42 dates (6 weeks * 7 days) to ensure consistent 6-row layout
for (let i = 0; i < 42; i++) {
const date = new Date(startDate);
date.setDate(startDate.getDate() + i);
dates.push(date);
}
this.currentMonthDates = dates;
}
}
const modusWcDateCss = ".modus-wc-input--error{border-color:var(--modus-wc-color-error) !important}.modus-wc-input--info{border-color:var(--modus-wc-color-info) !important}.modus-wc-input--success{border-color:var(--modus-wc-color-success) !important}.modus-wc-input--warning{border-color:var(--modus-wc-color-warning) !important}.modus-wc-input-xs{height:var(--modus-wc-size-xs);min-height:var(--modus-wc-size-xs)}.modus-wc-input-sm{height:var(--modus-wc-size-sm);min-height:var(--modus-wc-size-sm)}.modus-wc-input-md{height:var(--modus-wc-size-md);min-height:var(--modus-wc-size-md)}.modus-wc-input-lg{height:var(--modus-wc-size-lg);min-height:var(--modus-wc-size-lg)}.modus-wc-input-xl{height:var(--modus-wc-size-xl);min-height:var(--modus-wc-size-xl)}modus-wc-date .modus-wc-date-input{background-color:inherit}modus-wc-date .modus-wc-date-input .modus-wc-input-label{padding-bottom:var(--modus-wc-spacing-sm)}modus-wc-date .modus-wc-date-input--readonly{background-color:var(--modus-wc-color-base-200)}modus-wc-date{--calendar-grid-columns:repeat(7, 1fr);--calendar-grid-columns-with-week-numbers:auto repeat(7, 1fr);position:relative}modus-wc-date .date-input-container{align-items:center;display:inline-flex;position:relative;width:100%}modus-wc-date .date-input-container .calendar-icon-button{align-items:center;display:flex;justify-content:center;position:absolute;right:var(--modus-wc-spacing-xs)}modus-wc-date .date-input-container .calendar-icon-button :hover{color:var(--modus-wc-color-blue-light)}modus-wc-date .date-input-container .calendar-icon-button :disabled{background-color:transparent;cursor:not-allowed}modus-wc-date .calendar-container{background:var(--modus-wc-color-white);border:1px solid var(--modus-wc-color-gray-4);border-radius:var(--modus-wc-border-radius-md);box-shadow:0 4px 12px rgba(0, 0, 0, 0.15);height:327px;overflow:hidden;position:fixed;width:272px;z-index:9999}modus-wc-date .calendar-container.has-week-numbers{width:320px}modus-wc-date .calendar-container .calendar-header{align-items:center;background-color:var(--modus-wc-color-primary);color:var(--modus-wc-color-white);display:flex;gap:var(--modus-wc-spacing-sm);justify-content:center;padding:var(--modus-wc-spacing-md) var(--modus-wc-spacing-xl)}modus-wc-date .calendar-container .calendar-header .nav-btn i{color:var(--modus-wc-color-white)}modus-wc-date .calendar-container .calendar-header .nav-btn:hover i{color:var(--modus-wc-color-blue-dark)}modus-wc-date .calendar-container .calendar-header .calendar-selects{align-items:center;display:flex;gap:var(--modus-wc-spacing-xs)}modus-wc-date .calendar-container .calendar-header .calendar-selects select{box-shadow:0 1px 2px 0 rgba(0, 0, 0, 0.05)}modus-wc-date .calendar-container .calendar-header .calendar-selects select option{background-color:var(--modus-wc-color-white);color:var(--modus-wc-color-gray-9)}modus-wc-date .calendar-container .calendar-body{background-color:var(--modus-wc-color-base-page);font-size:var(--modus-wc-font-size-md);height:272px;padding:var(--modus-wc-spacing-xl)}modus-wc-date .calendar-container .calendar-body .calendar-days-week{display:grid;gap:var(--modus-wc-spacing-xs);grid-template-columns:var(--calendar-grid-columns);margin-bottom:var(--modus-wc-spacing-sm)}modus-wc-date .calendar-container .calendar-body .calendar-days-week.has-week-numbers{grid-template-columns:var(--calendar-grid-columns-with-week-numbers)}modus-wc-date .calendar-container .calendar-body .calendar-days-week .week-number-header{margin-inline-end:var(--modus-wc-spacing-md);padding:var(--modus-wc-spacing-xs)}modus-wc-date .calendar-container .calendar-body .calendar-days-week .day-header{color:var(--modus-wc-color-base-content-hight-contrast);font-weight:var(--modus-wc-font-weight-bold);padding:var(--modus-wc-spacing-xs);text-align:center}modus-wc-date .calendar-container .calendar-body .calendar-dates{display:grid;grid-template-columns:var(--calendar-grid-columns)}modus-wc-date .calendar-container .calendar-body .calendar-dates.has-week-numbers{grid-template-columns:var(--calendar-grid-columns-with-week-numbers)}modus-wc-date .calendar-container .calendar-body .calendar-dates .week-number{align-items:center;border-inline-end:1px solid color-mix(in sRGB, var(--modus-wc-color-base-inverted) 10%, transparent);display:flex;font-size:var(--modus-wc-font-size-sm);font-weight:var(--modus-wc-font-weight-bold);justify-content:center;padding-inline-end:var(--modus-wc-spacing-sm)}modus-wc-date .calendar-container .calendar-body .calendar-dates .calendar-day{align-items:center;background:none;border:none;border-radius:50%;color:var(--modus-wc-color-base-content-hight-contrast);cursor:pointer;display:flex;height:32px;justify-content:center;width:32px}modus-wc-date .calendar-container .calendar-body .calendar-dates .calendar-day:hover{background-color:var(--modus-wc-color-blue-light);color:var(--modus-wc-color-white)}modus-wc-date .calendar-container .calendar-body .calendar-dates .calendar-day:focus{background-color:var(--modus-wc-color-blue-light);color:var(--modus-wc-color-white);outline:none}modus-wc-date .calendar-container .calendar-body .calendar-dates .calendar-day.current-day{border:2px solid var(--modus-wc-color-blue-light);outline:none}modus-wc-date .calendar-container .calendar-body .calendar-dates .calendar-day.selected{background-color:var(--modus-wc-color-blue-light);color:var(--modus-wc-color-white);font-weight:var(--modus-wc-font-weight-semibold)}modus-wc-date .calendar-container .calendar-body .calendar-dates .calendar-day.other-month{color:var(--modus-wc-color-base-content-high-contrast);opacity:0.5}modus-wc-date .calendar-container .calendar-body .calendar-dates .calendar-day.other-month:hover{background-color:var(--modus-wc-color-gray-2);color:var(--modus-wc-color-gray-6)}modus-wc-date .calendar-container .calendar-body .calendar-dates .calendar-day.disabled{color:var(--modus-wc-color-gray-4)}modus-wc-date .calendar-container .calendar-body .calendar-dates .calendar-day.disabled:hover{background-color:transparent;color:var(--modus-wc-color-gray-4)}modus-wc-date .calendar-container .calendar-body .calendar-dates .calendar-day.disabled:focus{border:none}[data-theme=modus-classic-light] modus-wc-date .modus-wc-date-input.modus-wc-input,[data-theme=modus-classic-dark] modus-wc-date .modus-wc-date-input.modus-wc-input{border-radius:var(--modus-wc-border-radius-md)}[data-theme=modus-classic-light] modus-wc-date .modus-wc-date-input.modus-wc-input.modus-wc-input-sm,[data-theme=modus-classic-dark] modus-wc-date .modus-wc-date-input.modus-wc-input.modus-wc-input-sm{font-size:var(--modus-wc-font-size-sm);height:var(--modus-wc-input-height-sm);padding:var(--modus-wc-spacing-sm) var(--modus-wc-spacing-xs)}[data-theme=modus-classic-light] modus-wc-date .modus-wc-date-input.modus-wc-input.modus-wc-input-md,[data-theme=modus-classic-dark] modus-wc-date .modus-wc-date-input.modus-wc-input.modus-wc-input-md{font-size:var(--modus-wc-font-size-md);height:var(--modus-wc-input-height-md);padding:var(--modus-wc-spacing-sm)}[data-theme=modus-classic-light] modus-wc-date .modus-wc-date-input.modus-wc-input.modus-wc-input-lg,[data-theme=modus-classic-dark] modus-wc-date .modus-wc-date-input.modus-wc-input.modus-wc-input-lg{font-size:var(--modus-wc-font-size-lg);height:var(--modus-wc-input-height-lg);padding:var(--modus-wc-spacing-md) var(--modus-wc-spacing-sm)}[data-theme=modus-classic-light] modus-wc-date .modus-wc-date-input.modus-wc-input:focus,[data-theme=modus-classic-dark] modus-wc-date .modus-wc-date-input.modus-wc-input:focus{border-color:var(--modus-wc-color-blue-light);border-width:var(--modus-wc-border-width-sm)}[data-theme=modus-classic-light] modus-wc-date .modus-wc-date-input.modus-wc-input.modus-wc-date-input--readonly,[data-theme=modus-classic-dark] modus-wc-date .modus-wc-date-input.modus-wc-input.modus-wc-date-input--readonly{background-color:var(--modus-wc-color-base-100)}[data-theme=modus-classic-light] modus-wc-date .modus-wc-date-input.modus-wc-input.modus-wc-input-bordered:not(:disabled):not(:focus){border-color:var(--modus-wc-color-gray-6)}[data-theme=modus-classic-dark] modus-wc-date .modus-wc-date-input{color-scheme:dark}[data-theme=modus-classic-dark] modus-wc-date .modus-wc-date-input.modus-wc-input:focus{border-color:var(--modus-wc-color-highlight-blue)}[data-theme=connect-light] modus-wc-date .modus-wc-date-input,[data-theme=connect-dark] modus-wc-date .modus-wc-date-input{border-bottom-width:var(--input-bottom-border-width);outline-width:0 !important}[data-theme=connect-light] modus-wc-date .modus-wc-date-input:not(.modus-wc-select,.modus-wc-number-input),[data-theme=connect-dark] modus-wc-date .modus-wc-date-input:not(.modus-wc-select,.modus-wc-number-input){padding:0 var(--modus-wc-spacing-sm)}[data-theme=connect-light] modus-wc-date .modus-wc-date-input:hover,[data-theme=connect-dark] modus-wc-date .modus-wc-date-input:hover{border-bottom-color:var(--modus-wc-color-primary)}[data-theme=connect-light] modus-wc-date .modus-wc-date-input:active,[data-theme=connect-dark] modus-wc-date .modus-wc-date-input:active{border-bottom-color:var(--modus-wc-color-primary)}[data-theme=connect-light] modus-wc-date .modus-wc-date-input:focus,[data-theme=connect-dark] modus-wc-date .modus-wc-date-input:focus{border-bottom-color:var(--modus-wc-color-primary);outline:none}[data-theme=connect-light] modus-wc-date .modus-wc-date-input:focus-within,[data-theme=connect-dark] modus-wc-date .modus-wc-date-input:focus-within{border-bottom-color:var(--modus-wc-color-primary);outline:none}[data-theme=connect-dark] modus-wc-date .calendar-container .calendar-header .nav-btn i,[data-theme=modus-modern-dark] modus-wc-date .calendar-container .calendar-header .nav-btn i,[data-theme=modus-classic-dark] modus-wc-date .calendar-container .calendar-header .nav-btn i{color:var(--modus-wc-color-black)}[data-theme=connect-dark] modus-wc-date .calendar-container .calendar-header .calendar-selects select,[data-theme=modus-modern-dark] modus-wc-date .calendar-container .calendar-header .calendar-selects select,[data-theme=modus-classic-dark] modus-wc-date .calendar-container .calendar-header .calendar-selects select{background-color:unset;color:var(--modus-wc-color-black);font-weight:600}[data-theme=connect-dark] modus-wc-date .calendar-container .calendar-header .calendar-selects select option,[data-theme=modus-modern-dark] modus-wc-date .calendar-container .calendar-header .calendar-selects select option,[data-theme=modus-classic-dark] modus-wc-date .calendar-container .calendar-header .calendar-selects select option{background-color:var(--modus-wc-color-black);color:var(--modus-wc-color-white)}";
const MONTH_SHORT_NAMES = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
const WEEK_START_DAY_MAP = {
sunday: 0,
monday: 1,
tuesday: 2,
wednesday: 3,
thursday: 4,
friday: 5,
saturday: 6,
};
const ModusWcDate$1 = /*@__PURE__*/ proxyCustomElement(class ModusWcDate extends H {
constructor() {
super();
this.__registerHost();
this.inputBlur = createEvent(this, "inputBlur");
this.inputChange = createEvent(this, "inputChange");
this.inputFocus = createEvent(this, "inputFocus");
this.calendarMonthChange = createEvent(this, "calendarMonthChange");
this.calendarYearChange = createEvent(this, "calendarYearChange");
this.inheritedAttributes = {};
this.popperInstance = null;
/** Show the calendar dropdown */
this.showCalendar = false;
/** Calendar state object */
this.calendar = new DatePickerCalendar();
/** Currently focused date index in calendar */
this.focusedDateIndex = -1;
/** Tracks whether the component currently has focus */
this.hasFocus = false;
/** Indicates that the input should have a border. */
this.bordered = true;
/** Custom CSS class to apply to the input. */
this.customClass = '';
/** Whether the form control is disabled. */
this.disabled = false;
/** Whether the value is editable. */
this.readOnly = false;
/** A value is required or must be checked for the form to be submittable. */
this.required = false;
/** The size of the input. */
this.size = 'md';
/** The date format for display and input. */
this.format = 'dd-mm-yyyy';
/** The value of the control. */
this.value = '';
/** The first day of the week for the calendar display */
this.weekStartDay = 'sunday';
/** Displays ISO 8601 week numbers in the calendar.Week numbers are calculated with Monday as the first day of the week.*/
this.showWeekNumbers = false;
this.handleBlur = (event) => {
// Check if focus is moving to an element within the component
const relatedTarget = event.relatedTarget;
// istanbul ignore next (unreachable code)
if (relatedTarget && this.el.contains(relatedTarget)) {
// Focus is moving within the component, don't emit blur
return;
}
// Focus is leaving the component
this.hasFocus = false;
this.syncValueFromInput();
this.inputBlur.emit(event);
};
this.handleFocus = (event) => {
// Only emit focus if component didn't already have focus
if (!this.hasFocus) {
this.hasFocus = true;
this.inputFocus.emit(event);
}
};
this.handleInput = (event) => {
this.inputChange.emit(event);
};
this.handleInputKeyDown = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.syncValueFromInput();
}
};
this.setupPopper = () => {
if (this.popperInstance) {
this.popperInstance.destroy();
}
this.popperInstance = createPopper(this.inputRef, this.calendarRef, {
placement: 'bottom-start',
strategy: 'fixed',
modifiers: [
{
name: 'offset',
options: {
offset: [0, 8],
},
},
{
name: 'flip',
options: {
fallbackPlacements: ['top-start', 'bottom-end', 'top-end'],
},
},
],
});
};
this.toggleCalendar = () => {
this.showCalendar = !this.showCalendar;
// If opening the calendar and there's a selected date, navigate to it
if (this.showCalendar) {
const selectedDate = this.parseISODate(this.value);
this.ensureCalendarWithinBounds(selectedDate);
// Set focus to the selected date if it exists
if (selectedDate) {
const selectedIndex = this.calendar.dates.findIndex((date) => date && this.compareDate(date, selectedDate) === 0);
if (selectedIndex !== -1) {
this.focusedDateIndex = selectedIndex;
}
}
// set focus to today
else {
this.ensureCalendarWithinBounds(new Date());
this.focusedDateIndex = this.calendar.dates.findIndex((date) => date && this.compareDate(date, new Date()) === 0);
}
}
else {
// Reset focus when closing
this.focusedDateIndex = -1;
}
// Always ensure input is focused when toggling calendar (opening or closing)
if (this.inputRef) {
this.inputRef.focus();
}
};
this.handleDateSelect = (date) => {
if (this.isDateDisabled(date)) {
return;
}
this.value = this.formatISODate(date);
// If the selected date is from a different month, navigate to that month
// istanbul ignore next (unreachable code)
if (date.getMonth() !== this.calendar.selectedMonth ||
date.getFullYear() !== this.calendar.selectedYear) {
const firstDayOfWeek = WEEK_START_DAY_MAP[this.weekStartDay || 'sunday'];
const newCalendar = new DatePickerCalendar(firstDayOfWeek);
newCalendar.gotoDate(date.getFullYear(), date.getMonth());
this.calendar = newCalendar;
}
this.showCalendar = false;
this.hasFocus = false;
this.inputBlur.emit(new FocusEvent('blur', { bubbles: true }));
};
this.addMonthOffset = (offset) => {
const target = new Date(this.calendar.selectedYear, this.calendar.selectedMonth + offset, 1);
this.updateCalendarAndEmitEvents(target.getFullYear(), target.getMonth());
};
this.handleMonthChange = (event) => {
event.stopPropagation();
// Try to get the value from the original input event
const inputEvent = event.detail;
const selectTarget = inputEvent === null || inputEvent === void 0 ? void 0 : inputEvent.target;
const monthValue = selectTarget === null || selectTarget === void 0 ? void 0 : selectTarget.value;
const newMonth = parseInt(monthValue || '0', 10);
const currentYear = this.calendar.selectedYear;
if (Number.isNaN(newMonth)) {
return;
}
this.updateCalendarAndEmitEvents(currentYear, newMonth);
};
this.handleYearChange = (event) => {
event.stopPropagation();
// Try to get the value from the original input event
const inputEvent = event.detail;
const selectTarget = inputEvent === null || inputEvent === void 0 ? void 0 : inputEvent.target;
const yearValue = selectTarget === null || selectTarget === void 0 ? void 0 : selectTarget.value;
const newYear = parseInt(yearValue || '0', 10);
const currentMonth = this.calendar.selectedMonth;
if (Number.isNaN(newYear)) {
return;
}
this.updateCalendarAndEmitEvents(newYear, currentMonth);
};
this.handleDateKeyDown = (event, date) => {
if (this.isDateDisabled(date)) {
return;
}
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this.handleDateSelect(date);
}
};
}
handleMinChange(newValue) {
this.minDate = this.parseISODate(newValue);
if (this.maxDate && this.minDate && this.minDate > this.maxDate) {
this.maxDate = this.cloneDate(this.minDate);
}
this.ensureValueWithinBounds();
}
handleMaxChange(newValue) {
this.maxDate = this.parseISODate(newValue);
if (this.minDate && this.maxDate && this.maxDate < this.minDate) {
this.minDate = this.cloneDate(this.maxDate);
}
this.ensureValueWithinBounds();
}
handleValueChange(newValue) {
if (newValue === undefined) {
return;
}
if (!newValue) {
if (this.inputRef) {
this.inputRef.value = '';
}
return;
}
const parsed = this.parseISODate(newValue);
if (!parsed) {
if (this.value) {
this.value = '';
}
return;
}
const clamped = this.clampDate(parsed);
const formatted = this.formatISODate(clamped);
if (newValue !== formatted) {
this.value = formatted;
return;
}
if (this.inputRef) {
this.inputRef.value = formatted;
const event = new Event('input', { bubbles: true });
this.inputRef.dispatchEvent(event);
}
this.ensureCalendarWithinBounds(clamped);
}
handleWeekStartDayChange() {
// Reinitialize calendar with new first day of week
const firstDayOfWeek = WEEK_START_DAY_MAP[this.weekStartDay];
this.calendar = new DatePickerCalendar(firstDayOfWeek);
// Navigate to currently selected date if exists
const selectedDate = this.parseISODate(this.value);
if (selectedDate) {
this.calendar.gotoDate(selectedDate.getFullYear(), selectedDate.getMonth());
}
}
componentWillLoad() {
if (!this.el.ariaLabel) {
this.el.ariaLabel = 'Date input';
}
this.inheritedAttributes = inheritAriaAttributes(this.el);
// Initialize calendar with the correct first day of week
const firstDayOfWeek = WEEK_START_DAY_MAP[this.weekStartDay];
this.calendar = new DatePickerCalendar(firstDayOfWeek);
this.handleMinChange(this.min);
this.handleMaxChange(this.max);
this.handleValueChange(this.value);
}
componentDidUpdate() {
if (this.showCalendar && this.inputRef && this.calendarRef) {
this.setupPopper();
}
else if (this.popperInstance) {
this.popperInstance.destroy();
this.popperInstance = null;
}
}
disconnectedCallback() {
if (this.popperInstance) {
this.popperInstance.destroy();
this.popperInstance = null;
}
}
getClasses() {
const classList = [
'modus-wc-date-input',
'modus-wc-input',
'modus-wc-w-full',
];
const propClasses = convertPropsToClasses({
bordered: this.bordered,
feedback: this.feedback,
readOnly: this.readOnly,
size: this.size,
});
// The order CSS classes are added matters to CSS specificity
if (propClasses)
classList.push(propClasses);
if (this.customClass)
classList.push(this.customClass);
return classList.join(' ');
}
handleClickOutside(event) {
const path = event.composedPath();
const insideComponent = path.includes(this.el);
if (!insideComponent && this.showCalendar) {
this.showCalendar = false;
this.hasFocus = false;
}
}
handleEscapeKey(event) {
if (event.key === 'Escape' && this.showCalendar) {
this.showCalendar = false;
event.preventDefault();
}
}
navigateToAdjacentMonth(currentIndex, isUp) {
var _a, _b, _c;
const currentColumn = currentIndex % 7;
// Navigate to previous/next month
// Date constructor will normalize out-of-bounds months (e.g., -1 → Dec of prev year, 12 → Jan of next year)
this.updateCalendarAndEmitEvents(this.calendar.selectedYear, this.calendar.selectedMonth + (isUp ? -1 : 1));
// Find target date in same column
const weekRange = isUp ? [5, 4, 3, 2, 1, 0] : [0, 1, 2, 3, 4, 5];
for (const week of weekRange) {
const indexInWeek = week * 7 + currentColumn;
// istanbul ignore next (optional chaining)
if (indexInWeek < this.calendar.dates.length &&
((_a = this.calendar.dates[indexInWeek]) === null || _a === void 0 ? void 0 : _a.getMonth()) ===
this.calendar.selectedMonth) {
this.focusedDateIndex = indexInWeek;
return;
}
}
// Fallback to first/last current-month date
// istanbul ignore next (fallback scenario)
const currentMonthIndices = this.calendar.dates
.map((date, index) => (date === null || date === void 0 ? void 0 : date.getMonth()) === this.calendar.selectedMonth ? index : -1)
.filter((index) => index !== -1);
// istanbul ignore next (fallback scenario)
this.focusedDateIndex = isUp
? ((_b = currentMonthIndices[currentMonthIndices.length - 1]) !== null && _b !== void 0 ? _b : this.calendar.dates.length - 1)
: ((_c = currentMonthIndices[0]) !== null && _c !== void 0 ? _c : 0);
}
handleArrowKeys(event) {
var _a;
if (!this.showCalendar) {
return;
}
const key = event.key;
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key)) {
return;
}
event.preventDefault();
const totalDates = this.calendar.dates.length;
let newIndex = this.focusedDateIndex;
// If no date is focused, start with the first date or selected date
if (newIndex === -1) {
if (this.value) {
const selectedDate = this.parseISODate(this.value);
if (selectedDate) {
newIndex = this.calendar.dates.findIndex((date) => this.compareDate(date, selectedDate) === 0);
}
}
// istanbul ignore next (unreachable code)
if (newIndex === -1) {
newIndex = 0;
}
}
// Calculate target position for each arrow key
let targetIndex = newIndex;
let shouldChangeMonth = false;
let targetDate = null;
// Navigate based on arrow key
// istanbul ignore next (unreachable code)
switch (key) {
case 'ArrowLeft':
targetIndex = newIndex - 1;
break;
case 'ArrowRight':
targetIndex = newIndex + 1;
break;
case 'ArrowUp':
targetIndex = newIndex - 7;
break;
case 'ArrowDown':
targetIndex = newIndex + 7;
break;
}
// Check if target index is valid and get the target date
if (targetIndex >= 0 && targetIndex < totalDates) {
targetDate = this.calendar.dates[targetIndex];
if (targetDate) {
// Skip disabled dates - keep moving in the same direction until we find a valid date
let searchIndex = targetIndex;
const direction = key === 'ArrowLeft'
? -1
: key === 'ArrowRight'
? 1
: key === 'ArrowUp'
? -7
: 7;
while (searchIndex >= 0 &&
searchIndex < totalDates &&
this.isDateDisabled(this.calendar.dates[searchIndex])) {
searchIndex += direction;
}
// If we found a valid date within bounds
if (searchIndex >= 0 &&
searchIndex < totalDates &&
this.calendar.dates[searchIndex]) {
targetDate = this.calendar.dates[searchIndex];
targetIndex = searchIndex;
}
else {
// No valid date found in this direction, don't move
return;
}
// If target date is from a different month, navigate to that month
// istanbul ignore next (optional chaining)
if (targetDate.getMonth() !== this.calendar.selectedMonth) {
shouldChangeMonth = true;
}
this.focusedDateIndex = targetIndex;
}
}
else {
// Target is outside current calendar, navigate to appropriate month
shouldChangeMonth = true;
if (key === 'ArrowUp') {
// Check if we can navigate to previous month
const prevMonthDate = new Date(this.calendar.selectedYear, this.calendar.selectedMonth - 1, 1);
if (!this.isDateDisabled(prevMonthDate)) {
this.navigateToAdjacentMonth(newIndex, true);
shouldChangeMonth = false; // Already handled in helper
}
}
else if (key === 'ArrowDown') {
// Check if we can navigate to next month
const nextMonthDate = new Date(this.calendar.selectedYear, this.calendar.selectedMonth + 1, 1);
if (!this.isDateDisabled(nextMonthDate)) {
this.navigateToAdjacentMonth(newIndex, false);
shouldChangeMonth = false; // Already handled in helper
}
}
else if (key === 'ArrowLeft') {
// Go to previous month's last day
const prevMonthDate = new Date(this.calendar.selectedYear, this.calendar.selectedMonth - 1, 1);
targetDate = new Date(prevMonthDate.getFullYear(), prevMonthDate.getMonth() + 1, 0); // Last day of previous month
// Only navigate if not disabled
if (this.isDateDisabled(targetDate)) {
return;
}
}
else {
// Go to next month's first day
targetDate = new Date(this.calendar.selectedYear, this.calendar.selectedMonth + 1, 1); // First day of next month
// Only navigate if not disabled
if (this.isDateDisabled(targetDate)) {
return;
}
}
}
// Handle month change if needed
if (shouldChangeMonth && targetDate) {
this.updateCalendarAndEmitEvents(targetDate.getFullYear(), targetDate.getMonth());
// Find the target date in the new calendar
const newTargetIndex = this.calendar.dates.findIndex(
// istanbul ignore next (optional chaining)
(date) => date && this.compareDate(date, targetDate) === 0);
// istanbul ignore next (inequality check)
if (newTargetIndex !== -1) {
this.focusedDateIndex = newTargetIndex;
}
else {
// Fallback positioning
// istanbul ignore next (fallback scenario)
if (key === 'ArrowLeft' || key === 'ArrowUp') {
// Focus on last current-month date
// istanbul ignore next (fallback scenario)
const lastCurrentMonthIndex = this.calendar.dates
.map((date, index) => date && date.getMonth() === targetDate.getMonth() ? index : -1)
.filter((index) => index !== -1)
.pop();
// istanbul ignore next (fallback scenario)
this.focusedDateIndex =
lastCurrentMonthIndex !== undefined
? lastCurrentMonthIndex
: this.calendar.dates.length - 1;
}
else {
// Focus on first current-month date
// istanbul ignore next (fallback scenario)
const firstCurrentMonthIndex = this.calendar.dates.findIndex((date) => date && date.getMonth() === targetDate.getMonth());
// istanbul ignore next (fallback scenario)
this.focusedDateIndex =
firstCurrentMonthIndex !== -1 ? firstCurrentMonthIndex : 0;
}
}
}
// Focus the corresponding button
// istanbul ignore next (unreachable code)
const dateButtons = (_a = this.calendarRef) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.calendar-day');
if (dateButtons && dateButtons[this.focusedDateIndex]) {
// istanbul ignore next (unreachable code)
dateButtons[this.focusedDateIndex].focus();
}
}
renderCalendarHeader() {
const currentYear = this.calendar.selectedYear;
const currentMonth = this.calendar.selectedMonth;
// Generate year options (current year ± 100 years)
const yearOptions = [];
for (let i = currentYear - 100; i <= currentYear + 100; i++) {
yearOptions.push({ value: i.toString(), label: i.toString() });
}
// Generate month options
const monthOptions = MONTH_SHORT_NAMES.map((month, index) => ({
value: index.toString(),
label: month,
}));
return (h("div", { class: "calendar-header" }, h("modus-wc-button", { type: "button", "aria-label": "Previous", variant: "borderless", shape: "circle", size: "xs", onButtonClick:
// istanbul ignore next (unreachable code)
() => this.addMonthOffset(-1), class: "nav-btn" }, h("modus-wc-icon", { name: "chevron_left", size: "sm" })), h("div", { class: "calendar-selects" }, h("modus-wc-select", { key: `month-${currentYear}-${currentMonth}`, class: "month-select", value: currentMonth.toString(), options: monthOptions, onInputChange:
// istanbul ignore next (unreachable code)
(e) => this.handleMonthChange(e), onInputBlur:
// istanbul ignore next (unreachable code)
(e) => e.stopPropagation(), bordered: false, size: "sm" }), h("modus-wc-select", { key: `year-${currentYear}`, class: "year-select", value: currentYear.toString(), options: yearOptions, onInputChange:
// istanbul ignore next (unreachable code)
(e) => this.handleYearChange(e), onInputBlur:
// istanbul ignore next (unreachable code)
(e) => e.stopPropagation(), bordered: false, size: "sm" })), h("modus-wc-button", { type: "button", "aria-label": "Next", variant: "borderless", shape: "circle", size: "xs", onButtonClick:
// istanbul ignore next (unreachable code)
() => this.addMonthOffset(1), class: "nav-btn" }, h("modus-wc-icon", { name: "chevron_right", size: "sm" }))));
}
renderCalendarBody() {
const today = new Date();
const selectedDate = this.parseISODate(this.value);
const currentMonth = this.calendar.selectedMonth;
return (h("div", { class: "calendar-body" }, h("div", { class: `calendar-days-week${this.showWeekNumbers ? ' has-week-numbers' : ''}` }, this.showWeekNumbers && h("div", { class: "week-number-header" }), this.calendar
.getDaysOfWeek('default', WEEK_START_DAY_MAP[this.weekStartDay])
.map((d) => {
return h("div", { class: "day-header" }, d);
})), h("div", { class: `calendar-dates${this.showWeekNumbers ? ' has-week-numbers' : ''}` }, this.calendar.dates.map((date, index) => {
// Add week number at the start of each row (every 7 days)
const weekNumberElement = this.showWeekNumbers && index % 7 === 0 ? (h("div", { class: "week-number", "aria-label": `Week ${this.calendar.getWeekNumber(date, WEEK_START_DAY_MAP[this.weekStartDay])}` }, this.calendar.getWeekNumber(date, WEEK_START_DAY_MAP[this.weekStartDay]))) : null;
if (!date) {
return weekNumberElement;
}
const isToday = this.compareDate(date, today) === 0;
const isSelected = (selectedDate && this.compareDate(date, selectedDate) === 0) ||
false;
const isCurrentMonth = date.getMonth() === currentMonth;
const isDisabled = this.isDateDisabled(date);
const button = (h("button", { type: "button", class: {
'calendar-day': true,
'current-day': isToday,
selected: isSelected,
'current-month': isCurrentMonth,
'other-month': !isCurrentMonth,
disabled: isDisabled,
}, disabled: isDisabled, onClick: () => this.handleDateSelect(date), onKeyDown: (e) => this.handleDateKeyDown(e, date), tabIndex: isDisabled ? -1 : 0 }, date.getDate()));
// Only create array when week number exists
return weekNumberElement ? [weekNumberElement, button] : button;
}))));
}
compareDate(date1, date2) {
if (!date1 && !date2) {
return 0;
}
else if (!date1 && date2) {
return -1;
}
else if (date1 && !date2) {
return 1;
}
let delta;
delta = date1.getFullYear() - date2.getFullYear();
if (delta !== 0) {
return delta;
}
delta = date1.getMonth() - date2.getMonth();
if (delta !== 0) {
return delta;
}
return date1.getDate() - date2.getDate();
}
parseISODate(value) {
var _a;
if (!value) {
return undefined;
}
let yearStr, monthStr, dayStr;
if (this.format === 'MMM DD, YYYY') {
// Parse "Jan 01, 2025" format
const match = value.match(/^([A-Za-z]{3})\s+(\d{1,2}),\s+(\d{4})$/);
if (!match) {
return undefined;
}
const [, monthName, day, year] = match;
// Case-insensitive month name lookup
monthStr = MONTH_SHORT_NAMES.findIndex((m) => m.toLowerCase() === monthName.toLowerCase());
if (monthStr === -1) {
return undefined;
}
dayStr = day;
yearStr = year;
}
else {
// istanbul ignore next (unreachable code)
const separator = ((_a = this.format) === null || _a === void 0 ? void 0 : _a.includes('/')) ? '/' : '-';
const parts = value.split(separator);
if (parts.length !== 3) {
return undefined;
}
if (this.format === 'dd-mm-yyyy' || this.format === 'dd/mm/yyyy') {
[dayStr, monthStr, yearStr] = parts;
}
else {
// yyyy-mm-dd or yyyy/mm/dd
[yearStr, monthStr, dayStr] = parts;
}
}
// istanbul ignore next (unreachable code)
if (yearStr == null || monthStr == null || dayStr == null) {
return undefined;
}
const year = Number(yearStr);
const month = typeof monthStr === 'number' ? monthStr : Number(monthStr) - 1;
const day = Number(dayStr);
if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day)) {
return undefined;
}
const date = new Date(year, month, day);
if (date.getFullYear() !== year ||
date.getMonth() !== month ||
date.getDate() !== day) {
return undefined;
}
return this.cloneDate(date);
}
formatISODate(date) {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
switch (this.format) {
case 'dd-mm-yyyy':
return `${day}-${month}-${year}`;
case 'dd/mm/yyyy':
return `${day}/${month}/${year}`;
case 'yyyy/mm/dd':
return `${year}/${month}/${day}`;
case 'MMM DD, YYYY': {
const monthName = MONTH_SHORT_NAMES[date.getMonth()];
return `${monthName} ${day}, ${year}`;
}
default:
// yyyy-mm-dd
return `${year}-${month}-${day}`;
}
}
cloneDate(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}
clampDate(date) {
let result = this.cloneDate(date);
if (this.minDate && result < this.minDate) {
result = this.cloneDate(this.minDate);
}
if (this.maxDate && result > this.maxDate) {
result = this.cloneDate(this.maxDate);
}
return result;
}
isDateDisabled(date) {
if (this.minDate && date < this.minDate) {
return true;
}
if (this.maxDate && date > this.maxDate) {
return true;
}
return false;
}
ensureValueWithinBounds() {
if (!this.value) {
return;
}
const parsed = this.parseISODate(this.value);
if (!parsed) {
this.value = '';
return;
}
const clamped = this.clampDate(parsed);
const formatted = this.formatISODate(clamped);
if (formatted !== this.value) {
this.value = formatted;
}
}
ensureCalendarWithinBounds(referenceDate) {
// Allow viewing any month, just disable dates outside min/max
if (referenceDate) {
const firstDayOfWeek = WEEK_START_DAY_MAP[this.weekStartDay];
const newCalendar = new DatePickerCalendar(firstDayOfWeek);
newCalendar.gotoDate(referenceDate.getFullYear(), referenceDate.getMonth());
this.calendar = newCalendar;
}
}
setCalendarMonth(year, month) {
const firstDayOfWeek = WEEK_START_DAY_MAP[this.weekStartDay];
const newCalendar = new DatePickerCalendar(firstDayOfWeek);
newCalendar.gotoDate(year, month);
this.calendar = newCalendar;
}
updateCalendarAndEmitEvents(year, month) {
const oldYear = this.calendar.selectedYear;
const oldMonth = this.calendar.selectedMonth;
this.setCalendarMonth(year, month);
// Emit events only if the values actually changed
if (month !== oldMonth) {
this.calendarMonthChange.emit(month);
}
if (year !== oldYear) {
this.calendarYearChange.emit(year);
}
}
syncValueFromInput() {
if (!this.inputRef) {
return;
}
const value = this.inputRef.value.trim();
if (!value) {
if (this.value) {
this.value = '';
}
return;
}
const parsed = this.parseISODate(value);
if (!parsed) {
this.inputRef.value = this.value || '';
return;
}
const clamped = this.clampDate(parsed);
const formatted = this.formatISODate(clamped);
this.value = formatted;
this.inputRef.value = formatted;
}
render() {
return (h(Host, { key: '6f000e9dc71d97f911585d99bff1a5a3055761e3' }, this.label && (h("modus-wc-input-label", { key: 'f687de871654f64a79ae022de727432ff2704f19', forId: this.inputId, labelText: this.label, required: this.required, size: this.size })), h("div", { key: '367647149b48874be5a78bd9f73bf36a1a89acf4', class: "date-input-container" }, h("input", Object.assign({ key: 'fa3bf4d35eb4c1c53117af2e12a3269c390b40d4', ref: (el) => (this.inputRef = el), "aria-disabled": this.disabled, class: this.getClasses(), disabled: this.disabled, id: this.inputId, name: this.name, onBlur: this.handleBlur, onFocus: this.handleFocus, onInput: this.handleInput, onKeyDown: this.handleInputKeyDown, placeholder: this.format, readonly: this.readOnly, required: this.required, tabIndex: this.inputTabIndex, type: "text", value: this.value }, this.inheritedAttributes)), h("modus-wc-button", { key: 'dbf3ae7cb1b6b1178f664b973e518be8d948d52d', "aria-label": "Open calendar", disabled: this.disabled || this.readOnly, variant: "borderless", shape: "circle", size: "xs", color: "tertiary", class: "calendar-icon-button", onButtonClick:
// istanbul ignore next (unreachable code)
() => this.toggleCalendar() }, h("modus-wc-icon", { key: 'ebac173bb763508b40fd1c84be9878d9299713e2', name: "calendar_blank", size: "sm" }))), this.showCalendar && (h("div", { key: '31cfd0de7895cba19d32e3babe2a39031a9373ed', ref: (el) => (this.calendarRef = el), class: `calendar-container${this.showWeekNumbers ? ' has-week-numbers' : ''}` }, this.renderCalendarHeader(), this.renderCalendarBody())), this.feedback && (h("modus-wc-input-feedback", { key: 'c5dcb5f36dd5aa35ed7f93b528287b3fe8421a10', level: this.feedback.level, message: this.feedback.message, size: this.size }))));
}
get el() { return this; }
static get watchers() { return {
"min": ["handleMinChange"],
"max": ["handleMaxChange"],
"value": ["handleValueChange"],
"weekStartDay": ["handleWeekStartDayChange"]