Calendar
One calendar.* entity with its events from the start of today to a horizon a week away, and the events of any other
calendars you read beside it, merged into one list. What’s next, and when. Read-only.
Quick example
Section titled “Quick example”import { useHaCalendar } from "@dashhaus/core";import { useHourCycle } from "@dashhaus/core/hooks";import { calendarAgenda } from "@dashhaus/core/entities/calendar/agenda";import { eventWhen, freeFor } from "@dashhaus/core/entities/calendar/words";
/** The family's next event and when it is, with the school's calendar read beside it, on the page's clock. */export function WhatsNext() { const { entity } = useHaCalendar("calendar.family", { calendarEntityIds: ["calendar.school"] }); const hourCycle = useHourCycle(); if (entity.state === "unavailable") return <p>{entity.name} is unavailable</p>; if (!entity.events) return <p>Reading {entity.name}…</p>; const { next } = calendarAgenda(entity.events, entity.now); if (!next) return <p>{freeFor(entity.days).line}</p>; return <p>{next.title}: {eventWhen(next, entity.now, hourCycle).line}</p>;}calendarAgenda picks the event a card leads with: one under way, else the next to start. Within a day a timed
event comes before the all-day ones, so bin day never hides the dentist at two. eventWhen says when it is, counted
from the entity’s own minute: In 2h 6m, Until 12:15, All day, Tomorrow 09:30, or on a 12-hour page
Tomorrow 9:30 AM (pass it the page’s useHourCycle()). Every skin says it in these words.
Component
Section titled “Component”<HaCalendar> is useHaCalendar as a component that draws nothing of its own. It takes every option of the hook as
a prop, and hands the hook’s result to its child function. Use it where hooks can’t go, such as one per item of a
list.
entityIdstring- The Home Assistant entity id.
children(live: HaCalendarResult) => ReactNode- Draws the hook's result. The component itself draws nothing.
useHaCalendar(entityId: string, options?: HaCalendarOptions): HaCalendarResultOne live, read-only calendar for any UI: the mapped entity composed with its events, and those of any other calendars read beside it, from today's start to the horizon; and today's plan by hour. The events are their own ranged read, `calendar.get_events` through the store's query, as the weather's forecasts are: read again each hour, on a new day, and whenever the calendar itself changes (an event starts or ends), the last events staying up while the next read loads. Today's plan is computed from those events, never read from the recorder; `loadHistory: false` leaves it out. It sends no services.
It reads the calendar and its area, and its events through calendar.get_events, one call per calendar, from the start
of today for days days. They are read again each hour, on a new day, and whenever the calendar itself changes (an
event starts or ends); the last events stay up while the next read loads. Until the first read settles, the calendar’s
own current or next event stands in. Pass events to supply your own, or loadEvents={false} to skip the read.
Options
Section titled “Options”calendarEntityIds?readonly string[]- More `calendar.*` entities read beside it, their events merged into its own and named by their calendar.
days?number- How many days to look ahead, today included. Default 7.
name?string- Overrides Home Assistant's friendly name.
area?string- Overrides the area Home Assistant's registries resolve.
lastSeen?string- Overrides when the entity was last reachable, shown while it is unavailable.
events?readonly CalendarEvent[]- Host-owned events override (every calendar's, merged); when omitted, they are read with `calendar.get_events`.
loadEvents?boolean- Set false when the events are supplied by another application-owned query: the entity's own event stands in.
onEventsError?(error: unknown) => voidloadHistory?boolean- Set false to leave out today's plan by hour (YAML `history: false`): `history` is then undefined.
Returns
Section titled “Returns”entityCalendarEntityhistoryCalendarHistory | undefined- Today's plan by hour, from the events: no recorder read. Undefined with `loadHistory: false`.
entity: CalendarEntity
Section titled “entity: CalendarEntity”entityIdstring- The primary calendar.
namestringarea?string- Its area: the `area` option, else the one Home Assistant's registries resolve; none when neither has one.
state"unavailable" | "ok"- "unavailable" while the primary calendar can't be reached; "ok" otherwise, even before its events are read.
events?CalendarEvent[]- Every event from today's local midnight to the end of the horizon, on every calendar it reads, by start; ones already over today are kept, so the details can show the whole day. Omitted until the first read settles and the entity names no event of its own.
nownumber- The minute the card counts from, in ms since the epoch: the hook's minute clock, or a seed's fixed 11:54. "In 25m", "Now" and today's events are all counted from it, so a card draws the same whenever it is drawn.
daysnumber- How many days it looks ahead, today included (the `days` option, default 7).
calendarsnumber- How many calendars it reads, itself included (1 plus the `calendars` option's).
lastUpdated?string- When its events were last read ("11:54").
lastSeen?string- While it is unavailable, when it was last reachable.
CalendarEvent
Section titled “CalendarEvent”One of entity.events.
titlestring- Its summary: "Dentist". An event with none is "Busy", as a shared calendar shows it.
startnumber- When it starts, in ms since the epoch. An all-day event starts at local midnight of its first day.
endnumber- When it ends, in ms since the epoch, exclusive. An all-day event ends at local midnight after its last day.
allDayboolean- A whole day or days, with no time of its own (a birthday, bin day).
location?stringcalendar?string- The calendar it is on, by name, when the card reads more than one: "School".
history: CalendarHistory
Section titled “history: CalendarHistory”Today’s plan, drawn from the events rather than Home Assistant’s recorder: the share of each hour taken by timed events,
the hours still to come included, and the minutes that adds up to. All-day events take no hours. Pass
loadHistory: false to leave it out: history is then undefined.
samplesHourSample[]- Per hour, 0 to 1: the share of it taken by timed events (all-day events take none).
currentHournumberbusyMinutesnumber- Minutes taken by timed events today, the whole day: those over, under way and still to come.
Today’s events
Section titled “Today’s events”calendarAgenda also sorts the day: today is every event on it, all-day first, those already over kept so the day
reads whole; left those not over yet; later what starts after today. eventSpan says an event’s span
(14:00–15:00, All day).
import { useHaCalendar } from "@dashhaus/core";import { calendarAgenda } from "@dashhaus/core/entities/calendar/agenda";import { eventSpan, isOver, leftToday } from "@dashhaus/core/entities/calendar/words";
/** Today's events, all-day first, those already over struck through, and how many are left. */export function Today() { const { entity } = useHaCalendar("calendar.family", { days: 1 }); const { today, left } = calendarAgenda(entity.events, entity.now); return ( <section> <h2>Today · {leftToday(left.length, today.length)}</h2> <ol> {today.map((event) => ( <li key={`${event.start}-${event.title}`} style={{ textDecoration: isOver(event, entity.now) ? "line-through" : undefined }}> {eventSpan(event, entity.now)} {event.title} </li> ))} </ol> </section> );}In a skin
Section titled “In a skin”Orb’s HaCalendarTile and Field’s HaCalendarModule are useHaCalendar above each skin’s card. Each leads with the
next event and when it is, counts what’s left today, and opens on today’s events and what’s coming. With nothing to
the horizon they say Free. They take the hook’s options as props, plus their own:
import { HaCalendarTile } from "@dashhaus/orb";import { HaCalendarModule } from "@dashhaus/field";
/** The family's calendar with the school's beside it, looking two weeks ahead. */export function OrbFamily() { return <HaCalendarTile entityId="calendar.family" calendarEntityIds={["calendar.school"]} days={14} />;}
/** The same as a compact row: the next event's title on the row's line, the clock it starts at on the screen. */export function FieldFamily() { return <HaCalendarModule entityId="calendar.family" footprint="compact" />;}entityIdstring- The Home Assistant entity id.
showControl?boolean- Compact only. false removes the inline control, shows the value in its place, and makes the row open the details. Default true.
footprint?CardFootprint- Card footprint. The skin's stylesheet sets the heights; the three names are the shared vocabulary.
defaultOpen?booleandefaultAction?undefined
entityIdstringfootprint?FieldFootprintdefaultOpen?boolean- Open the detail panel on first render (stories).