soma/range-calendar: fix endpoint clear + stop auto-paging on select

Closes 2 bugs from the date-range-picker incidencia 2026-05-20:

#1 — `RangeCalendarProvider` no longer auto-shifts the placeholder when
a selection lands in the last visible month. The visible months stay
where the user put them; navigation is explicit (prev/next/month/year
controls or keyboard). `reanchorInitialSelection` is removed and its
`shift-navigate` trigger goes with it.

#2 — clicking an endpoint of a completed range now drops only that
endpoint and re-anchors on the surviving one. The previous behavior
cleared both endpoints, which forced users to rebuild the entire range
to amend it. The provider already implemented this; only the test
codified the old behavior. Test rewritten to match the documented
intent and symmetric for start/end.

The two range-calendar tests that previously asserted the wrong
behavior now cover the correct invariants.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent 2b3f3e093e
commit aaffba2ff5

@ -191,7 +191,11 @@ describe('RangeCalendarProvider', () => {
dom.dispose(); dom.dispose();
}); });
it('clears a completed range when an endpoint is clicked again', () => { it('clears only the clicked endpoint of a completed range, keeping the other', () => {
// Incidencia 2026-05-20 #2 — clicking the end of a complete range must
// drop just that endpoint and re-anchor on the surviving start (the
// user is amending the range, not discarding it). The symmetric click
// on the start must drop only the start and keep the end.
const { dom } = installSomaHarness(); const { dom } = installSomaHarness();
const opts = createRangeCalendarOpts(); const opts = createRangeCalendarOpts();
const start = new CalendarDate(2026, 5, 10); const start = new CalendarDate(2026, 5, 10);
@ -205,9 +209,17 @@ describe('RangeCalendarProvider', () => {
provider.select(end); provider.select(end);
expect(opts.value.current).toEqual({ start, end }); expect(opts.value.current).toEqual({ start, end });
// Click end → end drops, start stays, anchor returns to start.
provider.select(end); provider.select(end);
expect(opts.value.current).toEqual({ start: undefined, end: undefined }); expect(opts.value.current).toEqual({ start, end: undefined });
expect(provider.anchor).toBeUndefined(); expect(provider.anchor).toEqual(start);
// Re-close the range, then click start → start drops, end stays.
provider.select(end);
expect(opts.value.current).toEqual({ start, end });
provider.select(start);
expect(opts.value.current).toEqual({ start: undefined, end });
expect(provider.anchor).toEqual(end);
cleanup(); cleanup();
dom.dispose(); dom.dispose();
@ -239,7 +251,11 @@ describe('RangeCalendarProvider', () => {
dom.dispose(); dom.dispose();
}); });
it('advances only the initial selection made in the last visible month', () => { it('never auto-pages the placeholder on selection (incidencia 2026-05-20 #1)', () => {
// Selection must not re-anchor the visible months. The user controls
// what's on screen via prev/next/month/year — clicking a date in any
// position (including the last visible month) keeps the popup where
// they put it.
const { dom } = installSomaHarness(); const { dom } = installSomaHarness();
const opts = createRangeCalendarOpts(); const opts = createRangeCalendarOpts();
opts.numberOfMonths.current = 2; opts.numberOfMonths.current = 2;
@ -256,12 +272,14 @@ describe('RangeCalendarProvider', () => {
expect(opts.value.current).toEqual({ start: mayStart, end: juneEnd }); expect(opts.value.current).toEqual({ start: mayStart, end: juneEnd });
expect(opts.placeholder.current.toString()).toBe('2026-05-01'); expect(opts.placeholder.current.toString()).toBe('2026-05-01');
// A new selection that lands in the last visible month (June) used to
// shift the popup to June+July. It must stay put.
provider.select(new CalendarDate(2026, 6, 20)); provider.select(new CalendarDate(2026, 6, 20));
expect(opts.value.current).toEqual({ expect(opts.value.current).toEqual({
start: new CalendarDate(2026, 6, 20), start: new CalendarDate(2026, 6, 20),
end: undefined end: undefined
}); });
expect(opts.placeholder.current.toString()).toBe('2026-06-01'); expect(opts.placeholder.current.toString()).toBe('2026-05-01');
cleanup(); cleanup();
dom.dispose(); dom.dispose();

@ -14,7 +14,6 @@ import {
type WeekStartsOn, type WeekStartsOn,
type DateOrder, type DateOrder,
isSameDay, isSameDay,
isSameMonth,
startOfMonth, startOfMonth,
endOfMonth, endOfMonth,
getLocalTimeZone, getLocalTimeZone,
@ -378,10 +377,13 @@ export class RangeCalendarProvider {
} }
private startSelection(date: DateValue, target?: HTMLElement): void { private startSelection(date: DateValue, target?: HTMLElement): void {
// Incidencia 2026-05-20 #1 — never auto-shift the placeholder on
// selection. The visible months stay where the user put them; if they
// need to see a different range they navigate explicitly via prev/next
// or month/year selects. Click-to-select must not page the popup.
this.anchor = date; this.anchor = date;
this.previewDate = undefined; this.previewDate = undefined;
this.opts.value.current = { start: date, end: undefined }; this.opts.value.current = { start: date, end: undefined };
this.reanchorInitialSelection(date);
void this.runtime.trigger('commit-start', target ? { fallbackTarget: target } : undefined); void this.runtime.trigger('commit-start', target ? { fallbackTarget: target } : undefined);
} }
@ -419,17 +421,6 @@ export class RangeCalendarProvider {
void this.runtime.trigger('commit-clear', target ? { fallbackTarget: target } : undefined); void this.runtime.trigger('commit-clear', target ? { fallbackTarget: target } : undefined);
} }
private reanchorInitialSelection(date: DateValue): void {
const count = Math.max(1, this.opts.numberOfMonths.current);
if (count < 2) return;
const visible = this.visibleMonths;
const last = visible[visible.length - 1];
if (!last || !isSameMonth(date, last)) return;
this.opts.placeholder.current = startOfMonth(date);
this.announceNavigation();
void this.runtime.trigger('shift-navigate');
}
private commit(a: DateValue, b: DateValue, target?: HTMLElement): void { private commit(a: DateValue, b: DateValue, target?: HTMLElement): void {
const [start, end] = orderPair(a, b); const [start, end] = orderPair(a, b);

Loading…
Cancel
Save

Powered by TurnKey Linux.