You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/eidos/components/calendar/README.md

14 KiB

Eidos Calendar

Visual wrapper for Soma Calendar. Calendar follows the compound Eidos surface: Calendar is the public root and parts hang from it (Calendar.Day, Calendar.Grid, etc.). There is no public Provider export.

Baseline

Source Relevant coverage Decision
Air morfo-runtime:src/uix/air/components/calendar Full visual shell over Terra: root size, header, heading, prev/next, month/year selects, grid, cell/day states; direct Air sound on selection/navigation. Keep the full part coverage and size. Do not copy direct sound; selection/navigation move to Morfo/Sema.
Soma current Single/multiple value, placeholder navigation, min/max, disabled/unavailable/holiday matchers, fixed weeks, multi-month, week numbers, locale, RTL and live announcements. Eidos wraps every public Soma part and adds only size, variant, color.
Morfo current 15 declared parts including WeekNumber contract, grid APG, day/cell state data. Add semantic events because Calendar is interactive. WeekNumber remains contract-only until Soma exposes a component.
Sema current No calendar pack before this migration. Add calendarSema for select/unselect and navigation.

Comparativa

Capability Air React Aria Ark UI Bits UI shadcn-svelte UIX decision
Single date yes yes yes yes yes Soma owns.
Multiple dates inherited Terra RangeCalendar separate in React Aria; non-contiguous supported by API docs selectionMode="multiple" type="multiple" via Bits Calendar Soma supports type="multiple".
Multiple months yes visibleDuration numOfMonths numberOfMonths via Bits Calendar Soma supports numberOfMonths; Eidos styles without assuming layout.
Fixed weeks yes display option fixedWeeks fixedWeeks via Bits Calendar Soma supports.
Month/year select yes documented as custom dropdowns documented example public MonthSelect/YearSelect generated wrappers around Bits Eidos exposes MonthSelect and YearSelect.
Min/max/unavailable yes minValue, maxValue, isDateUnavailable min, max, isDateUnavailable min/max/unavailable/disabled via Bits Calendar Soma supports selection and view bounds; invalid pairs surface as invalid + logger error.
Week numbers no Air-specific part not a first-class Calendar part showWeekNumbers documented separately through composition not primary API Soma exposes showWeekNumbers snippet data; visual support is included.
Holiday/weekend flags Air styled some date states only custom render state custom machine state possible custom render state possible via custom code Soma emits data-holiday and data-weekend; Eidos styles both.
Semantic feedback Air called interaction sounds in wrapper library-specific announcements callbacks callbacks none in wrapper Morfo/Sema owns commit-select, commit-unselect, shift-navigate.

Reference docs checked: React Aria Calendar, Ark UI Date Picker, Bits UI Calendar, shadcn-svelte Calendar, Radix Primitives components list and WAI-ARIA APG Date Picker Dialog/Grid.

Morfo / Sema

Event Family Target Runtime source Sema default
commit-select commit.select day CalendarProvider.select(date, target) after value changes soft form commit + tap
commit-unselect commit.remove day same, when a date is removed/cleared subtle form commit + tap
shift-navigate shift.navigate months (fallback grid) prevPage, nextPage, setMonth, setYear, keyboard month boundary family default slide (arc, gain 0.06) + the directional shift motion firma

Calendar is not passive: a 0-event contract would hide committed selection and visible-range navigation from the semantic layer. This migration closes that gap before adding the Eidos recipe.

Eidos API

<script lang="ts">
	import { Calendar } from '$uix/eidos/components/calendar';
	import { CalendarDate, type DateValue } from '$libs/days';

	const placeholder = new CalendarDate(2026, 5, 1);
	let value = $state<DateValue | undefined>(placeholder);
</script>

