fix: clear date selection when clicking an unavailable/closed day

bits-ui blocks onValueChange for unavailable dates, leaving stale selection. Adds native click detection on [data-unavailable] elements to call onchange(undefined) so the time picker hides and the user knows nothing was picked.
This commit is contained in:
2026-08-22 00:34:49 +01:00
parent 1fb84b8998
commit 8384197ef0
7 changed files with 75 additions and 2 deletions
@@ -485,6 +485,21 @@
}
});
// Clear selection when navigating to a month that doesn't contain the selected date.
// Runs AFTER all synchronous state changes settle, so clicking a date in a different
// month (fires both onPlaceholderChange and onValueChange) keeps the new selection,
// while clicking prev/next arrows without picking a date clears it.
$effect(() => {
if (
newDate &&
placeholderDate &&
(newDate.month !== placeholderDate.month || newDate.year !== placeholderDate.year)
) {
newDate = undefined;
newTime = '';
}
});
// ─── API calls ──────────────────────────────────────────
async function fetchHoursRange(startDate: CalendarDate, months: number) {
loadingHours = true;
@@ -437,6 +437,21 @@
}
});
// Clear selection when navigating to a month that doesn't contain the selected date.
// Runs AFTER all synchronous state changes settle, so clicking a date in a different
// month (fires both onPlaceholderChange and onValueChange) keeps the new selection,
// while clicking prev/next arrows without picking a date clears it.
$effect(() => {
if (
selectedDate &&
placeholder &&
(selectedDate.month !== placeholder.month || selectedDate.year !== placeholder.year)
) {
selectedDate = undefined;
selectedTime = null;
}
});
// =============== Reset State ===============
function resetState() {
currentStep = 1;
@@ -21,9 +21,10 @@
open: boolean;
bookingId: string;
onReschedule?: () => void;
onChanged?: () => void;
}
let { open = $bindable(), bookingId, onReschedule }: Props = $props();
let { open = $bindable(), bookingId, onReschedule, onChanged }: Props = $props();
let selectedBooking = $state<Booking | null>(null);
let showApprovalModal = $state(false);
@@ -83,6 +84,7 @@
toast.success('Booking cancelled');
showCancelModal = false;
fetchBookingDetails();
onChanged?.();
} else {
const text = await response.text();
toast.error('Failed to cancel: ' + extractErrorMessage(text));
@@ -770,6 +772,7 @@
onApproved={() => {
showApprovalModal = false;
fetchBookingDetails();
onChanged?.();
}}
/>
{/if}
@@ -783,6 +786,7 @@
showRescheduleModal = false;
fetchBookingDetails();
onReschedule?.();
onChanged?.();
}}
/>
{/if}
@@ -409,6 +409,21 @@
}
}
});
// Clear selection when navigating to a month that doesn't contain the selected date.
// Runs AFTER all synchronous state changes settle, so clicking a date in a different
// month (fires both onPlaceholderChange and onValueChange) keeps the new selection,
// while clicking prev/next arrows without picking a date clears it.
$effect(() => {
if (
selectedDate &&
placeholder &&
(selectedDate.month !== placeholder.month || selectedDate.year !== placeholder.year)
) {
selectedDate = undefined;
selectedTime = null;
}
});
</script>
<Modal.Root bind:open>
@@ -678,6 +678,21 @@
}
});
// Clear selection when navigating to a month that doesn't contain the selected date.
// Runs AFTER all synchronous state changes settle, so clicking a date in a different
// month (fires both onPlaceholderChange and onValueChange) keeps the new selection,
// while clicking prev/next arrows without picking a date clears it.
$effect(() => {
if (
selectedDate &&
placeholder &&
(selectedDate.month !== placeholder.month || selectedDate.year !== placeholder.year)
) {
selectedDate = undefined;
selectedTime = null;
}
});
async function fetchHoursRange(startDate: CalendarDate, months: number) {
// Calculate end month manually (CalendarDate is immutable)
let endYear = startDate.year;
@@ -54,5 +54,14 @@
onPlaceholderChange(p as CalendarDate);
}
}}
// Detect clicks on unavailable dates (bits-ui blocks onValueChange for these,
// so the parent's selection stays stale). Clear the selection so the time
// picker hides and the user knows they didn't pick anything.
onclick={(e: MouseEvent) => {
const target = e.target as HTMLElement;
if (target.closest('[data-unavailable]')) {
onchange?.(undefined);
}
}}
/>
</div>
@@ -766,7 +766,7 @@
{/if}
</div>
<BookingModal bind:open={showBookingModal} bookingId={selectedBookingId ?? ''} />
<BookingModal bind:open={showBookingModal} bookingId={selectedBookingId ?? ''} onChanged={() => fetchWeekData()} />
{/if}
<style>