Appearance
Native Availability Picker
The local ChronoSketchCore and ChronoSketchUI packages provide civil availability rules, a retained model and SwiftUI multi-month selection. Availability Picker is under development for the next release; no distributed-version claim is made.
swift
import ChronoSketchCore
import ChronoSketchUI
// Throwing application setup on MainActor:
let start = try CivilDate("2026-09-14")
let data = try AvailabilityData(rules: [
AvailabilityRule(
range: AvailabilityRange(start: start, end: start.addingDays(120)),
available: true, minimumDays: 3)
])
let model = try AvailabilityPickerModel(start: start, days: 120, data: data)
// SwiftUI body, retaining model outside repeated view construction:
AvailabilityPickerView(model: model) { selection in
// Save a complete valid selection, or clear app state on nil.
}See the input field reference and application flexibility for the shared rules and boundaries. Swift wraps start/end in AvailabilityRange with CivilDate values; AvailabilityData(rules:) validates the supplied intervals.
All ranges exclude their end. Rules separately specify available, canStart, canEnd, minimumDays, and optional application reason. Nonoverlapping rule intervals are required; missing coverage is unknown. Minimum duration applies to all included dates. The last selectable date is start plus days minus one; full boundary months do not extend that limit. An unavailable but end-permitted day may close a selection without being part of its used interval.
setSelection updates selection without notification; explicit user reset invokes the callback with nil. setData replaces data and cancels pending loads. Active previews retain their original rules; completion checks latest data and explains conflicts. A completed selection remains visible if later data invalidates it.
setDataProvider supplies an application-owned Sendable async closure from AvailabilityRange to AvailabilityData. The view requests clipped visible-month windows. Return rules inside the query; uncovered dates remain unknown. Provider errors show retry; cancellation and generation checks suppress stale responses. reload refreshes, and suspendLoading cancels when hidden. Cache is temporary and bounded by the configured horizon. There is no networking or persistence client.
AvailabilityPickerConfiguration supplies locale, optional Sunday-zero week start, short weekday names (seven, Sunday first), month names (twelve, January first), visible-month count, layout column capacity and interactive/static mode. Haptics are on by default and can be disabled. showAvailable, showUnavailable, showRestrictions, showAnnotations and showPreview control indicators; showGaps optionally marks dates with a valid possible range.
text, dayContent and tooltipContent receive a date-aware context containing CivilDate, Sunday-zero weekday, ISO week/week-year, rule, annotation, selection and issue. Content closures return AnyView and remain inside reserved areas. Text and configuration are presentation values and can update without replacing the model. timeZone is optional callback context, not a date conversion instruction.
setAnnotations accepts nonoverlapping identified/tagged ranges with optional labels. These decorate dates without changing availability. SwiftUI tint/styles and custom content supply application appearance. Hourly selection is deferred. Physical haptics and a full VoiceOver audit need device acceptance beyond package and simulator checks.
Available-day shape
Web dayStyle: 'squared' | 'rounded' and Swift configuration.dayStyle (.squared / .rounded) control the available-day surface. Squared is the default. The surface is centered at 36px/pt maximum (Web shrinks it further in narrow cells), with small square corners or a circle. The month grid and minimum 44px/pt hit area stay unchanged. Restriction, annotation and bookable-gap indicators fit the same compact day surface. Selection and preview retain their range markings. Both demos expose this setting as “Day shape”.
Day badges
Set configuration.dayBadge to return an optional AvailabilityDayBadge for the existing date context. The badge sits at the upper-right edge of the 36pt surface, with a small overflow and no separate action. The day number and hit area remain unchanged; the localized label joins its accessibility description.
swift
configuration.dayBadge = { context in
guard context.annotation != nil else { return nil }
return AvailabilityDayBadge(
color: .green, iconColor: .white, label: "Special offer",
content: .icon("checkmark"))
}Required fields are background color and localized label. iconColor defaults to white and also colors numbers. content is .dot by default (16pt), .icon(SFSymbolName) or .number(UInt) (16pt). Zero is visible; values above 99 show 99+, with the full count provided in label. Every variant is a fixed 16pt circle without a border. Returning nil removes the badge. Presentation changes preserve model selection. Badges are independent of rule/annotation data and showAnnotations; applications control their visibility. Annotation labels are metadata, no longer inline day text. Existing day/tooltip content closures remain supported.
Indicator color
indicatorColor (Swift: configuration.indicatorColor) sets period-annotation accents and restriction indicators on available days. Supply a CSS color on Web or SwiftUI Color on iOS; the default follows the accent color. For example, use indicatorColor: 'var(--xc-accent)' or configuration.indicatorColor = .accentColor. Bookable-gap outlines use a neutral border to distinguish them from the indicator. Unavailable-day styling, badge colors and selection colors remain independent. Changing this presentation option preserves selection and does not affect rules.
Badge precedence over annotation accents
A day with a badge suppresses its additional bottom annotation accent. This avoids a duplicate visual marker alongside the badge and bookable-gap outline. Removing the badge restores the annotation accent when annotations are enabled. Rule restriction indicators, gap outlines and selection markings remain independent. This presentation precedence applies on Web and iOS and changes no availability or annotation data.
Badge sizing refinement: dot, icon and number share a fixed 16px/pt circle. No border, outline or outline-like shadow is drawn. Numeric content uses 7px/pt text so 99+ fits without changing badge dimensions.
Selection shape refinement: squared day styling uses a small 4px/pt selection corner radius. Web start/end round only the outer corners and retain straight inner connections. Rounded mode retains its existing selection geometry. Switching shape preserves the selected dates and does not affect selection rules.
Shared picker frame — 2026-09-15
All visible months belong to one shared card with common navigation and selection feedback/actions. Month headings use a subtle surface band and separator; months are separated by fine rules, not individually elevated cards. Side-by-side months have vertical separation; stacked rows have horizontal separation.
The card follows the configured column capacity (up to 360px/pt per column, capped by visible-month count and available width). Narrow layouts retain all months and stack them. Web removes the unused reserved detail height; actual feedback retains its accessible live region. There is no new model option or change to selection, loading, badges, date semantics or static-mode rules. Existing shape settings continue to control days and selection endpoints independently of the outer card.
Unavailable days use one thin, closed contour in the muted/secondary foreground color, matching the 36px/pt day shape. It replaces the restriction underline for these days, rather than adding a second border. The strikethrough remains; showUnavailable controls both marks. Available-day indicators are unchanged.
Shared hint card
showHints defaults to true. Interactive pickers show one reserved bottom hint card with a leading accent indicator, shared across all months. Pointer/focus feedback on Web and the current touched/hovered day on iOS update this card. Disable it independently of selection previews; static mode has no hint card. Web uses a sticky footer bounded by the picker; native uses a bottom safe-area inset. The card reserves 72px/pt (native scales with Dynamic Type), with internal scrolling for longer application content. text and existing custom tooltip content supply its contents. Selection errors and loading feedback remain visible independently of this setting. Both demo settings expose Hint card.
This replaces the former per-month Web detail area and supersedes the earlier empty-detail collapse behavior. No data or selection contract changes.
The confirmed selection appears inside the hint card above the changing day hint. Hover/focus updates never replace that selection summary. With hints disabled, the summary remains in the ordinary footer/feedback area. The Web demo no longer duplicates raw ISO selection dates below the picker.
Native selection haptics emit one signal after validation: a soft activation for an accepted start, success for a valid completed range, and error for a rejected start/end (including changed availability). No activation pulse precedes success/error. hapticsEnabled = false suppresses all three. Success confirms the local date selection, not a booking or persistence in the application.
Month cards follow dayStyle: rounded uses 12px/pt corners and 12px/pt gaps between islands; squared keeps adjoining rectangular cards. Each card has a complete border. Card heights stretch to the tallest month only in their own responsive grid row, so a six-week row does not add a sixth week to other rows. Day rows retain their spacing; shorter months receive trailing blank space. This supersedes the previous shared separator-only month framing.
Native selection follows the Web range treatment: full-cell connected bands, accent-filled start/end and a lighter interior. Only the start's leading edge and the end's trailing edge are rounded (24pt rounded, 4pt squared); interior edges are straight. Week/month wraps keep their grid boundaries without adding false start/end caps. An active start is accent-filled before completion. The briefly introduced separate native selection circles are superseded.
The bottom hint card shares the picker card width limit and 12pt outer inset, remaining centered and edge-aligned on wide displays.