Forms · RangeCalendar

Range calendar

Calendar primitive for picking a date range. Soma owns the state machine (start → preview → end), the keyboard nav, the min/max constraints (minValue, maxValue, minDays, maxDays) and the hover-preview signaling. Eidos paints the cells with distinct data-range-start / data-range-end / data-in-range / data-in-preview-range treatments — the shared partial at range-calendar.css is consumed by both this standalone component and by DateRangePicker's popover content.

parts{compiled.parts.order.length} events{events.length} sizes4 apg grid ↗ range {rangeLabel}
{#snippet children({ months, weekdays }: RangeCalendarProviderSnippetProps)} ‹ ›
{#each months as month (month.value.toString())}
{#snippet children(_props: RangeCalendarGridRowSnippetProps)} {#each weekdays as wd, i (i)} {wd} {/each} {/snippet} {#each month.weeks as week (week[0].toString())} {#snippet children(_props: RangeCalendarGridRowSnippetProps)} {#each week as date (date.toString())} {/each} {/snippet} {/each}
{/each}
{/snippet}
trace {#if trace.length === 0} click a day to start; hover to preview; click second to commit {:else} {#each trace.slice(0, 3) as entry (entry.at)} {entry.event} · {entry.family}{entry.intent ? ' · ' + entry.intent : ''} {fmtTime(entry.at)} {/each} {/if} range {rangeLabel}
{#if tab === 'live'}

Controls

Soma owns the range state machine, the keyboard navigation, the min/max constraints and the hover-preview signaling. Eidos adds variant/size/color cascades.

soma range constraints
soma grid layout
eidos visual props
eidos composition · with bind:value svelte
{eidosSnippet}
{/if} {#if tab === 'api'}

API reference

14 parts: Provider, Header, Heading, PrevButton, NextButton, MonthSelect, YearSelect, Grid, GridHead, GridBody, GridRow, HeadCell, Cell, Day.

Provider (RangeCalendar)
PropTypeDefaultDescription
value bindableDateRange{`{}`}Committed range.
placeholder bindableDateValuetodayMonth currently visible.
onValueChange(r: DateRange) => void—Fires when end is committed.
minValue · maxValueDateValue—Allowed bounds.
minDays · maxDaysnumber—Inclusive length constraints (days).
allowSingleDaybooleantruePermit start === end.
preventDeselectbooleanfalseClicking the selected endpoint keeps the range.
numberOfMonthsnumber1Visible months.
pagedNavigationbooleanfalsePrev/Next advance by numberOfMonths.
fixedWeeksbooleanfalseAlways render 6 rows.
disableDaysOutsideMonthbooleanfalseSkip non-current-month cells from selection.
disabled · readonlybooleanfalseOR-merged with Field.Provider.
isDateDisabled · isDateUnavailable · isDateHolidayDateMatcher—Per-cell decorators.
weekStartsOn · weekdayFormat · locale · dir—localeLocalization.
size eidos'xs' | 'sm' | 'md' | 'lg''md'Day cells + nav buttons + selects.
variant eidos'surface' | 'outline' | 'ghost''surface'Calendar chrome.
color eidosColorRole'primary'End-endpoint + in-range fill + focus accent.
Sub-parts

The sub-parts (Header, Heading, PrevButton, NextButton, MonthSelect, YearSelect, Grid, GridHead, GridBody, GridRow, HeadCell, Cell, Day) are pass-throughs to soma — they accept the headless props from the soma `RangeCalendarXxxProps` types unchanged. See the morfo table below for the per-part data contract.

{/if} {#if tab === 'morfo'}

morfo · declarative contract

Source: src/uix/morfo/components/range-calendar.ts.

FieldValue
name"{rangeCalendarMorfo.name}"
kebab"{rangeCalendarMorfo.kebab}"
apg{rangeCalendarMorfo.apg}
scope{rangeCalendarMorfo.scope.join(', ')}
parts.length{rangeCalendarMorfo.parts.length}
events.length{events.length}
Parts
{#each partsList as part (part.kebab)} {/each}
PartMarkerElementRoleArchetypeStatesOptional
{part.kebab} [{part.marker}] <{part.defaultElement}> {part.role ?? '—'} {part.archetype ?? '—'} {part.states.length ? part.states.join(' | ') : '—'} {part.optional ? 'yes' : 'no'}
Events declaration
{#each events as action (action.name)} {@const sem = action.semantic} {@const intentDecl = 'intent' in sem ? sem.intent : undefined} {@const intentStr = typeof intentDecl === 'string' ? intentDecl : intentDecl ? `fromProp:${(intentDecl as { fromProp?: string }).fromProp ?? '?'}` : '—'} {/each}
namefamilyverbsequenceintenttarget
{action.name} {sem.family} {sem.verb ?? '—'} {sem.sequence ?? 'pre'} {intentStr} {action.target}
{/if} {#if tab === 'sema'}

sema · events

{#each events as action (action.name)} {@const intentDecl = 'intent' in action.semantic ? action.semantic.intent : undefined} {@const effectiveIntent = typeof intentDecl === 'string' ? intentDecl : undefined} {/each}
NameFamilyVerbSequenceIntentPlay
{action.name} {action.semantic.family} {action.semantic.verb ?? '—'} {action.semantic.sequence ?? 'pre'} {effectiveIntent ?? '—'}
{/if} {#if tab === 'recipe'}

Eidos recipe

Selectors at src/uix/eidos/components/range-calendar/range-calendar.css. This file is consumed BOTH by the standalone eidos <RangeCalendar> and by <DateRangePicker>'s popover content — the same [data-range-calendar-*] attrs land in both environments. The picker recipe only adds scoping overrides (no chrome, max-content sizing, transparent background).

SelectorSourceWhat it paints
[data-range-calendar]eidosRoot chrome — border + bg + padding + token scaffolding.
[data-range-calendar][data-size]eidosDay-cell + control-button size scale (xs/sm/md/lg).
[data-range-calendar][data-variant]eidosSurface / outline / ghost chrome.
[data-range-calendar][data-color]eidosEnd-endpoint + in-range + focus accent tokens.
[data-range-calendar-day][data-range-start]eidosStart endpoint: affirm-family solid + left-edge stripe + semibold.
[data-range-calendar-day][data-range-end]eidosEnd endpoint: accent-family solid + right-edge stripe.
[data-range-calendar-day][data-range-start][data-range-end]eidosSingle-day range: diagonal-split gradient (start ↘ end).
[data-range-calendar-cell][data-in-range]eidosIn-range cells get accent-track background fill on the cell.
[data-range-calendar-day][data-in-preview-range]eidosHover-preview state while user is choosing end (transparent + accent border).
[data-range-calendar-day][data-today]eidosToday marker (border ring + semibold).
[data-range-calendar-day][data-outside-month]eidosOutside-month dimming.
[data-range-calendar-day][data-disabled] · [data-unavailable]eidosnot-allowed cursor + reduced opacity (+ line-through for unavailable).
{/if} {#if tab === 'a11y'}

Accessibility

Follows the APG grid pattern with localized labels for range-start / range-end / in-range announced via aria-label additions on the relevant day cells.

Keyboard
KeyAction
ArrowLeft / ArrowRightMove focus ±1 day.
ArrowUp / ArrowDownMove focus ±1 week.
PageUp / PageDownMove focus ±1 month.
Shift+PageUp / Shift+PageDownMove focus ±1 year.
Home / EndFirst / last day of the visible week.
Enter / SpaceCommit start (first press) or end (second press).
EscapeCancel the in-progress range; revert to last committed.
ARIA contract
PartAttributeValue
providerrole"application"
provideraria-labellocalized "Range calendar"
gridrole"grid"
cellrole · aria-selected"gridcell" / "true|false"
dayaria-label"date — range start | range end | in range"
prev-button · next-buttonaria-label"Previous month" / "Next month"
{/if}