Event system and beforeDateSelectCallback for validation and control of date selections
the wrapper forwards these to LiveView as web_daterangepicker:change — see the floating panel
The date picker fires two custom events when a date or range is selected:
date-select - Primary event for date selectionchange - Alias event (fires simultaneously with date-select)Note: Both events bubble and are composed (cross Shadow DOM boundaries).
// Single mode
{ formattedValue: "2025-11-17", date: Date object }
// Range mode
{
formattedValue: "2025-11-17 - 2025-11-24",
dateRange: { start: Date, end: Date },
enabledDates: [Date, ...], // 'allow' mode
disabledDates: [Date, ...], // 'allow' mode
dateRanges: [DateRange, ...], // 'split' mode
dates: [Date, ...], // 'split' or 'individual' mode
getEnabledDateCount: () => number,
getTotalDays: () => number
}
const picker = document.getElementById('my-picker');
picker.addEventListener('date-select', (event) => {
console.log('Date selected:', event.detail);
if (event.detail.date) {
console.log('Selected date:', event.detail.date); // single
} else if (event.detail.dateRange) {
console.log('Selected range:', event.detail.dateRange); // range
}
});
The beforeDateSelectCallback callback is invoked BEFORE a selection is finalized. It allows you to validate, block, adjust, or clear selections based on your business logic.
| Action | Description | Use Case |
|---|---|---|
'accept' | Allow the selection as-is | Validation passed |
'adjust' | Modify the selected date(s) | Round to business days, snap to availability |
'restore' | Reject and revert to previous selection | Validation failed, keep previous state |
'clear' | Clear the selection entirely | Hard reset on validation failure |
// Block weekends
picker.beforeDateSelectCallback = ({ date }) => {
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
return isWeekend
? { action: 'restore', message: 'Weekends not allowed' }
: { action: 'accept' };
};
// Adjust to next business day
picker.beforeDateSelectCallback = ({ date }) => {
const day = date.getDay();
if (day === 0) { // Sunday → Monday
const adjusted = new Date(date); adjusted.setDate(date.getDate() + 1);
return { action: 'adjust', adjustedDate: adjusted, message: 'Adjusted to Monday' };
}
return { action: 'accept' };
};
// Async API validation
picker.beforeDateSelectCallback = async ({ date }) => {
const { available } = await (await fetch('/api/check-date', {
method: 'POST', body: JSON.stringify({ date: date.toISOString() })
})).json();
return available ? { action: 'accept' } : { action: 'restore', message: 'Date not available' };
};
showInvalidRange: true to keep invalid range visible with error styling.
showMessage(html) for full control.
// Minimum nights requirement
picker.beforeDateSelectCallback = ({ range }) => {
const nights = Math.floor((range.end - range.start) / (1000 * 60 * 60 * 24));
return nights < 2
? { action: 'restore', message: 'Minimum 2 nights required' }
: { action: 'accept' };
};
// Maximum nights with showInvalidRange
picker.beforeDateSelectCallback = ({ range }) => {
const nights = Math.floor((range.end - range.start) / (1000 * 60 * 60 * 24));
if (nights > 7) {
picker.showMessage('Maximum 7 nights allowed', 'error');
return { action: 'restore', showInvalidRange: true };
}
return { action: 'accept' };
};
// Handle custom-action from showMessage() buttons
picker.addEventListener('custom-action', (e) => {
const { startDate, endDate } = e.detail.data; // v2.0.0: data-* live under detail.data
if (startDate && endDate) {
picker.selectedRanges = [{ start: new Date(startDate), end: new Date(endDate) }];
picker.hideMessage();
}
});
The beforeMonthChangedCallback is called BEFORE month navigation occurs. Use it to bulk-load metadata for all visible dates in ONE API call instead of calling getDateMetadataCallback 35-42 times per month.
Customize the month header text (e.g., "January 2026") to display additional information like room availability, occupancy rates, or any business-specific data.
getMonthHeaderCallback - Callback to generate header on-the-flymonthHeaders in beforeMonthChangedCallback result - Return headers with async-loaded dataPriority: monthHeaders > getMonthHeaderCallback > default format
// Approach 1: getMonthHeaderCallback
picker.getMonthHeaderCallback = ({ month, monthName, year }) => {
const rooms = roomAvailability[`${year}-${month.getMonth()}`] || 0;
return `${monthName} ${year} (${rooms} rooms)`;
};
// Approach 2: monthHeaders from beforeMonthChangedCallback
picker.beforeMonthChangedCallback = async ({ year, month }) => {
const data = await fetchAvailability(year, month);
const monthHeaders = new Map();
const monthKey = `${year}-${String(month).padStart(2, '0')}`;
monthHeaders.set(monthKey, `${monthName} ${year} (${data.totalRooms} rooms)`);
return { action: 'accept', metadata: data.dateMetadata, monthHeaders };
};
(pick a date anywhere on this page)