<Calendar type="single" bind:value {placeholder} size="md" variant="surface" color="primary">
	{#snippet children({ months, weekdays })}
		<Calendar.Header>
			<Calendar.PrevButton>Previous</Calendar.PrevButton>
			<Calendar.Heading />
			<Calendar.NextButton>Next</Calendar.NextButton>
		</Calendar.Header>
		{#each months as month (month.value.toString())}
			<Calendar.Grid>
				<Calendar.GridHead>
					<Calendar.GridRow>
						{#each weekdays as day}
							<Calendar.HeadCell>{day}</Calendar.HeadCell>
						{/each}
					</Calendar.GridRow>
				</Calendar.GridHead>
				<Calendar.GridBody>
					{#each month.weeks as week}
						<Calendar.GridRow>
							{#each week as date}
								<Calendar.Cell {date} month={month.value}>
									<Calendar.Day />
								</Calendar.Cell>
							{/each}
						</Calendar.GridRow>
					{/each}
				</Calendar.GridBody>
			</Calendar.Grid>
		{/each}
	{/snippet}
</Calendar>

Decisiones

  • No se expone Calendar.Provider en Eidos. El componente raíz monta el provider internamente y estampa data-size / data-variant / data-color. Los consumidores manipulan estado vía props (bind:value, bind:placeholder).
  • commit-select lleva intent: 'affirm' porque seleccionar una fecha es un compromiso evaluativamente positivo (suave). Un commit destructivo correspondería a commit-unselect con intent: 'neutral'.
  • shift-navigate cubre todo el movimiento de mes/año (prev/next, selects, atajos de teclado). No se subdivide en variantes de verbo: el sentido del paso viaja POR EMISIÓN en data-event-direction (forward/backward), y lo pone quien da el paso — la paginación y el teclado saben hacia dónde fueron; un salto desde el select no pasa ninguno.
  • El sello de shift-navigate cae en months, no en el provider, ni en el botón, ni en un grid. Months es la vista paginada: el sujeto del cruce, y lo único que puede LUCIR una firma de shift (una página se desliza, un botón no). Llegó ahí en dos correcciones. La primera, el 2026-08-11, lo sacó del botón: medido sobre la flecha «anterior», recibía su contact-activate, arrancaba el press-squeeze y 8,5 ms después —media trama— shift-navigate pisaba la misma ranura y el squeeze moría sin pintar. Una superficie, una ranura. La segunda, el 2026-08-12, lo sacó del grid: grid es una parte REPETIDA, y un emit sin ancla resuelve a la instancia viva más reciente, así que con numberOfMonths=2 sólo cruzaba el segundo mes y el primero cambiaba de fechas sin moverse — el shift invisible (cap. 34 §14) sobre la mitad del componente. Un destino que sólo es correcto para un valor del parámetro no es un destino. Medido tras el cambio: los dos grids se desplazan 10 px a los 40 ms del cruce, y −10 px en RTL.
  • El anillo de evento perdió el provider en la receta (box-shadow: var(--calendar-event-shadow)): nada apunta ya a [data-calendar] y la regla era una selección muerta. months tampoco entra en la lista: su firma es el cruce, no un anillo.
  • MonthPanel es una parte, no un div del consumidor. Los cuatro demos multi-mes fabricaban a mano un <div data-calendar-month-panel> que ningún morfo declaraba y que esta receta estilizaba igualmente; el contenedor lo montaban con un style en línea que rederivaba el número de meses en cada página. Ambos son partes desde el 2026-08-12, y la disposición vive aquí (grid-auto-flow: column, sin contar meses). Se quedan el día (lo alimenta commit-select) y las flechas y los dos selects, que componen <Button> y conservan el anillo por su propio contact-activate. Verificado en navegador.
  • Las flechas de teclado son focus moves puros: mueven el día enfocado pero no mutan value. Por eso el morfo declara 10 keyboards y sólo 3 eventos: Enter/Space (mutación) → commit-select, los 8 restantes no necesitan evento propio.
  • data-readonly es estado, no atributo de morfo input: el grid marca el aria-readonly y la receta lo tinta con tono atenuado pero permite seguir leyendo y navegando con teclado.

Talla y tema

Contrato en lib/recipes/base.ts > calendar — y esa entrada es la de la FAMILIA, no sólo la de este componente: RangeCalendar, MonthGrid, YearGrid y los calendarios de los dos pickers leen el mismo vocabulario --calendar-* (medido 2026-08-21: 114 / 77 / 77 referencias y CERO tokens propios). Alcance del censo 84 %.

La resolución por talla vive en lib/calendar-surface.css, enganchada al hook data-calendar-surface que este wrapper estampa: cuatro coordenadas (padding, control-size, day-size, font-size), xs..lg — la familia no tiene xl — y la celda dos pasos por debajo del bundle de control. Antes ese bloque estaba copiado en cuatro recetas con un privado de nombre distinto en cada una.

El anillo de evento y la marca de festivo también son de la capa: la FORMA allí, el ACENTO desde aquí (--_calendar-surface-accent ← el forward de paleta THM-2 de cada superficie).

Gaps

Gap Disposición Detalle
Public Calendar.WeekNumber part diferir Morfo declara el part, pero Soma expone los números de semana a través del snippet data de GridRow en vez de un componente DOM. Requiere refactor en Soma antes de exponer en Eidos.
Range selection descartar Vive en range-calendar / componentes de range, no en Calendar.
Popover date picker shell descartar Vive en date-picker; Calendar es inline por diseño.
Modal + footer (clear / cancel / close) compartido con pickers diferir Calendar inline no lo necesita; los pickers lo aportan en su capa. Si llega un caso de Calendar como contenido de Drawer, se reevalúa.
Año/mes con scroll vertical estilo iOS diferir Patrón nuevo, no cubierto por ninguna referencia que mantengamos. Sin caso de uso real.

Powered by TurnKey Linux.