@dataverse-kit/calendar-kit 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@ and a Power Pages site.
7
7
 
8
8
  ```bash
9
9
  npm install @dataverse-kit/calendar-kit
10
- # peers: react@18, react-dom@18; @fluentui/react-components@9 only for ./v9
10
+ # peers: react@18 or 19, react-dom@18 or 19; @fluentui/react-components@9 only for ./v9
11
11
  ```
12
12
 
13
13
  ## Entries
@@ -143,6 +143,16 @@ Measured on a live Dataverse org, not taken from documentation; each is pinned b
143
143
  - `webAPI.updateRecord` and `navigation.openForm` parameters are **different contracts** — see
144
144
  `src/dataverse/dates.ts`. A bare `YYYY-MM-DD` quick-create parameter stored the previous day.
145
145
  - A dataset range filter is ANDed with the view's own filter.
146
+ - **Code app (live, 2026-09-29, React 19 template):** the generated service returns
147
+ `{ success, data, skipToken, count, error }`; the next page arrives as **`skipToken`** (a Dataverse
148
+ paging cookie), which the adapter passes back — 2 + 2 + 1 rows at `maxPageSize: 2`. Rows are Web
149
+ API shaped (`…Z` instants, FormattedValue annotations included without asking). A move written
150
+ through `service.update` changed exactly that row (`modifiedon` witness).
151
+ - **Power Pages (live, traditional portal):** the range + `statuscode` filter returned exactly the
152
+ open slots; a lookup's FormattedValue label came back for a role with **no** read permission on
153
+ the target table. On a PRIVATE site a signed-in maker previews as the **Anonymous** web role.
154
+ - Both hosts: one load per mount. (Before 0.3.0 the hook loaded twice — the calendar reports its
155
+ range from a child effect that runs first.)
146
156
  - The pull sources' `$filter` literals, checked at both edges of a week on the Web API: a
147
157
  DateOnly column needs a **bare day** (an instant is compared by its UTC date, which is wrong for
148
158
  any week east of UTC), and a TimeZoneIndependent column needs the **wall clock + `Z`** (an
@@ -155,8 +165,9 @@ Measured on a live Dataverse org, not taken from documentation; each is pinned b
155
165
  (Vite, webpack, pcf-scripts). Their `.cjs` builds cannot be `require()`d from plain Node. The
156
166
  pure entries (`./core`, `./dataverse`, `./adapters/*`, `./fake`) work anywhere.
157
167
  - Importing both the root and `./v9` bundles `CalendarView` twice. Pick one.
158
- - **Unmeasured:** the code-app connector's paging field. Both `skipToken` and `nextLink` are read,
159
- paging is capped (`maxRows`, default 1000) so a connector that always returns a token stops.
168
+ - Paging is capped (`maxRows`, default 1000) so a connector that always returns a page token stops.
169
+ - Power Pages `/_api` ignored `Prefer: odata.maxpagesize` (measured at 2: all rows came back in one
170
+ page). The adapter's `@odata.nextLink` loop is correct but was only exercised by unit tests.
160
171
  - On a spring-forward day a selection inside the missing hour has zero length (that hour does
161
172
  not exist in the zone).
162
173
 
package/dist/index.cjs CHANGED
@@ -561,9 +561,11 @@ function useCalendarSource(source) {
561
561
  const range = React3.useRef(null);
562
562
  const seq = React3.useRef(0);
563
563
  const inflight = React3.useRef(null);
564
+ const loadedFor = React3.useRef(null);
564
565
  const load = React3.useCallback(async () => {
565
566
  const r = range.current;
566
567
  if (!source || !r) return;
568
+ loadedFor.current = source;
567
569
  inflight.current?.abort();
568
570
  const controller = new AbortController();
569
571
  inflight.current = controller;
@@ -582,16 +584,16 @@ function useCalendarSource(source) {
582
584
  }
583
585
  }, [source]);
584
586
  React3.useEffect(() => {
585
- setEvents([]);
586
- void load();
587
- }, [load]);
588
- React3.useEffect(
589
- () => () => {
587
+ if (loadedFor.current !== source) {
588
+ setEvents([]);
589
+ void load();
590
+ }
591
+ return () => {
590
592
  seq.current++;
591
593
  inflight.current?.abort();
592
- },
593
- []
594
- );
594
+ loadedFor.current = null;
595
+ };
596
+ }, [load, source]);
595
597
  const onRangeChange = React3.useCallback(
596
598
  (r) => {
597
599
  const prev = range.current;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/view/CalendarView.tsx","../src/core/datetime.ts","../src/core/colors.ts","../src/view/fcBridge.ts","../src/view/useOptimisticChanges.ts","../src/view/theme.ts","../src/view/useCalendarSource.ts"],"sourcesContent":["/**\n * `@dataverse-kit/calendar-kit` — a calendar fed plain events. Fluent-free: this is the entry a\n * Power Pages site (Fluent v8, or none) uses. For Fluent v9 styling import from `./v9`; for the\n * contract and date rules alone, `./core`; for Dataverse, `./dataverse` and `./adapters/*`.\n */\nexport { CalendarView } from './view/CalendarView';\nexport type { CalendarViewName, CalendarViewProps } from './view/CalendarView';\nexport { themeStyle } from './view/theme';\nexport type { CalendarTheme } from './view/theme';\nexport * from './core';\nexport { useCalendarSource } from './view/useCalendarSource';\nexport type { CalendarSourceBinding } from './view/useCalendarSource';\n","import * as React from 'react';\nimport FullCalendar from '@fullcalendar/react';\nimport dayGridPlugin from '@fullcalendar/react/daygrid';\nimport timeGridPlugin from '@fullcalendar/react/timegrid';\nimport listPlugin from '@fullcalendar/react/list';\nimport interactionPlugin from '@fullcalendar/react/interaction';\nimport classicThemePlugin from '@fullcalendar/react/themes/classic';\nimport type {\n DateSelectInfo,\n DatesSetInfo,\n EventChangeInfo,\n EventClickInfo,\n} from '@fullcalendar/react';\n\nimport '@fullcalendar/react/skeleton.css';\nimport '@fullcalendar/react/themes/classic/theme.css';\nimport '@fullcalendar/react/themes/classic/palette.css';\n\nimport { browserZone } from '../core/datetime';\nimport type { BusinessHours, CalendarEvent, CalendarEventChange, SlotSelection, VisibleRange, ZoneOffset } from '../core/types';\nimport { applyPending, nowWall, sameAllDayness, toFcEvent, toSelection, toVisibleRange } from './fcBridge';\nimport { useOptimisticChanges, type ChangeInfoLike } from './useOptimisticChanges';\nimport { themeStyle, type CalendarTheme } from './theme';\n\n/** The views a host can ask for, by plain name. */\nexport type CalendarViewName = 'month' | 'week' | 'workWeek' | 'day' | 'list';\n\nconst FC_VIEW: Record<CalendarViewName, string> = {\n month: 'dayGridMonth',\n week: 'timeGridWeek',\n workWeek: 'timeGridWorkWeek',\n day: 'timeGridDay',\n list: 'listWeek',\n};\n\nconst VIEW_LABEL: Record<CalendarViewName, string> = {\n month: 'Month',\n week: 'Week',\n workWeek: 'Work week',\n day: 'Day',\n list: 'List',\n};\n\nexport interface CalendarViewProps {\n /** Plain event objects. See `CalendarEvent` for how a value's shape sets its kind. */\n events: readonly CalendarEvent[];\n /** The zone to show instants in. Default: the browser's. A PCF host passes the USER's. */\n zone?: ZoneOffset;\n /** Starting view. Default `week`. */\n view?: CalendarViewName;\n /** View buttons offered, in order. Default: month, week, day, list. */\n views?: readonly CalendarViewName[];\n /** `YYYY-MM-DD` to open on. Default today (in `zone`). */\n initialDate?: string;\n /** Allow drag and resize. Default false — read-only is the safe default. */\n editable?: boolean;\n /** Allow selecting an empty range (→ `onSelectRange`). Default: true when a handler is given. */\n selectable?: boolean;\n businessHours?: readonly BusinessHours[];\n /** 0 = Sunday. */\n firstDay?: number;\n /** `HH:mm:ss` bounds of the time grid. */\n slotMinTime?: string;\n slotMaxTime?: string;\n /** Where the time grid first scrolls to. Default `08:00:00`. */\n scrollTime?: string;\n /** Any CSS height, `'auto'`, or a number of pixels. Default `'auto'`. */\n height?: number | string;\n theme?: CalendarTheme;\n /** Shown while data for a new range loads. */\n loading?: boolean;\n className?: string;\n /**\n * Classes for FullCalendar's own parts. ★ v7 HASHES its class names (`fc-classic-1El`), so a\n * descendant selector like `.fc-button` matches NOTHING — measured: 0 of 7 buttons. These go\n * through FullCalendar's `buttonClass` / `headerToolbarClass` options instead.\n */\n classNames?: { button?: string; toolbar?: string };\n /** Accessible name for the calendar region. */\n 'aria-label'?: string;\n\n onEventClick?: (event: CalendarEvent) => void;\n onSelectRange?: (selection: SlotSelection) => void;\n /**\n * A move or resize. Return a promise to make it OPTIMISTIC: the event stays where it was\n * dropped while the promise runs, and goes back — with a message — if it rejects.\n */\n onEventChange?: (change: CalendarEventChange) => void | Promise<void>;\n onRangeChange?: (range: VisibleRange) => void;\n /** Called after a failed change has been reverted. The banner is shown either way. */\n onChangeError?: (error: unknown, change: CalendarEventChange) => void;\n}\n\n/** FullCalendar's change info → the hook's minimal shape (what the hook reads, and nothing else). */\nconst asChangeInfo = (info: EventChangeInfo): ChangeInfoLike => ({\n event: {\n start: info.event.start as Date | null,\n end: info.event.end as Date | null,\n allDay: info.event.allDay,\n extendedProps: info.event.extendedProps,\n },\n revert: () => info.revert(),\n});\n\nconst bannerStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '6px 10px',\n marginBottom: 6,\n borderRadius: 4,\n border: '1px solid currentColor',\n};\n\n/**\n * FullCalendar v7 (MIT views only), fed plain events. Knows nothing about Dataverse and nothing\n * about Fluent — this entry is what a Power Pages site uses. `FluentCalendar` (`./v9`) wraps it\n * with Fluent tokens.\n *\n * ★ Nothing here portals and nothing uses tabster, so it cannot join the Fluent/tabster\n * singleton race that has taken down PCF controls on model-driven forms. The error banner is\n * in-tree for the same reason: a Toast or Dialog would portal to `document.body`.\n */\nexport function CalendarView(props: CalendarViewProps): React.ReactElement {\n const zone = props.zone ?? browserZone;\n const zoneRef = React.useRef(zone);\n zoneRef.current = zone;\n\n const { pending, error, dismissError, handleChange } = useOptimisticChanges(\n props.events,\n props.onEventChange,\n props.onChangeError,\n zoneRef\n );\n\n const fcEvents = React.useMemo(\n () => applyPending(props.events, pending).map((e) => toFcEvent(e, zone)),\n [props.events, pending, zone]\n );\n\n const views = props.views ?? (['month', 'week', 'day', 'list'] as const);\n\n /**\n * ★★ Options the host did not set are OMITTED, never passed as `undefined`. FullCalendar's\n * option refiners treat a present-but-undefined key as a value: `slotMinTime={undefined}`\n * replaced the default and the time-grid views threw \"Cannot read properties of undefined\n * (reading 'years')\" on first render. The list view never reads it, so a list-only test\n * was green — found by rendering the WEEK view.\n */\n const optional: Record<string, unknown> = {};\n if (props.firstDay !== undefined) optional.firstDay = props.firstDay;\n if (props.slotMinTime !== undefined) optional.slotMinTime = props.slotMinTime;\n if (props.slotMaxTime !== undefined) optional.slotMaxTime = props.slotMaxTime;\n if (props.initialDate !== undefined) optional.initialDate = props.initialDate;\n if (props.classNames?.button) optional.buttonClass = props.classNames.button;\n if (props.classNames?.toolbar) optional.headerToolbarClass = props.classNames.toolbar;\n if (props.businessHours) optional.businessHours = props.businessHours.map((b) => ({ ...b, daysOfWeek: [...b.daysOfWeek] }));\n const selectable = props.selectable ?? !!props.onSelectRange;\n\n return (\n <div\n className={props.className}\n style={themeStyle(props.theme)}\n role=\"region\"\n aria-label={props['aria-label'] ?? 'Calendar'}\n aria-busy={props.loading || undefined}\n data-calendar-kit=\"\"\n >\n {error && (\n <div role=\"alert\" style={{ ...bannerStyle, color: props.theme?.error ?? '#a4262c' }}>\n <span style={{ flex: 1 }}>{error}</span>\n <button type=\"button\" onClick={dismissError} aria-label=\"Dismiss\" style={{ font: 'inherit' }}>\n ×\n </button>\n </div>\n )}\n <FullCalendar\n plugins={[dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin, classicThemePlugin]}\n timeZone=\"UTC\"\n now={() => nowWall(zoneRef.current)}\n initialView={FC_VIEW[props.view ?? 'week']}\n views={{ timeGridWorkWeek: { type: 'timeGridWeek', hiddenDays: [0, 6] } }}\n headerToolbar={{\n start: 'today prev,next',\n center: 'title',\n end: views.map((v) => FC_VIEW[v]).join(','),\n }}\n // ★ v7 renamed v6's `buttonText` to `buttons: { name: { text } }`, keyed by VIEW name.\n buttons={{\n today: { text: 'Today' },\n ...Object.fromEntries(views.map((v) => [FC_VIEW[v], { text: VIEW_LABEL[v] }])),\n }}\n height={props.height ?? 'auto'}\n events={fcEvents}\n editable={!!props.editable && !!props.onEventChange}\n selectable={selectable}\n selectMirror\n nowIndicator\n dayMaxEvents\n {...optional}\n scrollTime={props.scrollTime ?? '08:00:00'}\n eventAllow={(span, moving) => (moving ? sameAllDayness(span.allDay, moving.allDay) : true)}\n datesSet={(info: DatesSetInfo) =>\n props.onRangeChange?.(toVisibleRange(info.start as Date, info.end as Date, info.view.type, zoneRef.current))\n }\n eventClick={(info: EventClickInfo) => {\n info.jsEvent.preventDefault();\n const source = (info.event.extendedProps as { source?: CalendarEvent }).source;\n if (source) props.onEventClick?.(source);\n }}\n select={(info: DateSelectInfo) => {\n props.onSelectRange?.(toSelection(info.start as Date, info.end as Date, info.allDay, zoneRef.current));\n info.view.calendar.unselect();\n }}\n eventDrop={(info: EventChangeInfo) => handleChange('move')(asChangeInfo(info))}\n eventResize={(info: EventChangeInfo) => handleChange('resize')(asChangeInfo(info))}\n />\n </div>\n );\n}\n","import type { CalendarValue, ValueKind, ZoneOffset } from './types';\n\n/**\n * Value-kind detection and zone conversion.\n *\n * ★★ Nothing in this file may read the PROCESS time zone. Every `Date` is built and read through\n * its UTC fields, so the same input gives the same output on a laptop in Auckland and a CI\n * runner in UTC. `npm test` runs the suite under three TZ values to prove it — a single-zone\n * run cannot see a leak, because the developer's zone is the one the code was written in.\n */\n\nconst DATE_RE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst FLOATING_RE = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d{1,9})?)?$/;\nconst INSTANT_RE = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?(?:Z|[+-]\\d{2}:?\\d{2})$/i;\n\nconst pad = (n: number, w = 2): string => String(n).padStart(w, '0');\n\n/** The browser's own zone, east-positive. The default when a host supplies none. */\nexport const browserZone: ZoneOffset = (instant) => -instant.getTimezoneOffset();\n\n/** A fixed zone (no daylight saving). Mostly for tests and demos. */\nexport const fixedZone =\n (minutesEast: number): ZoneOffset =>\n () =>\n minutesEast;\n\nexport function kindOf(value: CalendarValue | Date): ValueKind {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) throw new RangeError('Invalid Date');\n return 'instant';\n }\n const local = DATE_RE.test(value) ? 'date' : FLOATING_RE.test(value) ? 'floating' : null;\n if (local) {\n // ★ The regex accepts `2026-02-31`; Date.UTC would silently roll it to March 3rd. Reject\n // any date whose fields do not survive a round trip.\n const m = (FLOATING_RE.exec(value) ?? DATE_RE.exec(value))!;\n const [, y, mo, d, h = '0', mi = '0', sec = '0'] = m;\n const t = new Date(Date.UTC(+y, +mo - 1, +d, +h, +mi, +sec));\n if (t.getUTCFullYear() !== +y || t.getUTCMonth() !== +mo - 1 || t.getUTCDate() !== +d || +h > 23 || +mi > 59 || +sec > 59) {\n throw new RangeError(`Not a real calendar date or time: ${JSON.stringify(value)}`);\n }\n return local;\n }\n if (INSTANT_RE.test(value) && !Number.isNaN(new Date(value).getTime())) return 'instant';\n throw new RangeError(`Not an ISO-8601 date, wall-clock time or instant: ${JSON.stringify(value)}`);\n}\n\n/** An instant as canonical UTC ISO text (`…T…Z`, milliseconds kept). */\nexport function toInstant(value: CalendarValue | Date): CalendarValue {\n if (value instanceof Date) return value.toISOString();\n if (kindOf(value) !== 'instant') throw new RangeError(`Not an instant: ${value}`);\n return new Date(value).toISOString();\n}\n\n/** `Date.UTC` from wall-clock fields — i.e. the wall clock treated AS IF it were UTC. */\nfunction wallMillis(wall: string): number {\n const m = FLOATING_RE.exec(wall) ?? DATE_RE.exec(wall);\n if (!m) throw new RangeError(`Not a date or wall-clock time: ${wall}`);\n const [, y, mo, d, h = '0', mi = '0', s = '0'] = m;\n return Date.UTC(+y, +mo - 1, +d, +h, +mi, +s);\n}\n\nfunction formatWall(ms: number): string {\n const d = new Date(ms);\n return (\n `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}` +\n `T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`\n );\n}\n\n/** The wall-clock reading of an instant in `zone`, as floating text (`YYYY-MM-DDTHH:mm:ss`). */\nexport function toWall(instant: CalendarValue | Date, zone: ZoneOffset): CalendarValue {\n const at = instant instanceof Date ? instant : new Date(toInstant(instant));\n return formatWall(at.getTime() + zone(at) * 60_000);\n}\n\n/**\n * The instant at which `zone`'s clocks read `wall`.\n *\n * The offset depends on the instant being solved for, so this takes the offset in force a day\n * BEFORE and a day AFTER the wall reading (no zone changes offset twice in 48 h) and tries both:\n * - an ordinary time: both give the same instant;\n * - a wall time that exists TWICE (fall-back): both round-trip, and the EARLIER is returned;\n * - a wall time that DOES NOT EXIST (spring-forward gap): neither round-trips, and the\n * pre-transition offset is used, which pushes it forward by the gap (02:30 → 03:30), as JS\n * `Date` does. What Dataverse does with one is UNMEASURED.\n *\n * ★ A reviewer caught the first version returning the LATER occurrence east of UTC (Berlin) and\n * the earlier one west of it (Pacific): the two-pass guess settled on whichever offset the\n * first guess happened to land in. Tests now pin both hemispheres with synthetic zones.\n */\nexport function fromWall(wall: CalendarValue, zone: ZoneOffset): CalendarValue {\n const w = wallMillis(wall);\n const before = zone(new Date(w - 86_400_000));\n const after = zone(new Date(w + 86_400_000));\n const fits = (offset: number): number | null => {\n const t = w - offset * 60_000;\n return zone(new Date(t)) === offset ? t : null;\n };\n const candidates = [fits(before), fits(after)].filter((t): t is number => t !== null);\n const t = candidates.length > 0 ? Math.min(...candidates) : w - before * 60_000;\n return new Date(t).toISOString();\n}\n\n/** The calendar day (`YYYY-MM-DD`) of any value, in `zone` for instants. */\nexport function toDate(value: CalendarValue | Date, zone: ZoneOffset): CalendarValue {\n const k = kindOf(value);\n if (k === 'date') return value as string;\n if (k === 'floating') return (value as string).slice(0, 10);\n return toWall(value, zone).slice(0, 10);\n}\n\n/** Calendar-day arithmetic on a `YYYY-MM-DD` value (no zone involved). */\nexport function addDays(date: CalendarValue, days: number): CalendarValue {\n if (kindOf(date) !== 'date') throw new RangeError(`addDays needs a date, got ${date}`);\n return formatWall(wallMillis(date) + days * 86_400_000).slice(0, 10);\n}\n\n/**\n * The UTC fields of a `Date` as floating text. For reading a `Date` that some library has\n * produced in a \"UTC-as-wall-clock\" convention (FullCalendar with `timeZone: 'UTC'`).\n */\nexport function utcFieldsToWall(d: Date): CalendarValue {\n return formatWall(d.getTime());\n}\n\n/** Normalise floating text to seconds precision (`…T09:00` → `…T09:00:00`). */\nexport function normaliseFloating(value: CalendarValue): CalendarValue {\n return formatWall(wallMillis(value));\n}\n","/**\n * Event colours.\n *\n * ★ Without FullCalendar Premium there are no resource lanes, so \"appointments by location\"\n * is expressed as ONE COLOUR PER GROUP. `groupColor` is deterministic (same key → same colour\n * on every load and every machine), which is what lets a person learn the legend.\n * ★ Every palette entry carries white text at >= 4.5:1 (WCAG AA, normal text) — asserted by a\n * test, not by eye. Host-supplied colours (Dataverse option colours are often pale) get their\n * text colour from `readableTextOn` instead of assuming white; a well-known client control\n * hard-coded white on yellow, which is ~1.4:1.\n */\nexport const GROUP_PALETTE: readonly string[] = [\n '#0f6cbd', // blue\n '#107c10', // green\n '#c50f1f', // red\n '#8764b8', // lavender\n '#ae4b00', // dark orange\n '#03787c', // teal\n '#4f52b2', // indigo\n '#8a5b00', // dark gold\n '#c0077d', // magenta\n '#5c2e91', // purple\n];\n\n/** FNV-1a — tiny, stable across engines, good enough spread for a 10-colour palette. */\nfunction hash(key: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < key.length; i++) {\n h ^= key.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\nexport function groupColor(key: string, palette: readonly string[] = GROUP_PALETTE): string {\n return palette[hash(key) % palette.length];\n}\n\n/** `#rgb` / `#rrggbb` → [r, g, b] 0-255, or null for anything else (named colours, var()). */\nexport function parseHex(color: string): [number, number, number] | null {\n const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color.trim());\n if (!m) return null;\n const h = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];\n return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16)) as [number, number, number];\n}\n\nfunction luminance([r, g, b]: [number, number, number]): number {\n const lin = (c: number): number => {\n const s = c / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);\n}\n\n/** WCAG contrast ratio between two hex colours, or null if either cannot be parsed. */\nexport function contrastRatio(a: string, b: string): number | null {\n const pa = parseHex(a);\n const pb = parseHex(b);\n if (!pa || !pb) return null;\n const [hi, lo] = [luminance(pa), luminance(pb)].sort((x, y) => y - x);\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/**\n * Black or white text, whichever reads better on `background`. Unparseable backgrounds\n * (a CSS variable, a named colour) get `undefined`, i.e. the theme's own event text colour.\n */\nexport function readableTextOn(background: string): '#000000' | '#ffffff' | undefined {\n const onWhite = contrastRatio(background, '#ffffff');\n const onBlack = contrastRatio(background, '#000000');\n if (onWhite === null || onBlack === null) return undefined;\n return onWhite >= onBlack ? '#ffffff' : '#000000';\n}\n","import { readableTextOn } from '../core/colors';\nimport { addDays, fromWall, kindOf, normaliseFloating, toInstant, toWall, utcFieldsToWall } from '../core/datetime';\nimport type {\n CalendarEvent,\n CalendarEventChange,\n CalendarValue,\n SlotSelection,\n ValueKind,\n VisibleRange,\n ZoneOffset,\n} from '../core/types';\n\n/**\n * The translation between the kit's contract and FullCalendar's. Pure (types only from here),\n * so every rule is unit-testable without rendering a calendar.\n *\n * ★★★ THE ZONE STRATEGY. FullCalendar is always run with `timeZone: 'UTC'` and fed WALL-CLOCK\n * values already shifted into the calendar's zone. Its `Date`s then carry the wall clock in\n * their UTC fields, and we shift back on the way out.\n *\n * Why not give FullCalendar the zone? Because a PCF host does not expose one. It exposes\n * `getTimeZoneOffsetMinutes(date)` — an offset per instant, no IANA name — and MEASURED on an\n * org, a user set to Tokyo in a Pacific browser saw an event FullCalendar placed on Monday 9:00\n * that the grid beside it said was Tuesday 1:00. The browser's zone is simply the wrong zone.\n * Shifting per instant with that function is also daylight-saving correct, which a fixed\n * `'+09:00'`-style offset would not be.\n */\n\nexport interface FcEventInput {\n id: string;\n title: string;\n start: string;\n end?: string;\n allDay: boolean;\n color?: string;\n textColor?: string;\n editable?: boolean;\n durationEditable?: boolean;\n /**\n * `kind` is how FullCalendar holds the values (`date` when shown all-day); `originalKind` is\n * what the host SENT, and what a change is converted back into — the \"an event keeps its kind\"\n * contract. They differ only for an instant/floating start forced all-day with `allDay: true`.\n */\n extendedProps: { source: CalendarEvent; kind: ValueKind; originalKind: ValueKind };\n}\n\nfunction toFcValue(value: CalendarValue | Date, zone: ZoneOffset): string {\n const k = kindOf(value);\n if (k === 'instant') return toWall(value, zone);\n if (k === 'floating') return normaliseFloating(value as string);\n return value as string;\n}\n\n/** The exclusive all-day end for any end value: a timed end that is not midnight rounds UP a day. */\nfunction exclusiveAllDayEnd(end: CalendarValue | Date, zone: ZoneOffset): string {\n const wall = toFcValue(end, zone);\n if (wall.length === 10) return wall;\n return wall.slice(11, 19) === '00:00:00' ? wall.slice(0, 10) : addDays(wall.slice(0, 10), 1);\n}\n\nexport function toFcEvent(event: CalendarEvent, zone: ZoneOffset): FcEventInput {\n const originalKind = kindOf(event.start);\n const allDay = event.allDay ?? originalKind === 'date';\n const start = allDay ? toFcValue(event.start, zone).slice(0, 10) : toFcValue(event.start, zone);\n const out: FcEventInput = {\n id: event.id,\n title: event.title,\n start,\n allDay,\n extendedProps: { source: event, kind: allDay ? 'date' : originalKind, originalKind },\n };\n // ★★ Optional keys are OMITTED, never set to `undefined`. FullCalendar treats a PRESENT key as a\n // value: `editable: undefined` resolves to `startEditable === false` (read back through\n // FullCalendar's API — see fullcalendar-contract.test.tsx), which locked EVERY event in an\n // editable calendar. Same family as the `slotMinTime={undefined}` crash in CalendarView.\n if (event.end !== undefined) out.end = allDay ? exclusiveAllDayEnd(event.end, zone) : toFcValue(event.end, zone);\n if (event.color) {\n out.color = event.color;\n const text = readableTextOn(event.color);\n if (text) out.textColor = text;\n }\n if (event.readOnly) out.editable = false;\n // ★ An event with no END is not resizable: a resize would invent an end the host has nowhere\n // to store (a map with no end column), and the calendar would keep showing a length that\n // was never saved. Moving it is still fine.\n else if (event.end === undefined) out.durationEditable = false;\n return out;\n}\n\n/** A FullCalendar `Date` (UTC fields = wall clock) → a value of `kind`. */\nexport function fromFcDate(d: Date, kind: ValueKind, zone: ZoneOffset): CalendarValue {\n const wall = utcFieldsToWall(d);\n if (kind === 'date') return wall.slice(0, 10);\n if (kind === 'floating') return wall;\n return fromWall(wall, zone);\n}\n\nexport interface FcChangedEventLike {\n start: Date | null;\n end: Date | null;\n allDay: boolean;\n extendedProps: Record<string, unknown>;\n}\n\n/** A calendar value of `kind` → the same moment in `target` kind (all-day midnight for dates). */\nfunction asKind(value: CalendarValue, from: ValueKind, target: ValueKind, zone: ZoneOffset): CalendarValue {\n if (from === target || from !== 'date') return value;\n return target === 'instant' ? fromWall(`${value}T00:00:00`, zone) : `${value}T00:00:00`;\n}\n\n/** The host's original value, canonicalised, for fields a change did not touch. */\nfunction canonical(value: CalendarValue | Date): CalendarValue {\n const k = kindOf(value);\n return k === 'instant' ? toInstant(value) : k === 'floating' ? normaliseFloating(value as string) : (value as string);\n}\n\n/** A dropped/resized FullCalendar event → a change in the ORIGINAL event's kind. */\nexport function toChange(\n fcEvent: FcChangedEventLike,\n kind: CalendarEventChange['kind'],\n zone: ZoneOffset\n): CalendarEventChange {\n const { source, kind: valueKind, originalKind } = fcEvent.extendedProps as FcEventInput['extendedProps'];\n if (!fcEvent.start) throw new Error(`FullCalendar returned no start for event ${source.id}`);\n const back = (d: Date): CalendarValue => asKind(fromFcDate(d, valueKind, zone), valueKind, originalKind ?? valueKind, zone);\n return {\n event: source,\n kind,\n // ★ A RESIZE never moves the start. Recomputing it from FullCalendar's wall-clock Date is\n // lossy on the fall-back day (the second 01:30 reads back as the first), and toWebApiPatch\n // writes `start` unconditionally — so resizing an end silently moved the start an hour.\n start: kind === 'resize' ? canonical(source.start) : back(fcEvent.start),\n end: fcEvent.end ? back(fcEvent.end) : undefined,\n allDay: fcEvent.allDay,\n };\n}\n\nexport function toSelection(start: Date, end: Date, allDay: boolean, zone: ZoneOffset): SlotSelection {\n const kind: ValueKind = allDay ? 'date' : 'instant';\n return { start: fromFcDate(start, kind, zone), end: fromFcDate(end, kind, zone), allDay };\n}\n\nexport function toVisibleRange(start: Date, end: Date, view: string, zone: ZoneOffset): VisibleRange {\n return {\n start: fromFcDate(start, 'instant', zone),\n end: fromFcDate(end, 'instant', zone),\n view,\n startDate: utcFieldsToWall(start).slice(0, 10),\n endDate: utcFieldsToWall(end).slice(0, 10),\n };\n}\n\n/** \"Now\" in the calendar's zone, for FullCalendar's `now` option (drives today + now-line). */\nexport const nowWall = (zone: ZoneOffset): string => toWall(new Date(), zone);\n\n/**\n * ★ A drag may not change an event's KIND. Dropping a timed appointment on the all-day row\n * would have to invent a date-only value for a date-time column (or the reverse), and there\n * is no right answer to invent. FullCalendar allows it by default; this forbids it.\n */\nexport const sameAllDayness = (spanAllDay: boolean, eventAllDay: boolean): boolean => spanAllDay === eventAllDay;\n\n/* ── Optimistic overrides ────────────────────────────────────────────────────────────────────\n * FullCalendar moves an event the moment it is dropped. The host's `events` prop still has the\n * OLD position until its save resolves AND it re-renders with fresh data — and a PCF host\n * re-renders on anything (resize, focus), so without this the event would snap back mid-save\n * and jump forward again later. An override holds the new position until the props agree.\n */\nexport interface PendingMove {\n start: CalendarValue;\n end: CalendarValue | undefined;\n allDay: boolean;\n /** Increments per change; only the newest change for an event may settle or revert it. */\n token: number;\n /**\n * Set when the save RESOLVED: the `events` prop at that moment. The move is dropped on the\n * first `events` that is a different array — whether or not it matches. Without this a save\n * whose value never comes back verbatim (no end column mapped, a plugin adjusting the time)\n * kept its override forever, showing a position that was never stored.\n */\n savedWith?: readonly CalendarEvent[];\n}\n\nconst same = (a: CalendarValue | Date | undefined, b: CalendarValue | undefined): boolean => {\n if (a === undefined || b === undefined) return a === b;\n const ka = kindOf(a);\n if (ka !== kindOf(b)) return false;\n if (ka === 'instant') return new Date(a).getTime() === new Date(b).getTime();\n return ka === 'floating' ? normaliseFloating(a as string) === normaliseFloating(b) : a === b;\n};\n\n/** Events with pending moves applied. */\nexport function applyPending(events: readonly CalendarEvent[], pending: ReadonlyMap<string, PendingMove>): CalendarEvent[] {\n if (pending.size === 0) return events as CalendarEvent[];\n return events.map((e) => {\n const p = pending.get(e.id);\n return p ? { ...e, start: p.start, end: p.end, allDay: p.allDay } : e;\n });\n}\n\n/**\n * Ids whose pending move can be dropped: the props now agree, the event vanished (deleted\n * elsewhere — no ghost), or the save resolved and the host has since re-rendered.\n */\nexport function settledIds(events: readonly CalendarEvent[], pending: ReadonlyMap<string, PendingMove>): string[] {\n const byId = new Map(events.map((e) => [e.id, e]));\n const done: string[] = [];\n for (const [id, p] of pending) {\n const e = byId.get(id);\n if (!e || (same(e.start, p.start) && same(e.end, p.end)) || (p.savedWith !== undefined && p.savedWith !== events)) done.push(id);\n }\n return done;\n}\n","import * as React from 'react';\nimport type { CalendarEvent, CalendarEventChange, ZoneOffset } from '../core/types';\nimport { settledIds, toChange, type FcChangedEventLike, type PendingMove } from './fcBridge';\n\n/** The part of FullCalendar's EventChangeInfo this hook uses — small enough to fake in a test. */\nexport interface ChangeInfoLike {\n event: FcChangedEventLike;\n revert(): void;\n}\n\nexport type ChangeHandler = (change: CalendarEventChange) => void | Promise<void>;\n\nfunction describe(error: unknown): string {\n const m = (error as { message?: unknown } | null)?.message;\n return typeof m === 'string' && m ? m : String(error);\n}\n\n/**\n * The optimistic-save state machine behind drag and resize, separated from the calendar so\n * every transition is testable without driving a real drag (which synthetic input could not do\n * against FullCalendar v7 — see CLAUDE.md).\n *\n * drop ──▶ pending(token) ──resolve──▶ saved (held until the host's next `events`)\n * └─reject/throw─▶ revert + banner (only if still the NEWEST change)\n */\nexport function useOptimisticChanges(\n events: readonly CalendarEvent[],\n onEventChange: ChangeHandler | undefined,\n onChangeError: ((error: unknown, change: CalendarEventChange) => void) | undefined,\n zoneRef: React.MutableRefObject<ZoneOffset>\n): {\n pending: ReadonlyMap<string, PendingMove>;\n error: string | null;\n dismissError: () => void;\n handleChange: (kind: CalendarEventChange['kind']) => (info: ChangeInfoLike) => void;\n} {\n const [pending, setPending] = React.useState<ReadonlyMap<string, PendingMove>>(() => new Map());\n const [error, setError] = React.useState<string | null>(null);\n const eventsRef = React.useRef(events);\n eventsRef.current = events;\n // ★ The newest token per event, held in a REF so a settling promise can ask \"am I still the\n // current change?\" synchronously. Asking inside a setState updater would be a side effect in\n // an updater, which React may run twice or late.\n const latest = React.useRef(new Map<string, number>());\n const seq = React.useRef(0);\n const mounted = React.useRef(true);\n React.useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n React.useEffect(() => {\n const done = settledIds(events, pending);\n if (done.length === 0) return;\n for (const id of done) if (latest.current.get(id) === pending.get(id)?.token) latest.current.delete(id);\n setPending((prev) => {\n const next = new Map(prev);\n for (const id of done) next.delete(id);\n return next;\n });\n }, [events, pending]);\n\n const handleChange = React.useCallback(\n (kind: CalendarEventChange['kind']) =>\n (info: ChangeInfoLike): void => {\n if (!onEventChange) {\n info.revert();\n return;\n }\n let change: CalendarEventChange;\n try {\n change = toChange(info.event, kind, zoneRef.current);\n } catch (e) {\n info.revert();\n setError(describe(e));\n return;\n }\n setError(null);\n const id = change.event.id;\n const token = ++seq.current;\n\n const fail = (err: unknown): void => {\n const current = latest.current.get(id) === token;\n if (current) latest.current.delete(id);\n // ★ onChangeError BEFORE revert, so a revert that throws (calendar already destroyed)\n // cannot swallow the host's error callback.\n onChangeError?.(err, change);\n if (!mounted.current) return;\n setError(`Could not save \"${change.event.title}\": ${describe(err)}`);\n if (!current) return; // a newer move of this event owns its position now\n setPending((prev) => {\n if (!prev.has(id)) return prev;\n const next = new Map(prev);\n next.delete(id);\n return next;\n });\n try {\n info.revert();\n } catch {\n /* the calendar was torn down under us; nothing left to put back */\n }\n };\n\n // ★ Registered BEFORE the handler runs: a synchronous throw calls `fail`, which asks \"am I\n // the current change?\" — registering after the call answered \"no\" and skipped the\n // revert (caught by the sync-throw test).\n latest.current.set(id, token);\n let result: void | Promise<void>;\n try {\n result = onEventChange(change);\n } catch (err) {\n // A handler that throws synchronously is a failed save, not an unhandled error that\n // leaves the event where it was dropped.\n fail(err);\n return;\n }\n if (!result || typeof (result as Promise<void>).then !== 'function') {\n // Fire-and-forget handler: nothing to hold; the host owns the position from here.\n if (latest.current.get(id) === token) latest.current.delete(id);\n return;\n }\n\n setPending((prev) => new Map(prev).set(id, { start: change.start, end: change.end, allDay: change.allDay, token }));\n (result as Promise<void>).then(\n () => {\n if (!mounted.current || latest.current.get(id) !== token) return;\n const savedWith = eventsRef.current;\n setPending((prev) => {\n const p = prev.get(id);\n if (!p || p.token !== token) return prev;\n return new Map(prev).set(id, { ...p, savedWith });\n });\n },\n fail\n );\n },\n [onEventChange, onChangeError, zoneRef]\n );\n\n return { pending, error, dismissError: React.useCallback(() => setError(null), []), handleChange };\n}\n","import type * as React from 'react';\n\n/**\n * The colours a calendar can be themed with, as plain CSS values (hex, rgb(), or `var(--x)`).\n * Each maps onto one of FullCalendar v7's classic-theme custom properties, so theming is just\n * setting variables on the calendar's own container — no stylesheet override, nothing global.\n *\n * ★ Scoped to the container on purpose: FullCalendar's palette declares its defaults on `:root`,\n * and a PCF control shares `:root` with the whole form. Two calendars on one form each set\n * their own, and neither touches the host.\n */\nexport interface CalendarTheme {\n primary?: string;\n primaryForeground?: string;\n background?: string;\n foreground?: string;\n mutedForeground?: string;\n faint?: string;\n muted?: string;\n strong?: string;\n border?: string;\n strongBorder?: string;\n today?: string;\n highlight?: string;\n now?: string;\n button?: string;\n buttonForeground?: string;\n buttonBorder?: string;\n buttonStrong?: string;\n buttonStrongBorder?: string;\n fontFamily?: string;\n fontSize?: string;\n /** Rendered as a banner above the calendar when a save fails. */\n error?: string;\n}\n\nconst VAR: Record<Exclude<keyof CalendarTheme, 'fontFamily' | 'fontSize' | 'error'>, string> = {\n primary: '--fc-classic-primary',\n primaryForeground: '--fc-classic-primary-foreground',\n background: '--fc-classic-background',\n foreground: '--fc-classic-foreground',\n mutedForeground: '--fc-classic-muted-foreground',\n faint: '--fc-classic-faint',\n muted: '--fc-classic-muted',\n strong: '--fc-classic-strong',\n border: '--fc-classic-border',\n strongBorder: '--fc-classic-strong-border',\n today: '--fc-classic-today',\n highlight: '--fc-classic-highlight',\n now: '--fc-classic-now',\n button: '--fc-classic-button',\n buttonForeground: '--fc-classic-button-foreground',\n buttonBorder: '--fc-classic-button-border',\n buttonStrong: '--fc-classic-button-strong',\n buttonStrongBorder: '--fc-classic-button-strong-border',\n};\n\n/** The container style that applies `theme`. Unset keys keep FullCalendar's defaults. */\nexport function themeStyle(theme: CalendarTheme | undefined): React.CSSProperties {\n const style: Record<string, string> = {};\n if (!theme) return style;\n for (const [key, cssVar] of Object.entries(VAR)) {\n const v = theme[key as keyof typeof VAR];\n if (v) style[cssVar] = v;\n }\n if (theme.fontFamily) style.fontFamily = theme.fontFamily;\n if (theme.fontSize) style.fontSize = theme.fontSize;\n if (theme.background) style.background = theme.background;\n if (theme.foreground) style.color = theme.foreground;\n return style as React.CSSProperties;\n}\n","import * as React from 'react';\nimport type { CalendarDataSource, CalendarEvent, CalendarEventChange, SlotSelection, VisibleRange } from '../core/types';\n\nexport interface CalendarSourceBinding {\n events: readonly CalendarEvent[];\n loading: boolean;\n /** The last load failure, or null. A failed load keeps the previous events on screen. */\n error: string | null;\n /** Re-run the current range (e.g. after booking a slot elsewhere on the page). */\n reload: () => void;\n /** Spread onto `<CalendarView>` / `<FluentCalendar>`. */\n props: {\n events: readonly CalendarEvent[];\n loading: boolean;\n editable: boolean;\n selectable: boolean;\n onRangeChange: (range: VisibleRange) => void;\n onEventChange?: (change: CalendarEventChange) => Promise<void>;\n onEventClick?: (event: CalendarEvent) => void;\n onSelectRange?: (slot: SlotSelection) => void;\n };\n}\n\nfunction describe(error: unknown): string {\n const m = (error as { message?: unknown } | null)?.message;\n return typeof m === 'string' && m ? m : String(error);\n}\n\nconst isAbort = (e: unknown): boolean => (e as { name?: unknown } | null)?.name === 'AbortError';\n\n/**\n * Drive a calendar from a PULLED `CalendarDataSource` (code app, Power Pages): load on every range\n * change, save a drag through the source, then reload so the calendar shows what the server kept.\n *\n * ★ Only the NEWEST load may land. Ranges change faster than networks answer (click \"next\" three\n * times), so an older response arriving last must not overwrite the newer week. Each load aborts\n * its predecessor AND is sequence-checked — a source that ignores `signal` is still safe.\n * ★ A save that rejects is RE-THROWN, so the calendar's optimistic layer reverts the event and shows\n * the message; swallowing it here would leave the event where it was dropped, unsaved.\n */\nexport function useCalendarSource(source: CalendarDataSource | null | undefined): CalendarSourceBinding {\n const [events, setEvents] = React.useState<readonly CalendarEvent[]>([]);\n const [loading, setLoading] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n const range = React.useRef<VisibleRange | null>(null);\n const seq = React.useRef(0);\n const inflight = React.useRef<AbortController | null>(null);\n\n const load = React.useCallback(async (): Promise<void> => {\n const r = range.current;\n if (!source || !r) return;\n inflight.current?.abort();\n const controller = new AbortController();\n inflight.current = controller;\n const mine = ++seq.current;\n setLoading(true);\n try {\n const next = await source.load(r, controller.signal);\n if (mine !== seq.current) return;\n setEvents(next);\n setError(null);\n } catch (e) {\n if (mine !== seq.current || isAbort(e)) return;\n setError(describe(e));\n } finally {\n if (mine === seq.current) setLoading(false);\n }\n }, [source]);\n\n // A new source (different filter, different table) re-queries the range already on screen.\n React.useEffect(() => {\n setEvents([]);\n void load();\n }, [load]);\n\n React.useEffect(\n () => () => {\n seq.current++;\n inflight.current?.abort();\n },\n []\n );\n\n const onRangeChange = React.useCallback(\n (r: VisibleRange) => {\n const prev = range.current;\n range.current = r;\n if (prev && prev.start === r.start && prev.end === r.end) return;\n void load();\n },\n [load]\n );\n\n const reload = React.useCallback(() => void load(), [load]);\n\n const onEventChange = React.useMemo(\n () =>\n source?.capabilities.update && source.update\n ? async (change: CalendarEventChange): Promise<void> => {\n await source.update!(change);\n await load();\n }\n : undefined,\n [source, load]\n );\n\n const open = React.useMemo(\n () => (source?.capabilities.open && source.open ? (e: CalendarEvent) => source.open!(e) : undefined),\n [source]\n );\n const onSelectRange = React.useMemo(\n () =>\n source?.capabilities.create && source.create\n ? (slot: SlotSelection) => {\n void Promise.resolve()\n .then(() => source.create!(slot))\n .then(\n () => load(),\n (e: unknown) => setError(describe(e))\n );\n }\n : undefined,\n [source, load]\n );\n\n return {\n events,\n loading,\n error,\n reload,\n props: {\n events,\n loading,\n editable: !!onEventChange,\n selectable: !!onSelectRange,\n onRangeChange,\n ...(onEventChange ? { onEventChange } : {}),\n ...(open ? { onEventClick: open } : {}),\n ...(onSelectRange ? { onSelectRange } : {}),\n },\n };\n}\n"],"mappings":"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","names":["React","t","FullCalendar","dayGridPlugin","timeGridPlugin","listPlugin","interactionPlugin","classicThemePlugin","React","describe"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/view/CalendarView.tsx","../src/core/datetime.ts","../src/core/colors.ts","../src/view/fcBridge.ts","../src/view/useOptimisticChanges.ts","../src/view/theme.ts","../src/view/useCalendarSource.ts"],"sourcesContent":["/**\n * `@dataverse-kit/calendar-kit` — a calendar fed plain events. Fluent-free: this is the entry a\n * Power Pages site (Fluent v8, or none) uses. For Fluent v9 styling import from `./v9`; for the\n * contract and date rules alone, `./core`; for Dataverse, `./dataverse` and `./adapters/*`.\n */\nexport { CalendarView } from './view/CalendarView';\nexport type { CalendarViewName, CalendarViewProps } from './view/CalendarView';\nexport { themeStyle } from './view/theme';\nexport type { CalendarTheme } from './view/theme';\nexport * from './core';\nexport { useCalendarSource } from './view/useCalendarSource';\nexport type { CalendarSourceBinding } from './view/useCalendarSource';\n","import * as React from 'react';\nimport FullCalendar from '@fullcalendar/react';\nimport dayGridPlugin from '@fullcalendar/react/daygrid';\nimport timeGridPlugin from '@fullcalendar/react/timegrid';\nimport listPlugin from '@fullcalendar/react/list';\nimport interactionPlugin from '@fullcalendar/react/interaction';\nimport classicThemePlugin from '@fullcalendar/react/themes/classic';\nimport type {\n DateSelectInfo,\n DatesSetInfo,\n EventChangeInfo,\n EventClickInfo,\n} from '@fullcalendar/react';\n\nimport '@fullcalendar/react/skeleton.css';\nimport '@fullcalendar/react/themes/classic/theme.css';\nimport '@fullcalendar/react/themes/classic/palette.css';\n\nimport { browserZone } from '../core/datetime';\nimport type { BusinessHours, CalendarEvent, CalendarEventChange, SlotSelection, VisibleRange, ZoneOffset } from '../core/types';\nimport { applyPending, nowWall, sameAllDayness, toFcEvent, toSelection, toVisibleRange } from './fcBridge';\nimport { useOptimisticChanges, type ChangeInfoLike } from './useOptimisticChanges';\nimport { themeStyle, type CalendarTheme } from './theme';\n\n/** The views a host can ask for, by plain name. */\nexport type CalendarViewName = 'month' | 'week' | 'workWeek' | 'day' | 'list';\n\nconst FC_VIEW: Record<CalendarViewName, string> = {\n month: 'dayGridMonth',\n week: 'timeGridWeek',\n workWeek: 'timeGridWorkWeek',\n day: 'timeGridDay',\n list: 'listWeek',\n};\n\nconst VIEW_LABEL: Record<CalendarViewName, string> = {\n month: 'Month',\n week: 'Week',\n workWeek: 'Work week',\n day: 'Day',\n list: 'List',\n};\n\nexport interface CalendarViewProps {\n /** Plain event objects. See `CalendarEvent` for how a value's shape sets its kind. */\n events: readonly CalendarEvent[];\n /** The zone to show instants in. Default: the browser's. A PCF host passes the USER's. */\n zone?: ZoneOffset;\n /** Starting view. Default `week`. */\n view?: CalendarViewName;\n /** View buttons offered, in order. Default: month, week, day, list. */\n views?: readonly CalendarViewName[];\n /** `YYYY-MM-DD` to open on. Default today (in `zone`). */\n initialDate?: string;\n /** Allow drag and resize. Default false — read-only is the safe default. */\n editable?: boolean;\n /** Allow selecting an empty range (→ `onSelectRange`). Default: true when a handler is given. */\n selectable?: boolean;\n businessHours?: readonly BusinessHours[];\n /** 0 = Sunday. */\n firstDay?: number;\n /** `HH:mm:ss` bounds of the time grid. */\n slotMinTime?: string;\n slotMaxTime?: string;\n /** Where the time grid first scrolls to. Default `08:00:00`. */\n scrollTime?: string;\n /** Any CSS height, `'auto'`, or a number of pixels. Default `'auto'`. */\n height?: number | string;\n theme?: CalendarTheme;\n /** Shown while data for a new range loads. */\n loading?: boolean;\n className?: string;\n /**\n * Classes for FullCalendar's own parts. ★ v7 HASHES its class names (`fc-classic-1El`), so a\n * descendant selector like `.fc-button` matches NOTHING — measured: 0 of 7 buttons. These go\n * through FullCalendar's `buttonClass` / `headerToolbarClass` options instead.\n */\n classNames?: { button?: string; toolbar?: string };\n /** Accessible name for the calendar region. */\n 'aria-label'?: string;\n\n onEventClick?: (event: CalendarEvent) => void;\n onSelectRange?: (selection: SlotSelection) => void;\n /**\n * A move or resize. Return a promise to make it OPTIMISTIC: the event stays where it was\n * dropped while the promise runs, and goes back — with a message — if it rejects.\n */\n onEventChange?: (change: CalendarEventChange) => void | Promise<void>;\n onRangeChange?: (range: VisibleRange) => void;\n /** Called after a failed change has been reverted. The banner is shown either way. */\n onChangeError?: (error: unknown, change: CalendarEventChange) => void;\n}\n\n/** FullCalendar's change info → the hook's minimal shape (what the hook reads, and nothing else). */\nconst asChangeInfo = (info: EventChangeInfo): ChangeInfoLike => ({\n event: {\n start: info.event.start as Date | null,\n end: info.event.end as Date | null,\n allDay: info.event.allDay,\n extendedProps: info.event.extendedProps,\n },\n revert: () => info.revert(),\n});\n\nconst bannerStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '6px 10px',\n marginBottom: 6,\n borderRadius: 4,\n border: '1px solid currentColor',\n};\n\n/**\n * FullCalendar v7 (MIT views only), fed plain events. Knows nothing about Dataverse and nothing\n * about Fluent — this entry is what a Power Pages site uses. `FluentCalendar` (`./v9`) wraps it\n * with Fluent tokens.\n *\n * ★ Nothing here portals and nothing uses tabster, so it cannot join the Fluent/tabster\n * singleton race that has taken down PCF controls on model-driven forms. The error banner is\n * in-tree for the same reason: a Toast or Dialog would portal to `document.body`.\n */\nexport function CalendarView(props: CalendarViewProps): React.ReactElement {\n const zone = props.zone ?? browserZone;\n const zoneRef = React.useRef(zone);\n zoneRef.current = zone;\n\n const { pending, error, dismissError, handleChange } = useOptimisticChanges(\n props.events,\n props.onEventChange,\n props.onChangeError,\n zoneRef\n );\n\n const fcEvents = React.useMemo(\n () => applyPending(props.events, pending).map((e) => toFcEvent(e, zone)),\n [props.events, pending, zone]\n );\n\n const views = props.views ?? (['month', 'week', 'day', 'list'] as const);\n\n /**\n * ★★ Options the host did not set are OMITTED, never passed as `undefined`. FullCalendar's\n * option refiners treat a present-but-undefined key as a value: `slotMinTime={undefined}`\n * replaced the default and the time-grid views threw \"Cannot read properties of undefined\n * (reading 'years')\" on first render. The list view never reads it, so a list-only test\n * was green — found by rendering the WEEK view.\n */\n const optional: Record<string, unknown> = {};\n if (props.firstDay !== undefined) optional.firstDay = props.firstDay;\n if (props.slotMinTime !== undefined) optional.slotMinTime = props.slotMinTime;\n if (props.slotMaxTime !== undefined) optional.slotMaxTime = props.slotMaxTime;\n if (props.initialDate !== undefined) optional.initialDate = props.initialDate;\n if (props.classNames?.button) optional.buttonClass = props.classNames.button;\n if (props.classNames?.toolbar) optional.headerToolbarClass = props.classNames.toolbar;\n if (props.businessHours) optional.businessHours = props.businessHours.map((b) => ({ ...b, daysOfWeek: [...b.daysOfWeek] }));\n const selectable = props.selectable ?? !!props.onSelectRange;\n\n return (\n <div\n className={props.className}\n style={themeStyle(props.theme)}\n role=\"region\"\n aria-label={props['aria-label'] ?? 'Calendar'}\n aria-busy={props.loading || undefined}\n data-calendar-kit=\"\"\n >\n {error && (\n <div role=\"alert\" style={{ ...bannerStyle, color: props.theme?.error ?? '#a4262c' }}>\n <span style={{ flex: 1 }}>{error}</span>\n <button type=\"button\" onClick={dismissError} aria-label=\"Dismiss\" style={{ font: 'inherit' }}>\n ×\n </button>\n </div>\n )}\n <FullCalendar\n plugins={[dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin, classicThemePlugin]}\n timeZone=\"UTC\"\n now={() => nowWall(zoneRef.current)}\n initialView={FC_VIEW[props.view ?? 'week']}\n views={{ timeGridWorkWeek: { type: 'timeGridWeek', hiddenDays: [0, 6] } }}\n headerToolbar={{\n start: 'today prev,next',\n center: 'title',\n end: views.map((v) => FC_VIEW[v]).join(','),\n }}\n // ★ v7 renamed v6's `buttonText` to `buttons: { name: { text } }`, keyed by VIEW name.\n buttons={{\n today: { text: 'Today' },\n ...Object.fromEntries(views.map((v) => [FC_VIEW[v], { text: VIEW_LABEL[v] }])),\n }}\n height={props.height ?? 'auto'}\n events={fcEvents}\n editable={!!props.editable && !!props.onEventChange}\n selectable={selectable}\n selectMirror\n nowIndicator\n dayMaxEvents\n {...optional}\n scrollTime={props.scrollTime ?? '08:00:00'}\n eventAllow={(span, moving) => (moving ? sameAllDayness(span.allDay, moving.allDay) : true)}\n datesSet={(info: DatesSetInfo) =>\n props.onRangeChange?.(toVisibleRange(info.start as Date, info.end as Date, info.view.type, zoneRef.current))\n }\n eventClick={(info: EventClickInfo) => {\n info.jsEvent.preventDefault();\n const source = (info.event.extendedProps as { source?: CalendarEvent }).source;\n if (source) props.onEventClick?.(source);\n }}\n select={(info: DateSelectInfo) => {\n props.onSelectRange?.(toSelection(info.start as Date, info.end as Date, info.allDay, zoneRef.current));\n info.view.calendar.unselect();\n }}\n eventDrop={(info: EventChangeInfo) => handleChange('move')(asChangeInfo(info))}\n eventResize={(info: EventChangeInfo) => handleChange('resize')(asChangeInfo(info))}\n />\n </div>\n );\n}\n","import type { CalendarValue, ValueKind, ZoneOffset } from './types';\n\n/**\n * Value-kind detection and zone conversion.\n *\n * ★★ Nothing in this file may read the PROCESS time zone. Every `Date` is built and read through\n * its UTC fields, so the same input gives the same output on a laptop in Auckland and a CI\n * runner in UTC. `npm test` runs the suite under three TZ values to prove it — a single-zone\n * run cannot see a leak, because the developer's zone is the one the code was written in.\n */\n\nconst DATE_RE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst FLOATING_RE = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d{1,9})?)?$/;\nconst INSTANT_RE = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?(?:Z|[+-]\\d{2}:?\\d{2})$/i;\n\nconst pad = (n: number, w = 2): string => String(n).padStart(w, '0');\n\n/** The browser's own zone, east-positive. The default when a host supplies none. */\nexport const browserZone: ZoneOffset = (instant) => -instant.getTimezoneOffset();\n\n/** A fixed zone (no daylight saving). Mostly for tests and demos. */\nexport const fixedZone =\n (minutesEast: number): ZoneOffset =>\n () =>\n minutesEast;\n\nexport function kindOf(value: CalendarValue | Date): ValueKind {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) throw new RangeError('Invalid Date');\n return 'instant';\n }\n const local = DATE_RE.test(value) ? 'date' : FLOATING_RE.test(value) ? 'floating' : null;\n if (local) {\n // ★ The regex accepts `2026-02-31`; Date.UTC would silently roll it to March 3rd. Reject\n // any date whose fields do not survive a round trip.\n const m = (FLOATING_RE.exec(value) ?? DATE_RE.exec(value))!;\n const [, y, mo, d, h = '0', mi = '0', sec = '0'] = m;\n const t = new Date(Date.UTC(+y, +mo - 1, +d, +h, +mi, +sec));\n if (t.getUTCFullYear() !== +y || t.getUTCMonth() !== +mo - 1 || t.getUTCDate() !== +d || +h > 23 || +mi > 59 || +sec > 59) {\n throw new RangeError(`Not a real calendar date or time: ${JSON.stringify(value)}`);\n }\n return local;\n }\n if (INSTANT_RE.test(value) && !Number.isNaN(new Date(value).getTime())) return 'instant';\n throw new RangeError(`Not an ISO-8601 date, wall-clock time or instant: ${JSON.stringify(value)}`);\n}\n\n/** An instant as canonical UTC ISO text (`…T…Z`, milliseconds kept). */\nexport function toInstant(value: CalendarValue | Date): CalendarValue {\n if (value instanceof Date) return value.toISOString();\n if (kindOf(value) !== 'instant') throw new RangeError(`Not an instant: ${value}`);\n return new Date(value).toISOString();\n}\n\n/** `Date.UTC` from wall-clock fields — i.e. the wall clock treated AS IF it were UTC. */\nfunction wallMillis(wall: string): number {\n const m = FLOATING_RE.exec(wall) ?? DATE_RE.exec(wall);\n if (!m) throw new RangeError(`Not a date or wall-clock time: ${wall}`);\n const [, y, mo, d, h = '0', mi = '0', s = '0'] = m;\n return Date.UTC(+y, +mo - 1, +d, +h, +mi, +s);\n}\n\nfunction formatWall(ms: number): string {\n const d = new Date(ms);\n return (\n `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}` +\n `T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`\n );\n}\n\n/** The wall-clock reading of an instant in `zone`, as floating text (`YYYY-MM-DDTHH:mm:ss`). */\nexport function toWall(instant: CalendarValue | Date, zone: ZoneOffset): CalendarValue {\n const at = instant instanceof Date ? instant : new Date(toInstant(instant));\n return formatWall(at.getTime() + zone(at) * 60_000);\n}\n\n/**\n * The instant at which `zone`'s clocks read `wall`.\n *\n * The offset depends on the instant being solved for, so this takes the offset in force a day\n * BEFORE and a day AFTER the wall reading (no zone changes offset twice in 48 h) and tries both:\n * - an ordinary time: both give the same instant;\n * - a wall time that exists TWICE (fall-back): both round-trip, and the EARLIER is returned;\n * - a wall time that DOES NOT EXIST (spring-forward gap): neither round-trips, and the\n * pre-transition offset is used, which pushes it forward by the gap (02:30 → 03:30), as JS\n * `Date` does. What Dataverse does with one is UNMEASURED.\n *\n * ★ A reviewer caught the first version returning the LATER occurrence east of UTC (Berlin) and\n * the earlier one west of it (Pacific): the two-pass guess settled on whichever offset the\n * first guess happened to land in. Tests now pin both hemispheres with synthetic zones.\n */\nexport function fromWall(wall: CalendarValue, zone: ZoneOffset): CalendarValue {\n const w = wallMillis(wall);\n const before = zone(new Date(w - 86_400_000));\n const after = zone(new Date(w + 86_400_000));\n const fits = (offset: number): number | null => {\n const t = w - offset * 60_000;\n return zone(new Date(t)) === offset ? t : null;\n };\n const candidates = [fits(before), fits(after)].filter((t): t is number => t !== null);\n const t = candidates.length > 0 ? Math.min(...candidates) : w - before * 60_000;\n return new Date(t).toISOString();\n}\n\n/** The calendar day (`YYYY-MM-DD`) of any value, in `zone` for instants. */\nexport function toDate(value: CalendarValue | Date, zone: ZoneOffset): CalendarValue {\n const k = kindOf(value);\n if (k === 'date') return value as string;\n if (k === 'floating') return (value as string).slice(0, 10);\n return toWall(value, zone).slice(0, 10);\n}\n\n/** Calendar-day arithmetic on a `YYYY-MM-DD` value (no zone involved). */\nexport function addDays(date: CalendarValue, days: number): CalendarValue {\n if (kindOf(date) !== 'date') throw new RangeError(`addDays needs a date, got ${date}`);\n return formatWall(wallMillis(date) + days * 86_400_000).slice(0, 10);\n}\n\n/**\n * The UTC fields of a `Date` as floating text. For reading a `Date` that some library has\n * produced in a \"UTC-as-wall-clock\" convention (FullCalendar with `timeZone: 'UTC'`).\n */\nexport function utcFieldsToWall(d: Date): CalendarValue {\n return formatWall(d.getTime());\n}\n\n/** Normalise floating text to seconds precision (`…T09:00` → `…T09:00:00`). */\nexport function normaliseFloating(value: CalendarValue): CalendarValue {\n return formatWall(wallMillis(value));\n}\n","/**\n * Event colours.\n *\n * ★ Without FullCalendar Premium there are no resource lanes, so \"appointments by location\"\n * is expressed as ONE COLOUR PER GROUP. `groupColor` is deterministic (same key → same colour\n * on every load and every machine), which is what lets a person learn the legend.\n * ★ Every palette entry carries white text at >= 4.5:1 (WCAG AA, normal text) — asserted by a\n * test, not by eye. Host-supplied colours (Dataverse option colours are often pale) get their\n * text colour from `readableTextOn` instead of assuming white; a well-known client control\n * hard-coded white on yellow, which is ~1.4:1.\n */\nexport const GROUP_PALETTE: readonly string[] = [\n '#0f6cbd', // blue\n '#107c10', // green\n '#c50f1f', // red\n '#8764b8', // lavender\n '#ae4b00', // dark orange\n '#03787c', // teal\n '#4f52b2', // indigo\n '#8a5b00', // dark gold\n '#c0077d', // magenta\n '#5c2e91', // purple\n];\n\n/** FNV-1a — tiny, stable across engines, good enough spread for a 10-colour palette. */\nfunction hash(key: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < key.length; i++) {\n h ^= key.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\nexport function groupColor(key: string, palette: readonly string[] = GROUP_PALETTE): string {\n return palette[hash(key) % palette.length];\n}\n\n/** `#rgb` / `#rrggbb` → [r, g, b] 0-255, or null for anything else (named colours, var()). */\nexport function parseHex(color: string): [number, number, number] | null {\n const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color.trim());\n if (!m) return null;\n const h = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];\n return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16)) as [number, number, number];\n}\n\nfunction luminance([r, g, b]: [number, number, number]): number {\n const lin = (c: number): number => {\n const s = c / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);\n}\n\n/** WCAG contrast ratio between two hex colours, or null if either cannot be parsed. */\nexport function contrastRatio(a: string, b: string): number | null {\n const pa = parseHex(a);\n const pb = parseHex(b);\n if (!pa || !pb) return null;\n const [hi, lo] = [luminance(pa), luminance(pb)].sort((x, y) => y - x);\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/**\n * Black or white text, whichever reads better on `background`. Unparseable backgrounds\n * (a CSS variable, a named colour) get `undefined`, i.e. the theme's own event text colour.\n */\nexport function readableTextOn(background: string): '#000000' | '#ffffff' | undefined {\n const onWhite = contrastRatio(background, '#ffffff');\n const onBlack = contrastRatio(background, '#000000');\n if (onWhite === null || onBlack === null) return undefined;\n return onWhite >= onBlack ? '#ffffff' : '#000000';\n}\n","import { readableTextOn } from '../core/colors';\nimport { addDays, fromWall, kindOf, normaliseFloating, toInstant, toWall, utcFieldsToWall } from '../core/datetime';\nimport type {\n CalendarEvent,\n CalendarEventChange,\n CalendarValue,\n SlotSelection,\n ValueKind,\n VisibleRange,\n ZoneOffset,\n} from '../core/types';\n\n/**\n * The translation between the kit's contract and FullCalendar's. Pure (types only from here),\n * so every rule is unit-testable without rendering a calendar.\n *\n * ★★★ THE ZONE STRATEGY. FullCalendar is always run with `timeZone: 'UTC'` and fed WALL-CLOCK\n * values already shifted into the calendar's zone. Its `Date`s then carry the wall clock in\n * their UTC fields, and we shift back on the way out.\n *\n * Why not give FullCalendar the zone? Because a PCF host does not expose one. It exposes\n * `getTimeZoneOffsetMinutes(date)` — an offset per instant, no IANA name — and MEASURED on an\n * org, a user set to Tokyo in a Pacific browser saw an event FullCalendar placed on Monday 9:00\n * that the grid beside it said was Tuesday 1:00. The browser's zone is simply the wrong zone.\n * Shifting per instant with that function is also daylight-saving correct, which a fixed\n * `'+09:00'`-style offset would not be.\n */\n\nexport interface FcEventInput {\n id: string;\n title: string;\n start: string;\n end?: string;\n allDay: boolean;\n color?: string;\n textColor?: string;\n editable?: boolean;\n durationEditable?: boolean;\n /**\n * `kind` is how FullCalendar holds the values (`date` when shown all-day); `originalKind` is\n * what the host SENT, and what a change is converted back into — the \"an event keeps its kind\"\n * contract. They differ only for an instant/floating start forced all-day with `allDay: true`.\n */\n extendedProps: { source: CalendarEvent; kind: ValueKind; originalKind: ValueKind };\n}\n\nfunction toFcValue(value: CalendarValue | Date, zone: ZoneOffset): string {\n const k = kindOf(value);\n if (k === 'instant') return toWall(value, zone);\n if (k === 'floating') return normaliseFloating(value as string);\n return value as string;\n}\n\n/** The exclusive all-day end for any end value: a timed end that is not midnight rounds UP a day. */\nfunction exclusiveAllDayEnd(end: CalendarValue | Date, zone: ZoneOffset): string {\n const wall = toFcValue(end, zone);\n if (wall.length === 10) return wall;\n return wall.slice(11, 19) === '00:00:00' ? wall.slice(0, 10) : addDays(wall.slice(0, 10), 1);\n}\n\nexport function toFcEvent(event: CalendarEvent, zone: ZoneOffset): FcEventInput {\n const originalKind = kindOf(event.start);\n const allDay = event.allDay ?? originalKind === 'date';\n const start = allDay ? toFcValue(event.start, zone).slice(0, 10) : toFcValue(event.start, zone);\n const out: FcEventInput = {\n id: event.id,\n title: event.title,\n start,\n allDay,\n extendedProps: { source: event, kind: allDay ? 'date' : originalKind, originalKind },\n };\n // ★★ Optional keys are OMITTED, never set to `undefined`. FullCalendar treats a PRESENT key as a\n // value: `editable: undefined` resolves to `startEditable === false` (read back through\n // FullCalendar's API — see fullcalendar-contract.test.tsx), which locked EVERY event in an\n // editable calendar. Same family as the `slotMinTime={undefined}` crash in CalendarView.\n if (event.end !== undefined) out.end = allDay ? exclusiveAllDayEnd(event.end, zone) : toFcValue(event.end, zone);\n if (event.color) {\n out.color = event.color;\n const text = readableTextOn(event.color);\n if (text) out.textColor = text;\n }\n if (event.readOnly) out.editable = false;\n // ★ An event with no END is not resizable: a resize would invent an end the host has nowhere\n // to store (a map with no end column), and the calendar would keep showing a length that\n // was never saved. Moving it is still fine.\n else if (event.end === undefined) out.durationEditable = false;\n return out;\n}\n\n/** A FullCalendar `Date` (UTC fields = wall clock) → a value of `kind`. */\nexport function fromFcDate(d: Date, kind: ValueKind, zone: ZoneOffset): CalendarValue {\n const wall = utcFieldsToWall(d);\n if (kind === 'date') return wall.slice(0, 10);\n if (kind === 'floating') return wall;\n return fromWall(wall, zone);\n}\n\nexport interface FcChangedEventLike {\n start: Date | null;\n end: Date | null;\n allDay: boolean;\n extendedProps: Record<string, unknown>;\n}\n\n/** A calendar value of `kind` → the same moment in `target` kind (all-day midnight for dates). */\nfunction asKind(value: CalendarValue, from: ValueKind, target: ValueKind, zone: ZoneOffset): CalendarValue {\n if (from === target || from !== 'date') return value;\n return target === 'instant' ? fromWall(`${value}T00:00:00`, zone) : `${value}T00:00:00`;\n}\n\n/** The host's original value, canonicalised, for fields a change did not touch. */\nfunction canonical(value: CalendarValue | Date): CalendarValue {\n const k = kindOf(value);\n return k === 'instant' ? toInstant(value) : k === 'floating' ? normaliseFloating(value as string) : (value as string);\n}\n\n/** A dropped/resized FullCalendar event → a change in the ORIGINAL event's kind. */\nexport function toChange(\n fcEvent: FcChangedEventLike,\n kind: CalendarEventChange['kind'],\n zone: ZoneOffset\n): CalendarEventChange {\n const { source, kind: valueKind, originalKind } = fcEvent.extendedProps as FcEventInput['extendedProps'];\n if (!fcEvent.start) throw new Error(`FullCalendar returned no start for event ${source.id}`);\n const back = (d: Date): CalendarValue => asKind(fromFcDate(d, valueKind, zone), valueKind, originalKind ?? valueKind, zone);\n return {\n event: source,\n kind,\n // ★ A RESIZE never moves the start. Recomputing it from FullCalendar's wall-clock Date is\n // lossy on the fall-back day (the second 01:30 reads back as the first), and toWebApiPatch\n // writes `start` unconditionally — so resizing an end silently moved the start an hour.\n start: kind === 'resize' ? canonical(source.start) : back(fcEvent.start),\n end: fcEvent.end ? back(fcEvent.end) : undefined,\n allDay: fcEvent.allDay,\n };\n}\n\nexport function toSelection(start: Date, end: Date, allDay: boolean, zone: ZoneOffset): SlotSelection {\n const kind: ValueKind = allDay ? 'date' : 'instant';\n return { start: fromFcDate(start, kind, zone), end: fromFcDate(end, kind, zone), allDay };\n}\n\nexport function toVisibleRange(start: Date, end: Date, view: string, zone: ZoneOffset): VisibleRange {\n return {\n start: fromFcDate(start, 'instant', zone),\n end: fromFcDate(end, 'instant', zone),\n view,\n startDate: utcFieldsToWall(start).slice(0, 10),\n endDate: utcFieldsToWall(end).slice(0, 10),\n };\n}\n\n/** \"Now\" in the calendar's zone, for FullCalendar's `now` option (drives today + now-line). */\nexport const nowWall = (zone: ZoneOffset): string => toWall(new Date(), zone);\n\n/**\n * ★ A drag may not change an event's KIND. Dropping a timed appointment on the all-day row\n * would have to invent a date-only value for a date-time column (or the reverse), and there\n * is no right answer to invent. FullCalendar allows it by default; this forbids it.\n */\nexport const sameAllDayness = (spanAllDay: boolean, eventAllDay: boolean): boolean => spanAllDay === eventAllDay;\n\n/* ── Optimistic overrides ────────────────────────────────────────────────────────────────────\n * FullCalendar moves an event the moment it is dropped. The host's `events` prop still has the\n * OLD position until its save resolves AND it re-renders with fresh data — and a PCF host\n * re-renders on anything (resize, focus), so without this the event would snap back mid-save\n * and jump forward again later. An override holds the new position until the props agree.\n */\nexport interface PendingMove {\n start: CalendarValue;\n end: CalendarValue | undefined;\n allDay: boolean;\n /** Increments per change; only the newest change for an event may settle or revert it. */\n token: number;\n /**\n * Set when the save RESOLVED: the `events` prop at that moment. The move is dropped on the\n * first `events` that is a different array — whether or not it matches. Without this a save\n * whose value never comes back verbatim (no end column mapped, a plugin adjusting the time)\n * kept its override forever, showing a position that was never stored.\n */\n savedWith?: readonly CalendarEvent[];\n}\n\nconst same = (a: CalendarValue | Date | undefined, b: CalendarValue | undefined): boolean => {\n if (a === undefined || b === undefined) return a === b;\n const ka = kindOf(a);\n if (ka !== kindOf(b)) return false;\n if (ka === 'instant') return new Date(a).getTime() === new Date(b).getTime();\n return ka === 'floating' ? normaliseFloating(a as string) === normaliseFloating(b) : a === b;\n};\n\n/** Events with pending moves applied. */\nexport function applyPending(events: readonly CalendarEvent[], pending: ReadonlyMap<string, PendingMove>): CalendarEvent[] {\n if (pending.size === 0) return events as CalendarEvent[];\n return events.map((e) => {\n const p = pending.get(e.id);\n return p ? { ...e, start: p.start, end: p.end, allDay: p.allDay } : e;\n });\n}\n\n/**\n * Ids whose pending move can be dropped: the props now agree, the event vanished (deleted\n * elsewhere — no ghost), or the save resolved and the host has since re-rendered.\n */\nexport function settledIds(events: readonly CalendarEvent[], pending: ReadonlyMap<string, PendingMove>): string[] {\n const byId = new Map(events.map((e) => [e.id, e]));\n const done: string[] = [];\n for (const [id, p] of pending) {\n const e = byId.get(id);\n if (!e || (same(e.start, p.start) && same(e.end, p.end)) || (p.savedWith !== undefined && p.savedWith !== events)) done.push(id);\n }\n return done;\n}\n","import * as React from 'react';\nimport type { CalendarEvent, CalendarEventChange, ZoneOffset } from '../core/types';\nimport { settledIds, toChange, type FcChangedEventLike, type PendingMove } from './fcBridge';\n\n/** The part of FullCalendar's EventChangeInfo this hook uses — small enough to fake in a test. */\nexport interface ChangeInfoLike {\n event: FcChangedEventLike;\n revert(): void;\n}\n\nexport type ChangeHandler = (change: CalendarEventChange) => void | Promise<void>;\n\nfunction describe(error: unknown): string {\n const m = (error as { message?: unknown } | null)?.message;\n return typeof m === 'string' && m ? m : String(error);\n}\n\n/**\n * The optimistic-save state machine behind drag and resize, separated from the calendar so\n * every transition is testable without driving a real drag (which synthetic input could not do\n * against FullCalendar v7 — see CLAUDE.md).\n *\n * drop ──▶ pending(token) ──resolve──▶ saved (held until the host's next `events`)\n * └─reject/throw─▶ revert + banner (only if still the NEWEST change)\n */\nexport function useOptimisticChanges(\n events: readonly CalendarEvent[],\n onEventChange: ChangeHandler | undefined,\n onChangeError: ((error: unknown, change: CalendarEventChange) => void) | undefined,\n zoneRef: React.MutableRefObject<ZoneOffset>\n): {\n pending: ReadonlyMap<string, PendingMove>;\n error: string | null;\n dismissError: () => void;\n handleChange: (kind: CalendarEventChange['kind']) => (info: ChangeInfoLike) => void;\n} {\n const [pending, setPending] = React.useState<ReadonlyMap<string, PendingMove>>(() => new Map());\n const [error, setError] = React.useState<string | null>(null);\n const eventsRef = React.useRef(events);\n eventsRef.current = events;\n // ★ The newest token per event, held in a REF so a settling promise can ask \"am I still the\n // current change?\" synchronously. Asking inside a setState updater would be a side effect in\n // an updater, which React may run twice or late.\n const latest = React.useRef(new Map<string, number>());\n const seq = React.useRef(0);\n const mounted = React.useRef(true);\n React.useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n React.useEffect(() => {\n const done = settledIds(events, pending);\n if (done.length === 0) return;\n for (const id of done) if (latest.current.get(id) === pending.get(id)?.token) latest.current.delete(id);\n setPending((prev) => {\n const next = new Map(prev);\n for (const id of done) next.delete(id);\n return next;\n });\n }, [events, pending]);\n\n const handleChange = React.useCallback(\n (kind: CalendarEventChange['kind']) =>\n (info: ChangeInfoLike): void => {\n if (!onEventChange) {\n info.revert();\n return;\n }\n let change: CalendarEventChange;\n try {\n change = toChange(info.event, kind, zoneRef.current);\n } catch (e) {\n info.revert();\n setError(describe(e));\n return;\n }\n setError(null);\n const id = change.event.id;\n const token = ++seq.current;\n\n const fail = (err: unknown): void => {\n const current = latest.current.get(id) === token;\n if (current) latest.current.delete(id);\n // ★ onChangeError BEFORE revert, so a revert that throws (calendar already destroyed)\n // cannot swallow the host's error callback.\n onChangeError?.(err, change);\n if (!mounted.current) return;\n setError(`Could not save \"${change.event.title}\": ${describe(err)}`);\n if (!current) return; // a newer move of this event owns its position now\n setPending((prev) => {\n if (!prev.has(id)) return prev;\n const next = new Map(prev);\n next.delete(id);\n return next;\n });\n try {\n info.revert();\n } catch {\n /* the calendar was torn down under us; nothing left to put back */\n }\n };\n\n // ★ Registered BEFORE the handler runs: a synchronous throw calls `fail`, which asks \"am I\n // the current change?\" — registering after the call answered \"no\" and skipped the\n // revert (caught by the sync-throw test).\n latest.current.set(id, token);\n let result: void | Promise<void>;\n try {\n result = onEventChange(change);\n } catch (err) {\n // A handler that throws synchronously is a failed save, not an unhandled error that\n // leaves the event where it was dropped.\n fail(err);\n return;\n }\n if (!result || typeof (result as Promise<void>).then !== 'function') {\n // Fire-and-forget handler: nothing to hold; the host owns the position from here.\n if (latest.current.get(id) === token) latest.current.delete(id);\n return;\n }\n\n setPending((prev) => new Map(prev).set(id, { start: change.start, end: change.end, allDay: change.allDay, token }));\n (result as Promise<void>).then(\n () => {\n if (!mounted.current || latest.current.get(id) !== token) return;\n const savedWith = eventsRef.current;\n setPending((prev) => {\n const p = prev.get(id);\n if (!p || p.token !== token) return prev;\n return new Map(prev).set(id, { ...p, savedWith });\n });\n },\n fail\n );\n },\n [onEventChange, onChangeError, zoneRef]\n );\n\n return { pending, error, dismissError: React.useCallback(() => setError(null), []), handleChange };\n}\n","import type * as React from 'react';\n\n/**\n * The colours a calendar can be themed with, as plain CSS values (hex, rgb(), or `var(--x)`).\n * Each maps onto one of FullCalendar v7's classic-theme custom properties, so theming is just\n * setting variables on the calendar's own container — no stylesheet override, nothing global.\n *\n * ★ Scoped to the container on purpose: FullCalendar's palette declares its defaults on `:root`,\n * and a PCF control shares `:root` with the whole form. Two calendars on one form each set\n * their own, and neither touches the host.\n */\nexport interface CalendarTheme {\n primary?: string;\n primaryForeground?: string;\n background?: string;\n foreground?: string;\n mutedForeground?: string;\n faint?: string;\n muted?: string;\n strong?: string;\n border?: string;\n strongBorder?: string;\n today?: string;\n highlight?: string;\n now?: string;\n button?: string;\n buttonForeground?: string;\n buttonBorder?: string;\n buttonStrong?: string;\n buttonStrongBorder?: string;\n fontFamily?: string;\n fontSize?: string;\n /** Rendered as a banner above the calendar when a save fails. */\n error?: string;\n}\n\nconst VAR: Record<Exclude<keyof CalendarTheme, 'fontFamily' | 'fontSize' | 'error'>, string> = {\n primary: '--fc-classic-primary',\n primaryForeground: '--fc-classic-primary-foreground',\n background: '--fc-classic-background',\n foreground: '--fc-classic-foreground',\n mutedForeground: '--fc-classic-muted-foreground',\n faint: '--fc-classic-faint',\n muted: '--fc-classic-muted',\n strong: '--fc-classic-strong',\n border: '--fc-classic-border',\n strongBorder: '--fc-classic-strong-border',\n today: '--fc-classic-today',\n highlight: '--fc-classic-highlight',\n now: '--fc-classic-now',\n button: '--fc-classic-button',\n buttonForeground: '--fc-classic-button-foreground',\n buttonBorder: '--fc-classic-button-border',\n buttonStrong: '--fc-classic-button-strong',\n buttonStrongBorder: '--fc-classic-button-strong-border',\n};\n\n/** The container style that applies `theme`. Unset keys keep FullCalendar's defaults. */\nexport function themeStyle(theme: CalendarTheme | undefined): React.CSSProperties {\n const style: Record<string, string> = {};\n if (!theme) return style;\n for (const [key, cssVar] of Object.entries(VAR)) {\n const v = theme[key as keyof typeof VAR];\n if (v) style[cssVar] = v;\n }\n if (theme.fontFamily) style.fontFamily = theme.fontFamily;\n if (theme.fontSize) style.fontSize = theme.fontSize;\n if (theme.background) style.background = theme.background;\n if (theme.foreground) style.color = theme.foreground;\n return style as React.CSSProperties;\n}\n","import * as React from 'react';\nimport type { CalendarDataSource, CalendarEvent, CalendarEventChange, SlotSelection, VisibleRange } from '../core/types';\n\nexport interface CalendarSourceBinding {\n events: readonly CalendarEvent[];\n loading: boolean;\n /** The last load failure, or null. A failed load keeps the previous events on screen. */\n error: string | null;\n /** Re-run the current range (e.g. after booking a slot elsewhere on the page). */\n reload: () => void;\n /** Spread onto `<CalendarView>` / `<FluentCalendar>`. */\n props: {\n events: readonly CalendarEvent[];\n loading: boolean;\n editable: boolean;\n selectable: boolean;\n onRangeChange: (range: VisibleRange) => void;\n onEventChange?: (change: CalendarEventChange) => Promise<void>;\n onEventClick?: (event: CalendarEvent) => void;\n onSelectRange?: (slot: SlotSelection) => void;\n };\n}\n\nfunction describe(error: unknown): string {\n const m = (error as { message?: unknown } | null)?.message;\n return typeof m === 'string' && m ? m : String(error);\n}\n\nconst isAbort = (e: unknown): boolean => (e as { name?: unknown } | null)?.name === 'AbortError';\n\n/**\n * Drive a calendar from a PULLED `CalendarDataSource` (code app, Power Pages): load on every range\n * change, save a drag through the source, then reload so the calendar shows what the server kept.\n *\n * ★ Only the NEWEST load may land. Ranges change faster than networks answer (click \"next\" three\n * times), so an older response arriving last must not overwrite the newer week. Each load aborts\n * its predecessor AND is sequence-checked — a source that ignores `signal` is still safe.\n * ★ A save that rejects is RE-THROWN, so the calendar's optimistic layer reverts the event and shows\n * the message; swallowing it here would leave the event where it was dropped, unsaved.\n */\nexport function useCalendarSource(source: CalendarDataSource | null | undefined): CalendarSourceBinding {\n const [events, setEvents] = React.useState<readonly CalendarEvent[]>([]);\n const [loading, setLoading] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n const range = React.useRef<VisibleRange | null>(null);\n const seq = React.useRef(0);\n const inflight = React.useRef<AbortController | null>(null);\n // The source the current (or last) load was issued for — see the mount effect below.\n const loadedFor = React.useRef<CalendarDataSource | null>(null);\n\n const load = React.useCallback(async (): Promise<void> => {\n const r = range.current;\n if (!source || !r) return;\n loadedFor.current = source;\n inflight.current?.abort();\n const controller = new AbortController();\n inflight.current = controller;\n const mine = ++seq.current;\n setLoading(true);\n try {\n const next = await source.load(r, controller.signal);\n if (mine !== seq.current) return;\n setEvents(next);\n setError(null);\n } catch (e) {\n if (mine !== seq.current || isAbort(e)) return;\n setError(describe(e));\n } finally {\n if (mine === seq.current) setLoading(false);\n }\n }, [source]);\n\n // A new source (different filter, different table) re-queries the range already on screen.\n // ★ MEASURED on a live code app and Power Pages site: the calendar reports its range from a CHILD\n // effect, which runs BEFORE this one — so on mount a load for THIS source is already in flight,\n // and unconditionally loading again doubled every mount (2 calls; the portal bills per call).\n // Skip when the load in flight is already for this source. The cleanup clears the marker, so a\n // StrictMode fake-unmount (which aborts the in-flight load) re-queries on the re-run instead of\n // stranding the calendar empty.\n React.useEffect(() => {\n if (loadedFor.current !== source) {\n setEvents([]);\n void load();\n }\n return () => {\n // `seq` is a counter, not a DOM ref: bumping the CURRENT value is the point (it retires any\n // load still in flight), so the rule's \"copy it first\" advice would defeat the cleanup.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n seq.current++;\n inflight.current?.abort();\n loadedFor.current = null;\n };\n }, [load, source]);\n\n const onRangeChange = React.useCallback(\n (r: VisibleRange) => {\n const prev = range.current;\n range.current = r;\n if (prev && prev.start === r.start && prev.end === r.end) return;\n void load();\n },\n [load]\n );\n\n const reload = React.useCallback(() => void load(), [load]);\n\n const onEventChange = React.useMemo(\n () =>\n source?.capabilities.update && source.update\n ? async (change: CalendarEventChange): Promise<void> => {\n await source.update!(change);\n await load();\n }\n : undefined,\n [source, load]\n );\n\n const open = React.useMemo(\n () => (source?.capabilities.open && source.open ? (e: CalendarEvent) => source.open!(e) : undefined),\n [source]\n );\n const onSelectRange = React.useMemo(\n () =>\n source?.capabilities.create && source.create\n ? (slot: SlotSelection) => {\n void Promise.resolve()\n .then(() => source.create!(slot))\n .then(\n () => load(),\n (e: unknown) => setError(describe(e))\n );\n }\n : undefined,\n [source, load]\n );\n\n return {\n events,\n loading,\n error,\n reload,\n props: {\n events,\n loading,\n editable: !!onEventChange,\n selectable: !!onSelectRange,\n onRangeChange,\n ...(onEventChange ? { onEventChange } : {}),\n ...(open ? { onEventClick: open } : {}),\n ...(onSelectRange ? { onSelectRange } : {}),\n },\n };\n}\n"],"mappings":"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","names":["React","t","FullCalendar","dayGridPlugin","timeGridPlugin","listPlugin","interactionPlugin","classicThemePlugin","React","describe"]}
package/dist/index.mjs CHANGED
@@ -508,9 +508,11 @@ function useCalendarSource(source) {
508
508
  const range = React3.useRef(null);
509
509
  const seq = React3.useRef(0);
510
510
  const inflight = React3.useRef(null);
511
+ const loadedFor = React3.useRef(null);
511
512
  const load = React3.useCallback(async () => {
512
513
  const r = range.current;
513
514
  if (!source || !r) return;
515
+ loadedFor.current = source;
514
516
  inflight.current?.abort();
515
517
  const controller = new AbortController();
516
518
  inflight.current = controller;
@@ -529,16 +531,16 @@ function useCalendarSource(source) {
529
531
  }
530
532
  }, [source]);
531
533
  React3.useEffect(() => {
532
- setEvents([]);
533
- void load();
534
- }, [load]);
535
- React3.useEffect(
536
- () => () => {
534
+ if (loadedFor.current !== source) {
535
+ setEvents([]);
536
+ void load();
537
+ }
538
+ return () => {
537
539
  seq.current++;
538
540
  inflight.current?.abort();
539
- },
540
- []
541
- );
541
+ loadedFor.current = null;
542
+ };
543
+ }, [load, source]);
542
544
  const onRangeChange = React3.useCallback(
543
545
  (r) => {
544
546
  const prev = range.current;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/view/CalendarView.tsx","../src/core/datetime.ts","../src/core/colors.ts","../src/view/fcBridge.ts","../src/view/useOptimisticChanges.ts","../src/view/theme.ts","../src/view/useCalendarSource.ts"],"sourcesContent":["import * as React from 'react';\nimport FullCalendar from '@fullcalendar/react';\nimport dayGridPlugin from '@fullcalendar/react/daygrid';\nimport timeGridPlugin from '@fullcalendar/react/timegrid';\nimport listPlugin from '@fullcalendar/react/list';\nimport interactionPlugin from '@fullcalendar/react/interaction';\nimport classicThemePlugin from '@fullcalendar/react/themes/classic';\nimport type {\n DateSelectInfo,\n DatesSetInfo,\n EventChangeInfo,\n EventClickInfo,\n} from '@fullcalendar/react';\n\nimport '@fullcalendar/react/skeleton.css';\nimport '@fullcalendar/react/themes/classic/theme.css';\nimport '@fullcalendar/react/themes/classic/palette.css';\n\nimport { browserZone } from '../core/datetime';\nimport type { BusinessHours, CalendarEvent, CalendarEventChange, SlotSelection, VisibleRange, ZoneOffset } from '../core/types';\nimport { applyPending, nowWall, sameAllDayness, toFcEvent, toSelection, toVisibleRange } from './fcBridge';\nimport { useOptimisticChanges, type ChangeInfoLike } from './useOptimisticChanges';\nimport { themeStyle, type CalendarTheme } from './theme';\n\n/** The views a host can ask for, by plain name. */\nexport type CalendarViewName = 'month' | 'week' | 'workWeek' | 'day' | 'list';\n\nconst FC_VIEW: Record<CalendarViewName, string> = {\n month: 'dayGridMonth',\n week: 'timeGridWeek',\n workWeek: 'timeGridWorkWeek',\n day: 'timeGridDay',\n list: 'listWeek',\n};\n\nconst VIEW_LABEL: Record<CalendarViewName, string> = {\n month: 'Month',\n week: 'Week',\n workWeek: 'Work week',\n day: 'Day',\n list: 'List',\n};\n\nexport interface CalendarViewProps {\n /** Plain event objects. See `CalendarEvent` for how a value's shape sets its kind. */\n events: readonly CalendarEvent[];\n /** The zone to show instants in. Default: the browser's. A PCF host passes the USER's. */\n zone?: ZoneOffset;\n /** Starting view. Default `week`. */\n view?: CalendarViewName;\n /** View buttons offered, in order. Default: month, week, day, list. */\n views?: readonly CalendarViewName[];\n /** `YYYY-MM-DD` to open on. Default today (in `zone`). */\n initialDate?: string;\n /** Allow drag and resize. Default false — read-only is the safe default. */\n editable?: boolean;\n /** Allow selecting an empty range (→ `onSelectRange`). Default: true when a handler is given. */\n selectable?: boolean;\n businessHours?: readonly BusinessHours[];\n /** 0 = Sunday. */\n firstDay?: number;\n /** `HH:mm:ss` bounds of the time grid. */\n slotMinTime?: string;\n slotMaxTime?: string;\n /** Where the time grid first scrolls to. Default `08:00:00`. */\n scrollTime?: string;\n /** Any CSS height, `'auto'`, or a number of pixels. Default `'auto'`. */\n height?: number | string;\n theme?: CalendarTheme;\n /** Shown while data for a new range loads. */\n loading?: boolean;\n className?: string;\n /**\n * Classes for FullCalendar's own parts. ★ v7 HASHES its class names (`fc-classic-1El`), so a\n * descendant selector like `.fc-button` matches NOTHING — measured: 0 of 7 buttons. These go\n * through FullCalendar's `buttonClass` / `headerToolbarClass` options instead.\n */\n classNames?: { button?: string; toolbar?: string };\n /** Accessible name for the calendar region. */\n 'aria-label'?: string;\n\n onEventClick?: (event: CalendarEvent) => void;\n onSelectRange?: (selection: SlotSelection) => void;\n /**\n * A move or resize. Return a promise to make it OPTIMISTIC: the event stays where it was\n * dropped while the promise runs, and goes back — with a message — if it rejects.\n */\n onEventChange?: (change: CalendarEventChange) => void | Promise<void>;\n onRangeChange?: (range: VisibleRange) => void;\n /** Called after a failed change has been reverted. The banner is shown either way. */\n onChangeError?: (error: unknown, change: CalendarEventChange) => void;\n}\n\n/** FullCalendar's change info → the hook's minimal shape (what the hook reads, and nothing else). */\nconst asChangeInfo = (info: EventChangeInfo): ChangeInfoLike => ({\n event: {\n start: info.event.start as Date | null,\n end: info.event.end as Date | null,\n allDay: info.event.allDay,\n extendedProps: info.event.extendedProps,\n },\n revert: () => info.revert(),\n});\n\nconst bannerStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '6px 10px',\n marginBottom: 6,\n borderRadius: 4,\n border: '1px solid currentColor',\n};\n\n/**\n * FullCalendar v7 (MIT views only), fed plain events. Knows nothing about Dataverse and nothing\n * about Fluent — this entry is what a Power Pages site uses. `FluentCalendar` (`./v9`) wraps it\n * with Fluent tokens.\n *\n * ★ Nothing here portals and nothing uses tabster, so it cannot join the Fluent/tabster\n * singleton race that has taken down PCF controls on model-driven forms. The error banner is\n * in-tree for the same reason: a Toast or Dialog would portal to `document.body`.\n */\nexport function CalendarView(props: CalendarViewProps): React.ReactElement {\n const zone = props.zone ?? browserZone;\n const zoneRef = React.useRef(zone);\n zoneRef.current = zone;\n\n const { pending, error, dismissError, handleChange } = useOptimisticChanges(\n props.events,\n props.onEventChange,\n props.onChangeError,\n zoneRef\n );\n\n const fcEvents = React.useMemo(\n () => applyPending(props.events, pending).map((e) => toFcEvent(e, zone)),\n [props.events, pending, zone]\n );\n\n const views = props.views ?? (['month', 'week', 'day', 'list'] as const);\n\n /**\n * ★★ Options the host did not set are OMITTED, never passed as `undefined`. FullCalendar's\n * option refiners treat a present-but-undefined key as a value: `slotMinTime={undefined}`\n * replaced the default and the time-grid views threw \"Cannot read properties of undefined\n * (reading 'years')\" on first render. The list view never reads it, so a list-only test\n * was green — found by rendering the WEEK view.\n */\n const optional: Record<string, unknown> = {};\n if (props.firstDay !== undefined) optional.firstDay = props.firstDay;\n if (props.slotMinTime !== undefined) optional.slotMinTime = props.slotMinTime;\n if (props.slotMaxTime !== undefined) optional.slotMaxTime = props.slotMaxTime;\n if (props.initialDate !== undefined) optional.initialDate = props.initialDate;\n if (props.classNames?.button) optional.buttonClass = props.classNames.button;\n if (props.classNames?.toolbar) optional.headerToolbarClass = props.classNames.toolbar;\n if (props.businessHours) optional.businessHours = props.businessHours.map((b) => ({ ...b, daysOfWeek: [...b.daysOfWeek] }));\n const selectable = props.selectable ?? !!props.onSelectRange;\n\n return (\n <div\n className={props.className}\n style={themeStyle(props.theme)}\n role=\"region\"\n aria-label={props['aria-label'] ?? 'Calendar'}\n aria-busy={props.loading || undefined}\n data-calendar-kit=\"\"\n >\n {error && (\n <div role=\"alert\" style={{ ...bannerStyle, color: props.theme?.error ?? '#a4262c' }}>\n <span style={{ flex: 1 }}>{error}</span>\n <button type=\"button\" onClick={dismissError} aria-label=\"Dismiss\" style={{ font: 'inherit' }}>\n ×\n </button>\n </div>\n )}\n <FullCalendar\n plugins={[dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin, classicThemePlugin]}\n timeZone=\"UTC\"\n now={() => nowWall(zoneRef.current)}\n initialView={FC_VIEW[props.view ?? 'week']}\n views={{ timeGridWorkWeek: { type: 'timeGridWeek', hiddenDays: [0, 6] } }}\n headerToolbar={{\n start: 'today prev,next',\n center: 'title',\n end: views.map((v) => FC_VIEW[v]).join(','),\n }}\n // ★ v7 renamed v6's `buttonText` to `buttons: { name: { text } }`, keyed by VIEW name.\n buttons={{\n today: { text: 'Today' },\n ...Object.fromEntries(views.map((v) => [FC_VIEW[v], { text: VIEW_LABEL[v] }])),\n }}\n height={props.height ?? 'auto'}\n events={fcEvents}\n editable={!!props.editable && !!props.onEventChange}\n selectable={selectable}\n selectMirror\n nowIndicator\n dayMaxEvents\n {...optional}\n scrollTime={props.scrollTime ?? '08:00:00'}\n eventAllow={(span, moving) => (moving ? sameAllDayness(span.allDay, moving.allDay) : true)}\n datesSet={(info: DatesSetInfo) =>\n props.onRangeChange?.(toVisibleRange(info.start as Date, info.end as Date, info.view.type, zoneRef.current))\n }\n eventClick={(info: EventClickInfo) => {\n info.jsEvent.preventDefault();\n const source = (info.event.extendedProps as { source?: CalendarEvent }).source;\n if (source) props.onEventClick?.(source);\n }}\n select={(info: DateSelectInfo) => {\n props.onSelectRange?.(toSelection(info.start as Date, info.end as Date, info.allDay, zoneRef.current));\n info.view.calendar.unselect();\n }}\n eventDrop={(info: EventChangeInfo) => handleChange('move')(asChangeInfo(info))}\n eventResize={(info: EventChangeInfo) => handleChange('resize')(asChangeInfo(info))}\n />\n </div>\n );\n}\n","import type { CalendarValue, ValueKind, ZoneOffset } from './types';\n\n/**\n * Value-kind detection and zone conversion.\n *\n * ★★ Nothing in this file may read the PROCESS time zone. Every `Date` is built and read through\n * its UTC fields, so the same input gives the same output on a laptop in Auckland and a CI\n * runner in UTC. `npm test` runs the suite under three TZ values to prove it — a single-zone\n * run cannot see a leak, because the developer's zone is the one the code was written in.\n */\n\nconst DATE_RE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst FLOATING_RE = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d{1,9})?)?$/;\nconst INSTANT_RE = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?(?:Z|[+-]\\d{2}:?\\d{2})$/i;\n\nconst pad = (n: number, w = 2): string => String(n).padStart(w, '0');\n\n/** The browser's own zone, east-positive. The default when a host supplies none. */\nexport const browserZone: ZoneOffset = (instant) => -instant.getTimezoneOffset();\n\n/** A fixed zone (no daylight saving). Mostly for tests and demos. */\nexport const fixedZone =\n (minutesEast: number): ZoneOffset =>\n () =>\n minutesEast;\n\nexport function kindOf(value: CalendarValue | Date): ValueKind {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) throw new RangeError('Invalid Date');\n return 'instant';\n }\n const local = DATE_RE.test(value) ? 'date' : FLOATING_RE.test(value) ? 'floating' : null;\n if (local) {\n // ★ The regex accepts `2026-02-31`; Date.UTC would silently roll it to March 3rd. Reject\n // any date whose fields do not survive a round trip.\n const m = (FLOATING_RE.exec(value) ?? DATE_RE.exec(value))!;\n const [, y, mo, d, h = '0', mi = '0', sec = '0'] = m;\n const t = new Date(Date.UTC(+y, +mo - 1, +d, +h, +mi, +sec));\n if (t.getUTCFullYear() !== +y || t.getUTCMonth() !== +mo - 1 || t.getUTCDate() !== +d || +h > 23 || +mi > 59 || +sec > 59) {\n throw new RangeError(`Not a real calendar date or time: ${JSON.stringify(value)}`);\n }\n return local;\n }\n if (INSTANT_RE.test(value) && !Number.isNaN(new Date(value).getTime())) return 'instant';\n throw new RangeError(`Not an ISO-8601 date, wall-clock time or instant: ${JSON.stringify(value)}`);\n}\n\n/** An instant as canonical UTC ISO text (`…T…Z`, milliseconds kept). */\nexport function toInstant(value: CalendarValue | Date): CalendarValue {\n if (value instanceof Date) return value.toISOString();\n if (kindOf(value) !== 'instant') throw new RangeError(`Not an instant: ${value}`);\n return new Date(value).toISOString();\n}\n\n/** `Date.UTC` from wall-clock fields — i.e. the wall clock treated AS IF it were UTC. */\nfunction wallMillis(wall: string): number {\n const m = FLOATING_RE.exec(wall) ?? DATE_RE.exec(wall);\n if (!m) throw new RangeError(`Not a date or wall-clock time: ${wall}`);\n const [, y, mo, d, h = '0', mi = '0', s = '0'] = m;\n return Date.UTC(+y, +mo - 1, +d, +h, +mi, +s);\n}\n\nfunction formatWall(ms: number): string {\n const d = new Date(ms);\n return (\n `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}` +\n `T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`\n );\n}\n\n/** The wall-clock reading of an instant in `zone`, as floating text (`YYYY-MM-DDTHH:mm:ss`). */\nexport function toWall(instant: CalendarValue | Date, zone: ZoneOffset): CalendarValue {\n const at = instant instanceof Date ? instant : new Date(toInstant(instant));\n return formatWall(at.getTime() + zone(at) * 60_000);\n}\n\n/**\n * The instant at which `zone`'s clocks read `wall`.\n *\n * The offset depends on the instant being solved for, so this takes the offset in force a day\n * BEFORE and a day AFTER the wall reading (no zone changes offset twice in 48 h) and tries both:\n * - an ordinary time: both give the same instant;\n * - a wall time that exists TWICE (fall-back): both round-trip, and the EARLIER is returned;\n * - a wall time that DOES NOT EXIST (spring-forward gap): neither round-trips, and the\n * pre-transition offset is used, which pushes it forward by the gap (02:30 → 03:30), as JS\n * `Date` does. What Dataverse does with one is UNMEASURED.\n *\n * ★ A reviewer caught the first version returning the LATER occurrence east of UTC (Berlin) and\n * the earlier one west of it (Pacific): the two-pass guess settled on whichever offset the\n * first guess happened to land in. Tests now pin both hemispheres with synthetic zones.\n */\nexport function fromWall(wall: CalendarValue, zone: ZoneOffset): CalendarValue {\n const w = wallMillis(wall);\n const before = zone(new Date(w - 86_400_000));\n const after = zone(new Date(w + 86_400_000));\n const fits = (offset: number): number | null => {\n const t = w - offset * 60_000;\n return zone(new Date(t)) === offset ? t : null;\n };\n const candidates = [fits(before), fits(after)].filter((t): t is number => t !== null);\n const t = candidates.length > 0 ? Math.min(...candidates) : w - before * 60_000;\n return new Date(t).toISOString();\n}\n\n/** The calendar day (`YYYY-MM-DD`) of any value, in `zone` for instants. */\nexport function toDate(value: CalendarValue | Date, zone: ZoneOffset): CalendarValue {\n const k = kindOf(value);\n if (k === 'date') return value as string;\n if (k === 'floating') return (value as string).slice(0, 10);\n return toWall(value, zone).slice(0, 10);\n}\n\n/** Calendar-day arithmetic on a `YYYY-MM-DD` value (no zone involved). */\nexport function addDays(date: CalendarValue, days: number): CalendarValue {\n if (kindOf(date) !== 'date') throw new RangeError(`addDays needs a date, got ${date}`);\n return formatWall(wallMillis(date) + days * 86_400_000).slice(0, 10);\n}\n\n/**\n * The UTC fields of a `Date` as floating text. For reading a `Date` that some library has\n * produced in a \"UTC-as-wall-clock\" convention (FullCalendar with `timeZone: 'UTC'`).\n */\nexport function utcFieldsToWall(d: Date): CalendarValue {\n return formatWall(d.getTime());\n}\n\n/** Normalise floating text to seconds precision (`…T09:00` → `…T09:00:00`). */\nexport function normaliseFloating(value: CalendarValue): CalendarValue {\n return formatWall(wallMillis(value));\n}\n","/**\n * Event colours.\n *\n * ★ Without FullCalendar Premium there are no resource lanes, so \"appointments by location\"\n * is expressed as ONE COLOUR PER GROUP. `groupColor` is deterministic (same key → same colour\n * on every load and every machine), which is what lets a person learn the legend.\n * ★ Every palette entry carries white text at >= 4.5:1 (WCAG AA, normal text) — asserted by a\n * test, not by eye. Host-supplied colours (Dataverse option colours are often pale) get their\n * text colour from `readableTextOn` instead of assuming white; a well-known client control\n * hard-coded white on yellow, which is ~1.4:1.\n */\nexport const GROUP_PALETTE: readonly string[] = [\n '#0f6cbd', // blue\n '#107c10', // green\n '#c50f1f', // red\n '#8764b8', // lavender\n '#ae4b00', // dark orange\n '#03787c', // teal\n '#4f52b2', // indigo\n '#8a5b00', // dark gold\n '#c0077d', // magenta\n '#5c2e91', // purple\n];\n\n/** FNV-1a — tiny, stable across engines, good enough spread for a 10-colour palette. */\nfunction hash(key: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < key.length; i++) {\n h ^= key.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\nexport function groupColor(key: string, palette: readonly string[] = GROUP_PALETTE): string {\n return palette[hash(key) % palette.length];\n}\n\n/** `#rgb` / `#rrggbb` → [r, g, b] 0-255, or null for anything else (named colours, var()). */\nexport function parseHex(color: string): [number, number, number] | null {\n const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color.trim());\n if (!m) return null;\n const h = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];\n return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16)) as [number, number, number];\n}\n\nfunction luminance([r, g, b]: [number, number, number]): number {\n const lin = (c: number): number => {\n const s = c / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);\n}\n\n/** WCAG contrast ratio between two hex colours, or null if either cannot be parsed. */\nexport function contrastRatio(a: string, b: string): number | null {\n const pa = parseHex(a);\n const pb = parseHex(b);\n if (!pa || !pb) return null;\n const [hi, lo] = [luminance(pa), luminance(pb)].sort((x, y) => y - x);\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/**\n * Black or white text, whichever reads better on `background`. Unparseable backgrounds\n * (a CSS variable, a named colour) get `undefined`, i.e. the theme's own event text colour.\n */\nexport function readableTextOn(background: string): '#000000' | '#ffffff' | undefined {\n const onWhite = contrastRatio(background, '#ffffff');\n const onBlack = contrastRatio(background, '#000000');\n if (onWhite === null || onBlack === null) return undefined;\n return onWhite >= onBlack ? '#ffffff' : '#000000';\n}\n","import { readableTextOn } from '../core/colors';\nimport { addDays, fromWall, kindOf, normaliseFloating, toInstant, toWall, utcFieldsToWall } from '../core/datetime';\nimport type {\n CalendarEvent,\n CalendarEventChange,\n CalendarValue,\n SlotSelection,\n ValueKind,\n VisibleRange,\n ZoneOffset,\n} from '../core/types';\n\n/**\n * The translation between the kit's contract and FullCalendar's. Pure (types only from here),\n * so every rule is unit-testable without rendering a calendar.\n *\n * ★★★ THE ZONE STRATEGY. FullCalendar is always run with `timeZone: 'UTC'` and fed WALL-CLOCK\n * values already shifted into the calendar's zone. Its `Date`s then carry the wall clock in\n * their UTC fields, and we shift back on the way out.\n *\n * Why not give FullCalendar the zone? Because a PCF host does not expose one. It exposes\n * `getTimeZoneOffsetMinutes(date)` — an offset per instant, no IANA name — and MEASURED on an\n * org, a user set to Tokyo in a Pacific browser saw an event FullCalendar placed on Monday 9:00\n * that the grid beside it said was Tuesday 1:00. The browser's zone is simply the wrong zone.\n * Shifting per instant with that function is also daylight-saving correct, which a fixed\n * `'+09:00'`-style offset would not be.\n */\n\nexport interface FcEventInput {\n id: string;\n title: string;\n start: string;\n end?: string;\n allDay: boolean;\n color?: string;\n textColor?: string;\n editable?: boolean;\n durationEditable?: boolean;\n /**\n * `kind` is how FullCalendar holds the values (`date` when shown all-day); `originalKind` is\n * what the host SENT, and what a change is converted back into — the \"an event keeps its kind\"\n * contract. They differ only for an instant/floating start forced all-day with `allDay: true`.\n */\n extendedProps: { source: CalendarEvent; kind: ValueKind; originalKind: ValueKind };\n}\n\nfunction toFcValue(value: CalendarValue | Date, zone: ZoneOffset): string {\n const k = kindOf(value);\n if (k === 'instant') return toWall(value, zone);\n if (k === 'floating') return normaliseFloating(value as string);\n return value as string;\n}\n\n/** The exclusive all-day end for any end value: a timed end that is not midnight rounds UP a day. */\nfunction exclusiveAllDayEnd(end: CalendarValue | Date, zone: ZoneOffset): string {\n const wall = toFcValue(end, zone);\n if (wall.length === 10) return wall;\n return wall.slice(11, 19) === '00:00:00' ? wall.slice(0, 10) : addDays(wall.slice(0, 10), 1);\n}\n\nexport function toFcEvent(event: CalendarEvent, zone: ZoneOffset): FcEventInput {\n const originalKind = kindOf(event.start);\n const allDay = event.allDay ?? originalKind === 'date';\n const start = allDay ? toFcValue(event.start, zone).slice(0, 10) : toFcValue(event.start, zone);\n const out: FcEventInput = {\n id: event.id,\n title: event.title,\n start,\n allDay,\n extendedProps: { source: event, kind: allDay ? 'date' : originalKind, originalKind },\n };\n // ★★ Optional keys are OMITTED, never set to `undefined`. FullCalendar treats a PRESENT key as a\n // value: `editable: undefined` resolves to `startEditable === false` (read back through\n // FullCalendar's API — see fullcalendar-contract.test.tsx), which locked EVERY event in an\n // editable calendar. Same family as the `slotMinTime={undefined}` crash in CalendarView.\n if (event.end !== undefined) out.end = allDay ? exclusiveAllDayEnd(event.end, zone) : toFcValue(event.end, zone);\n if (event.color) {\n out.color = event.color;\n const text = readableTextOn(event.color);\n if (text) out.textColor = text;\n }\n if (event.readOnly) out.editable = false;\n // ★ An event with no END is not resizable: a resize would invent an end the host has nowhere\n // to store (a map with no end column), and the calendar would keep showing a length that\n // was never saved. Moving it is still fine.\n else if (event.end === undefined) out.durationEditable = false;\n return out;\n}\n\n/** A FullCalendar `Date` (UTC fields = wall clock) → a value of `kind`. */\nexport function fromFcDate(d: Date, kind: ValueKind, zone: ZoneOffset): CalendarValue {\n const wall = utcFieldsToWall(d);\n if (kind === 'date') return wall.slice(0, 10);\n if (kind === 'floating') return wall;\n return fromWall(wall, zone);\n}\n\nexport interface FcChangedEventLike {\n start: Date | null;\n end: Date | null;\n allDay: boolean;\n extendedProps: Record<string, unknown>;\n}\n\n/** A calendar value of `kind` → the same moment in `target` kind (all-day midnight for dates). */\nfunction asKind(value: CalendarValue, from: ValueKind, target: ValueKind, zone: ZoneOffset): CalendarValue {\n if (from === target || from !== 'date') return value;\n return target === 'instant' ? fromWall(`${value}T00:00:00`, zone) : `${value}T00:00:00`;\n}\n\n/** The host's original value, canonicalised, for fields a change did not touch. */\nfunction canonical(value: CalendarValue | Date): CalendarValue {\n const k = kindOf(value);\n return k === 'instant' ? toInstant(value) : k === 'floating' ? normaliseFloating(value as string) : (value as string);\n}\n\n/** A dropped/resized FullCalendar event → a change in the ORIGINAL event's kind. */\nexport function toChange(\n fcEvent: FcChangedEventLike,\n kind: CalendarEventChange['kind'],\n zone: ZoneOffset\n): CalendarEventChange {\n const { source, kind: valueKind, originalKind } = fcEvent.extendedProps as FcEventInput['extendedProps'];\n if (!fcEvent.start) throw new Error(`FullCalendar returned no start for event ${source.id}`);\n const back = (d: Date): CalendarValue => asKind(fromFcDate(d, valueKind, zone), valueKind, originalKind ?? valueKind, zone);\n return {\n event: source,\n kind,\n // ★ A RESIZE never moves the start. Recomputing it from FullCalendar's wall-clock Date is\n // lossy on the fall-back day (the second 01:30 reads back as the first), and toWebApiPatch\n // writes `start` unconditionally — so resizing an end silently moved the start an hour.\n start: kind === 'resize' ? canonical(source.start) : back(fcEvent.start),\n end: fcEvent.end ? back(fcEvent.end) : undefined,\n allDay: fcEvent.allDay,\n };\n}\n\nexport function toSelection(start: Date, end: Date, allDay: boolean, zone: ZoneOffset): SlotSelection {\n const kind: ValueKind = allDay ? 'date' : 'instant';\n return { start: fromFcDate(start, kind, zone), end: fromFcDate(end, kind, zone), allDay };\n}\n\nexport function toVisibleRange(start: Date, end: Date, view: string, zone: ZoneOffset): VisibleRange {\n return {\n start: fromFcDate(start, 'instant', zone),\n end: fromFcDate(end, 'instant', zone),\n view,\n startDate: utcFieldsToWall(start).slice(0, 10),\n endDate: utcFieldsToWall(end).slice(0, 10),\n };\n}\n\n/** \"Now\" in the calendar's zone, for FullCalendar's `now` option (drives today + now-line). */\nexport const nowWall = (zone: ZoneOffset): string => toWall(new Date(), zone);\n\n/**\n * ★ A drag may not change an event's KIND. Dropping a timed appointment on the all-day row\n * would have to invent a date-only value for a date-time column (or the reverse), and there\n * is no right answer to invent. FullCalendar allows it by default; this forbids it.\n */\nexport const sameAllDayness = (spanAllDay: boolean, eventAllDay: boolean): boolean => spanAllDay === eventAllDay;\n\n/* ── Optimistic overrides ────────────────────────────────────────────────────────────────────\n * FullCalendar moves an event the moment it is dropped. The host's `events` prop still has the\n * OLD position until its save resolves AND it re-renders with fresh data — and a PCF host\n * re-renders on anything (resize, focus), so without this the event would snap back mid-save\n * and jump forward again later. An override holds the new position until the props agree.\n */\nexport interface PendingMove {\n start: CalendarValue;\n end: CalendarValue | undefined;\n allDay: boolean;\n /** Increments per change; only the newest change for an event may settle or revert it. */\n token: number;\n /**\n * Set when the save RESOLVED: the `events` prop at that moment. The move is dropped on the\n * first `events` that is a different array — whether or not it matches. Without this a save\n * whose value never comes back verbatim (no end column mapped, a plugin adjusting the time)\n * kept its override forever, showing a position that was never stored.\n */\n savedWith?: readonly CalendarEvent[];\n}\n\nconst same = (a: CalendarValue | Date | undefined, b: CalendarValue | undefined): boolean => {\n if (a === undefined || b === undefined) return a === b;\n const ka = kindOf(a);\n if (ka !== kindOf(b)) return false;\n if (ka === 'instant') return new Date(a).getTime() === new Date(b).getTime();\n return ka === 'floating' ? normaliseFloating(a as string) === normaliseFloating(b) : a === b;\n};\n\n/** Events with pending moves applied. */\nexport function applyPending(events: readonly CalendarEvent[], pending: ReadonlyMap<string, PendingMove>): CalendarEvent[] {\n if (pending.size === 0) return events as CalendarEvent[];\n return events.map((e) => {\n const p = pending.get(e.id);\n return p ? { ...e, start: p.start, end: p.end, allDay: p.allDay } : e;\n });\n}\n\n/**\n * Ids whose pending move can be dropped: the props now agree, the event vanished (deleted\n * elsewhere — no ghost), or the save resolved and the host has since re-rendered.\n */\nexport function settledIds(events: readonly CalendarEvent[], pending: ReadonlyMap<string, PendingMove>): string[] {\n const byId = new Map(events.map((e) => [e.id, e]));\n const done: string[] = [];\n for (const [id, p] of pending) {\n const e = byId.get(id);\n if (!e || (same(e.start, p.start) && same(e.end, p.end)) || (p.savedWith !== undefined && p.savedWith !== events)) done.push(id);\n }\n return done;\n}\n","import * as React from 'react';\nimport type { CalendarEvent, CalendarEventChange, ZoneOffset } from '../core/types';\nimport { settledIds, toChange, type FcChangedEventLike, type PendingMove } from './fcBridge';\n\n/** The part of FullCalendar's EventChangeInfo this hook uses — small enough to fake in a test. */\nexport interface ChangeInfoLike {\n event: FcChangedEventLike;\n revert(): void;\n}\n\nexport type ChangeHandler = (change: CalendarEventChange) => void | Promise<void>;\n\nfunction describe(error: unknown): string {\n const m = (error as { message?: unknown } | null)?.message;\n return typeof m === 'string' && m ? m : String(error);\n}\n\n/**\n * The optimistic-save state machine behind drag and resize, separated from the calendar so\n * every transition is testable without driving a real drag (which synthetic input could not do\n * against FullCalendar v7 — see CLAUDE.md).\n *\n * drop ──▶ pending(token) ──resolve──▶ saved (held until the host's next `events`)\n * └─reject/throw─▶ revert + banner (only if still the NEWEST change)\n */\nexport function useOptimisticChanges(\n events: readonly CalendarEvent[],\n onEventChange: ChangeHandler | undefined,\n onChangeError: ((error: unknown, change: CalendarEventChange) => void) | undefined,\n zoneRef: React.MutableRefObject<ZoneOffset>\n): {\n pending: ReadonlyMap<string, PendingMove>;\n error: string | null;\n dismissError: () => void;\n handleChange: (kind: CalendarEventChange['kind']) => (info: ChangeInfoLike) => void;\n} {\n const [pending, setPending] = React.useState<ReadonlyMap<string, PendingMove>>(() => new Map());\n const [error, setError] = React.useState<string | null>(null);\n const eventsRef = React.useRef(events);\n eventsRef.current = events;\n // ★ The newest token per event, held in a REF so a settling promise can ask \"am I still the\n // current change?\" synchronously. Asking inside a setState updater would be a side effect in\n // an updater, which React may run twice or late.\n const latest = React.useRef(new Map<string, number>());\n const seq = React.useRef(0);\n const mounted = React.useRef(true);\n React.useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n React.useEffect(() => {\n const done = settledIds(events, pending);\n if (done.length === 0) return;\n for (const id of done) if (latest.current.get(id) === pending.get(id)?.token) latest.current.delete(id);\n setPending((prev) => {\n const next = new Map(prev);\n for (const id of done) next.delete(id);\n return next;\n });\n }, [events, pending]);\n\n const handleChange = React.useCallback(\n (kind: CalendarEventChange['kind']) =>\n (info: ChangeInfoLike): void => {\n if (!onEventChange) {\n info.revert();\n return;\n }\n let change: CalendarEventChange;\n try {\n change = toChange(info.event, kind, zoneRef.current);\n } catch (e) {\n info.revert();\n setError(describe(e));\n return;\n }\n setError(null);\n const id = change.event.id;\n const token = ++seq.current;\n\n const fail = (err: unknown): void => {\n const current = latest.current.get(id) === token;\n if (current) latest.current.delete(id);\n // ★ onChangeError BEFORE revert, so a revert that throws (calendar already destroyed)\n // cannot swallow the host's error callback.\n onChangeError?.(err, change);\n if (!mounted.current) return;\n setError(`Could not save \"${change.event.title}\": ${describe(err)}`);\n if (!current) return; // a newer move of this event owns its position now\n setPending((prev) => {\n if (!prev.has(id)) return prev;\n const next = new Map(prev);\n next.delete(id);\n return next;\n });\n try {\n info.revert();\n } catch {\n /* the calendar was torn down under us; nothing left to put back */\n }\n };\n\n // ★ Registered BEFORE the handler runs: a synchronous throw calls `fail`, which asks \"am I\n // the current change?\" — registering after the call answered \"no\" and skipped the\n // revert (caught by the sync-throw test).\n latest.current.set(id, token);\n let result: void | Promise<void>;\n try {\n result = onEventChange(change);\n } catch (err) {\n // A handler that throws synchronously is a failed save, not an unhandled error that\n // leaves the event where it was dropped.\n fail(err);\n return;\n }\n if (!result || typeof (result as Promise<void>).then !== 'function') {\n // Fire-and-forget handler: nothing to hold; the host owns the position from here.\n if (latest.current.get(id) === token) latest.current.delete(id);\n return;\n }\n\n setPending((prev) => new Map(prev).set(id, { start: change.start, end: change.end, allDay: change.allDay, token }));\n (result as Promise<void>).then(\n () => {\n if (!mounted.current || latest.current.get(id) !== token) return;\n const savedWith = eventsRef.current;\n setPending((prev) => {\n const p = prev.get(id);\n if (!p || p.token !== token) return prev;\n return new Map(prev).set(id, { ...p, savedWith });\n });\n },\n fail\n );\n },\n [onEventChange, onChangeError, zoneRef]\n );\n\n return { pending, error, dismissError: React.useCallback(() => setError(null), []), handleChange };\n}\n","import type * as React from 'react';\n\n/**\n * The colours a calendar can be themed with, as plain CSS values (hex, rgb(), or `var(--x)`).\n * Each maps onto one of FullCalendar v7's classic-theme custom properties, so theming is just\n * setting variables on the calendar's own container — no stylesheet override, nothing global.\n *\n * ★ Scoped to the container on purpose: FullCalendar's palette declares its defaults on `:root`,\n * and a PCF control shares `:root` with the whole form. Two calendars on one form each set\n * their own, and neither touches the host.\n */\nexport interface CalendarTheme {\n primary?: string;\n primaryForeground?: string;\n background?: string;\n foreground?: string;\n mutedForeground?: string;\n faint?: string;\n muted?: string;\n strong?: string;\n border?: string;\n strongBorder?: string;\n today?: string;\n highlight?: string;\n now?: string;\n button?: string;\n buttonForeground?: string;\n buttonBorder?: string;\n buttonStrong?: string;\n buttonStrongBorder?: string;\n fontFamily?: string;\n fontSize?: string;\n /** Rendered as a banner above the calendar when a save fails. */\n error?: string;\n}\n\nconst VAR: Record<Exclude<keyof CalendarTheme, 'fontFamily' | 'fontSize' | 'error'>, string> = {\n primary: '--fc-classic-primary',\n primaryForeground: '--fc-classic-primary-foreground',\n background: '--fc-classic-background',\n foreground: '--fc-classic-foreground',\n mutedForeground: '--fc-classic-muted-foreground',\n faint: '--fc-classic-faint',\n muted: '--fc-classic-muted',\n strong: '--fc-classic-strong',\n border: '--fc-classic-border',\n strongBorder: '--fc-classic-strong-border',\n today: '--fc-classic-today',\n highlight: '--fc-classic-highlight',\n now: '--fc-classic-now',\n button: '--fc-classic-button',\n buttonForeground: '--fc-classic-button-foreground',\n buttonBorder: '--fc-classic-button-border',\n buttonStrong: '--fc-classic-button-strong',\n buttonStrongBorder: '--fc-classic-button-strong-border',\n};\n\n/** The container style that applies `theme`. Unset keys keep FullCalendar's defaults. */\nexport function themeStyle(theme: CalendarTheme | undefined): React.CSSProperties {\n const style: Record<string, string> = {};\n if (!theme) return style;\n for (const [key, cssVar] of Object.entries(VAR)) {\n const v = theme[key as keyof typeof VAR];\n if (v) style[cssVar] = v;\n }\n if (theme.fontFamily) style.fontFamily = theme.fontFamily;\n if (theme.fontSize) style.fontSize = theme.fontSize;\n if (theme.background) style.background = theme.background;\n if (theme.foreground) style.color = theme.foreground;\n return style as React.CSSProperties;\n}\n","import * as React from 'react';\nimport type { CalendarDataSource, CalendarEvent, CalendarEventChange, SlotSelection, VisibleRange } from '../core/types';\n\nexport interface CalendarSourceBinding {\n events: readonly CalendarEvent[];\n loading: boolean;\n /** The last load failure, or null. A failed load keeps the previous events on screen. */\n error: string | null;\n /** Re-run the current range (e.g. after booking a slot elsewhere on the page). */\n reload: () => void;\n /** Spread onto `<CalendarView>` / `<FluentCalendar>`. */\n props: {\n events: readonly CalendarEvent[];\n loading: boolean;\n editable: boolean;\n selectable: boolean;\n onRangeChange: (range: VisibleRange) => void;\n onEventChange?: (change: CalendarEventChange) => Promise<void>;\n onEventClick?: (event: CalendarEvent) => void;\n onSelectRange?: (slot: SlotSelection) => void;\n };\n}\n\nfunction describe(error: unknown): string {\n const m = (error as { message?: unknown } | null)?.message;\n return typeof m === 'string' && m ? m : String(error);\n}\n\nconst isAbort = (e: unknown): boolean => (e as { name?: unknown } | null)?.name === 'AbortError';\n\n/**\n * Drive a calendar from a PULLED `CalendarDataSource` (code app, Power Pages): load on every range\n * change, save a drag through the source, then reload so the calendar shows what the server kept.\n *\n * ★ Only the NEWEST load may land. Ranges change faster than networks answer (click \"next\" three\n * times), so an older response arriving last must not overwrite the newer week. Each load aborts\n * its predecessor AND is sequence-checked — a source that ignores `signal` is still safe.\n * ★ A save that rejects is RE-THROWN, so the calendar's optimistic layer reverts the event and shows\n * the message; swallowing it here would leave the event where it was dropped, unsaved.\n */\nexport function useCalendarSource(source: CalendarDataSource | null | undefined): CalendarSourceBinding {\n const [events, setEvents] = React.useState<readonly CalendarEvent[]>([]);\n const [loading, setLoading] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n const range = React.useRef<VisibleRange | null>(null);\n const seq = React.useRef(0);\n const inflight = React.useRef<AbortController | null>(null);\n\n const load = React.useCallback(async (): Promise<void> => {\n const r = range.current;\n if (!source || !r) return;\n inflight.current?.abort();\n const controller = new AbortController();\n inflight.current = controller;\n const mine = ++seq.current;\n setLoading(true);\n try {\n const next = await source.load(r, controller.signal);\n if (mine !== seq.current) return;\n setEvents(next);\n setError(null);\n } catch (e) {\n if (mine !== seq.current || isAbort(e)) return;\n setError(describe(e));\n } finally {\n if (mine === seq.current) setLoading(false);\n }\n }, [source]);\n\n // A new source (different filter, different table) re-queries the range already on screen.\n React.useEffect(() => {\n setEvents([]);\n void load();\n }, [load]);\n\n React.useEffect(\n () => () => {\n seq.current++;\n inflight.current?.abort();\n },\n []\n );\n\n const onRangeChange = React.useCallback(\n (r: VisibleRange) => {\n const prev = range.current;\n range.current = r;\n if (prev && prev.start === r.start && prev.end === r.end) return;\n void load();\n },\n [load]\n );\n\n const reload = React.useCallback(() => void load(), [load]);\n\n const onEventChange = React.useMemo(\n () =>\n source?.capabilities.update && source.update\n ? async (change: CalendarEventChange): Promise<void> => {\n await source.update!(change);\n await load();\n }\n : undefined,\n [source, load]\n );\n\n const open = React.useMemo(\n () => (source?.capabilities.open && source.open ? (e: CalendarEvent) => source.open!(e) : undefined),\n [source]\n );\n const onSelectRange = React.useMemo(\n () =>\n source?.capabilities.create && source.create\n ? (slot: SlotSelection) => {\n void Promise.resolve()\n .then(() => source.create!(slot))\n .then(\n () => load(),\n (e: unknown) => setError(describe(e))\n );\n }\n : undefined,\n [source, load]\n );\n\n return {\n events,\n loading,\n error,\n reload,\n props: {\n events,\n loading,\n editable: !!onEventChange,\n selectable: !!onSelectRange,\n onRangeChange,\n ...(onEventChange ? { onEventChange } : {}),\n ...(open ? { onEventClick: open } : {}),\n ...(onSelectRange ? { onSelectRange } : {}),\n },\n };\n}\n"],"mappings":"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","names":["React","t","React","describe"]}
1
+ {"version":3,"sources":["../src/view/CalendarView.tsx","../src/core/datetime.ts","../src/core/colors.ts","../src/view/fcBridge.ts","../src/view/useOptimisticChanges.ts","../src/view/theme.ts","../src/view/useCalendarSource.ts"],"sourcesContent":["import * as React from 'react';\nimport FullCalendar from '@fullcalendar/react';\nimport dayGridPlugin from '@fullcalendar/react/daygrid';\nimport timeGridPlugin from '@fullcalendar/react/timegrid';\nimport listPlugin from '@fullcalendar/react/list';\nimport interactionPlugin from '@fullcalendar/react/interaction';\nimport classicThemePlugin from '@fullcalendar/react/themes/classic';\nimport type {\n DateSelectInfo,\n DatesSetInfo,\n EventChangeInfo,\n EventClickInfo,\n} from '@fullcalendar/react';\n\nimport '@fullcalendar/react/skeleton.css';\nimport '@fullcalendar/react/themes/classic/theme.css';\nimport '@fullcalendar/react/themes/classic/palette.css';\n\nimport { browserZone } from '../core/datetime';\nimport type { BusinessHours, CalendarEvent, CalendarEventChange, SlotSelection, VisibleRange, ZoneOffset } from '../core/types';\nimport { applyPending, nowWall, sameAllDayness, toFcEvent, toSelection, toVisibleRange } from './fcBridge';\nimport { useOptimisticChanges, type ChangeInfoLike } from './useOptimisticChanges';\nimport { themeStyle, type CalendarTheme } from './theme';\n\n/** The views a host can ask for, by plain name. */\nexport type CalendarViewName = 'month' | 'week' | 'workWeek' | 'day' | 'list';\n\nconst FC_VIEW: Record<CalendarViewName, string> = {\n month: 'dayGridMonth',\n week: 'timeGridWeek',\n workWeek: 'timeGridWorkWeek',\n day: 'timeGridDay',\n list: 'listWeek',\n};\n\nconst VIEW_LABEL: Record<CalendarViewName, string> = {\n month: 'Month',\n week: 'Week',\n workWeek: 'Work week',\n day: 'Day',\n list: 'List',\n};\n\nexport interface CalendarViewProps {\n /** Plain event objects. See `CalendarEvent` for how a value's shape sets its kind. */\n events: readonly CalendarEvent[];\n /** The zone to show instants in. Default: the browser's. A PCF host passes the USER's. */\n zone?: ZoneOffset;\n /** Starting view. Default `week`. */\n view?: CalendarViewName;\n /** View buttons offered, in order. Default: month, week, day, list. */\n views?: readonly CalendarViewName[];\n /** `YYYY-MM-DD` to open on. Default today (in `zone`). */\n initialDate?: string;\n /** Allow drag and resize. Default false — read-only is the safe default. */\n editable?: boolean;\n /** Allow selecting an empty range (→ `onSelectRange`). Default: true when a handler is given. */\n selectable?: boolean;\n businessHours?: readonly BusinessHours[];\n /** 0 = Sunday. */\n firstDay?: number;\n /** `HH:mm:ss` bounds of the time grid. */\n slotMinTime?: string;\n slotMaxTime?: string;\n /** Where the time grid first scrolls to. Default `08:00:00`. */\n scrollTime?: string;\n /** Any CSS height, `'auto'`, or a number of pixels. Default `'auto'`. */\n height?: number | string;\n theme?: CalendarTheme;\n /** Shown while data for a new range loads. */\n loading?: boolean;\n className?: string;\n /**\n * Classes for FullCalendar's own parts. ★ v7 HASHES its class names (`fc-classic-1El`), so a\n * descendant selector like `.fc-button` matches NOTHING — measured: 0 of 7 buttons. These go\n * through FullCalendar's `buttonClass` / `headerToolbarClass` options instead.\n */\n classNames?: { button?: string; toolbar?: string };\n /** Accessible name for the calendar region. */\n 'aria-label'?: string;\n\n onEventClick?: (event: CalendarEvent) => void;\n onSelectRange?: (selection: SlotSelection) => void;\n /**\n * A move or resize. Return a promise to make it OPTIMISTIC: the event stays where it was\n * dropped while the promise runs, and goes back — with a message — if it rejects.\n */\n onEventChange?: (change: CalendarEventChange) => void | Promise<void>;\n onRangeChange?: (range: VisibleRange) => void;\n /** Called after a failed change has been reverted. The banner is shown either way. */\n onChangeError?: (error: unknown, change: CalendarEventChange) => void;\n}\n\n/** FullCalendar's change info → the hook's minimal shape (what the hook reads, and nothing else). */\nconst asChangeInfo = (info: EventChangeInfo): ChangeInfoLike => ({\n event: {\n start: info.event.start as Date | null,\n end: info.event.end as Date | null,\n allDay: info.event.allDay,\n extendedProps: info.event.extendedProps,\n },\n revert: () => info.revert(),\n});\n\nconst bannerStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '6px 10px',\n marginBottom: 6,\n borderRadius: 4,\n border: '1px solid currentColor',\n};\n\n/**\n * FullCalendar v7 (MIT views only), fed plain events. Knows nothing about Dataverse and nothing\n * about Fluent — this entry is what a Power Pages site uses. `FluentCalendar` (`./v9`) wraps it\n * with Fluent tokens.\n *\n * ★ Nothing here portals and nothing uses tabster, so it cannot join the Fluent/tabster\n * singleton race that has taken down PCF controls on model-driven forms. The error banner is\n * in-tree for the same reason: a Toast or Dialog would portal to `document.body`.\n */\nexport function CalendarView(props: CalendarViewProps): React.ReactElement {\n const zone = props.zone ?? browserZone;\n const zoneRef = React.useRef(zone);\n zoneRef.current = zone;\n\n const { pending, error, dismissError, handleChange } = useOptimisticChanges(\n props.events,\n props.onEventChange,\n props.onChangeError,\n zoneRef\n );\n\n const fcEvents = React.useMemo(\n () => applyPending(props.events, pending).map((e) => toFcEvent(e, zone)),\n [props.events, pending, zone]\n );\n\n const views = props.views ?? (['month', 'week', 'day', 'list'] as const);\n\n /**\n * ★★ Options the host did not set are OMITTED, never passed as `undefined`. FullCalendar's\n * option refiners treat a present-but-undefined key as a value: `slotMinTime={undefined}`\n * replaced the default and the time-grid views threw \"Cannot read properties of undefined\n * (reading 'years')\" on first render. The list view never reads it, so a list-only test\n * was green — found by rendering the WEEK view.\n */\n const optional: Record<string, unknown> = {};\n if (props.firstDay !== undefined) optional.firstDay = props.firstDay;\n if (props.slotMinTime !== undefined) optional.slotMinTime = props.slotMinTime;\n if (props.slotMaxTime !== undefined) optional.slotMaxTime = props.slotMaxTime;\n if (props.initialDate !== undefined) optional.initialDate = props.initialDate;\n if (props.classNames?.button) optional.buttonClass = props.classNames.button;\n if (props.classNames?.toolbar) optional.headerToolbarClass = props.classNames.toolbar;\n if (props.businessHours) optional.businessHours = props.businessHours.map((b) => ({ ...b, daysOfWeek: [...b.daysOfWeek] }));\n const selectable = props.selectable ?? !!props.onSelectRange;\n\n return (\n <div\n className={props.className}\n style={themeStyle(props.theme)}\n role=\"region\"\n aria-label={props['aria-label'] ?? 'Calendar'}\n aria-busy={props.loading || undefined}\n data-calendar-kit=\"\"\n >\n {error && (\n <div role=\"alert\" style={{ ...bannerStyle, color: props.theme?.error ?? '#a4262c' }}>\n <span style={{ flex: 1 }}>{error}</span>\n <button type=\"button\" onClick={dismissError} aria-label=\"Dismiss\" style={{ font: 'inherit' }}>\n ×\n </button>\n </div>\n )}\n <FullCalendar\n plugins={[dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin, classicThemePlugin]}\n timeZone=\"UTC\"\n now={() => nowWall(zoneRef.current)}\n initialView={FC_VIEW[props.view ?? 'week']}\n views={{ timeGridWorkWeek: { type: 'timeGridWeek', hiddenDays: [0, 6] } }}\n headerToolbar={{\n start: 'today prev,next',\n center: 'title',\n end: views.map((v) => FC_VIEW[v]).join(','),\n }}\n // ★ v7 renamed v6's `buttonText` to `buttons: { name: { text } }`, keyed by VIEW name.\n buttons={{\n today: { text: 'Today' },\n ...Object.fromEntries(views.map((v) => [FC_VIEW[v], { text: VIEW_LABEL[v] }])),\n }}\n height={props.height ?? 'auto'}\n events={fcEvents}\n editable={!!props.editable && !!props.onEventChange}\n selectable={selectable}\n selectMirror\n nowIndicator\n dayMaxEvents\n {...optional}\n scrollTime={props.scrollTime ?? '08:00:00'}\n eventAllow={(span, moving) => (moving ? sameAllDayness(span.allDay, moving.allDay) : true)}\n datesSet={(info: DatesSetInfo) =>\n props.onRangeChange?.(toVisibleRange(info.start as Date, info.end as Date, info.view.type, zoneRef.current))\n }\n eventClick={(info: EventClickInfo) => {\n info.jsEvent.preventDefault();\n const source = (info.event.extendedProps as { source?: CalendarEvent }).source;\n if (source) props.onEventClick?.(source);\n }}\n select={(info: DateSelectInfo) => {\n props.onSelectRange?.(toSelection(info.start as Date, info.end as Date, info.allDay, zoneRef.current));\n info.view.calendar.unselect();\n }}\n eventDrop={(info: EventChangeInfo) => handleChange('move')(asChangeInfo(info))}\n eventResize={(info: EventChangeInfo) => handleChange('resize')(asChangeInfo(info))}\n />\n </div>\n );\n}\n","import type { CalendarValue, ValueKind, ZoneOffset } from './types';\n\n/**\n * Value-kind detection and zone conversion.\n *\n * ★★ Nothing in this file may read the PROCESS time zone. Every `Date` is built and read through\n * its UTC fields, so the same input gives the same output on a laptop in Auckland and a CI\n * runner in UTC. `npm test` runs the suite under three TZ values to prove it — a single-zone\n * run cannot see a leak, because the developer's zone is the one the code was written in.\n */\n\nconst DATE_RE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst FLOATING_RE = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d{1,9})?)?$/;\nconst INSTANT_RE = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?(?:Z|[+-]\\d{2}:?\\d{2})$/i;\n\nconst pad = (n: number, w = 2): string => String(n).padStart(w, '0');\n\n/** The browser's own zone, east-positive. The default when a host supplies none. */\nexport const browserZone: ZoneOffset = (instant) => -instant.getTimezoneOffset();\n\n/** A fixed zone (no daylight saving). Mostly for tests and demos. */\nexport const fixedZone =\n (minutesEast: number): ZoneOffset =>\n () =>\n minutesEast;\n\nexport function kindOf(value: CalendarValue | Date): ValueKind {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) throw new RangeError('Invalid Date');\n return 'instant';\n }\n const local = DATE_RE.test(value) ? 'date' : FLOATING_RE.test(value) ? 'floating' : null;\n if (local) {\n // ★ The regex accepts `2026-02-31`; Date.UTC would silently roll it to March 3rd. Reject\n // any date whose fields do not survive a round trip.\n const m = (FLOATING_RE.exec(value) ?? DATE_RE.exec(value))!;\n const [, y, mo, d, h = '0', mi = '0', sec = '0'] = m;\n const t = new Date(Date.UTC(+y, +mo - 1, +d, +h, +mi, +sec));\n if (t.getUTCFullYear() !== +y || t.getUTCMonth() !== +mo - 1 || t.getUTCDate() !== +d || +h > 23 || +mi > 59 || +sec > 59) {\n throw new RangeError(`Not a real calendar date or time: ${JSON.stringify(value)}`);\n }\n return local;\n }\n if (INSTANT_RE.test(value) && !Number.isNaN(new Date(value).getTime())) return 'instant';\n throw new RangeError(`Not an ISO-8601 date, wall-clock time or instant: ${JSON.stringify(value)}`);\n}\n\n/** An instant as canonical UTC ISO text (`…T…Z`, milliseconds kept). */\nexport function toInstant(value: CalendarValue | Date): CalendarValue {\n if (value instanceof Date) return value.toISOString();\n if (kindOf(value) !== 'instant') throw new RangeError(`Not an instant: ${value}`);\n return new Date(value).toISOString();\n}\n\n/** `Date.UTC` from wall-clock fields — i.e. the wall clock treated AS IF it were UTC. */\nfunction wallMillis(wall: string): number {\n const m = FLOATING_RE.exec(wall) ?? DATE_RE.exec(wall);\n if (!m) throw new RangeError(`Not a date or wall-clock time: ${wall}`);\n const [, y, mo, d, h = '0', mi = '0', s = '0'] = m;\n return Date.UTC(+y, +mo - 1, +d, +h, +mi, +s);\n}\n\nfunction formatWall(ms: number): string {\n const d = new Date(ms);\n return (\n `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}` +\n `T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`\n );\n}\n\n/** The wall-clock reading of an instant in `zone`, as floating text (`YYYY-MM-DDTHH:mm:ss`). */\nexport function toWall(instant: CalendarValue | Date, zone: ZoneOffset): CalendarValue {\n const at = instant instanceof Date ? instant : new Date(toInstant(instant));\n return formatWall(at.getTime() + zone(at) * 60_000);\n}\n\n/**\n * The instant at which `zone`'s clocks read `wall`.\n *\n * The offset depends on the instant being solved for, so this takes the offset in force a day\n * BEFORE and a day AFTER the wall reading (no zone changes offset twice in 48 h) and tries both:\n * - an ordinary time: both give the same instant;\n * - a wall time that exists TWICE (fall-back): both round-trip, and the EARLIER is returned;\n * - a wall time that DOES NOT EXIST (spring-forward gap): neither round-trips, and the\n * pre-transition offset is used, which pushes it forward by the gap (02:30 → 03:30), as JS\n * `Date` does. What Dataverse does with one is UNMEASURED.\n *\n * ★ A reviewer caught the first version returning the LATER occurrence east of UTC (Berlin) and\n * the earlier one west of it (Pacific): the two-pass guess settled on whichever offset the\n * first guess happened to land in. Tests now pin both hemispheres with synthetic zones.\n */\nexport function fromWall(wall: CalendarValue, zone: ZoneOffset): CalendarValue {\n const w = wallMillis(wall);\n const before = zone(new Date(w - 86_400_000));\n const after = zone(new Date(w + 86_400_000));\n const fits = (offset: number): number | null => {\n const t = w - offset * 60_000;\n return zone(new Date(t)) === offset ? t : null;\n };\n const candidates = [fits(before), fits(after)].filter((t): t is number => t !== null);\n const t = candidates.length > 0 ? Math.min(...candidates) : w - before * 60_000;\n return new Date(t).toISOString();\n}\n\n/** The calendar day (`YYYY-MM-DD`) of any value, in `zone` for instants. */\nexport function toDate(value: CalendarValue | Date, zone: ZoneOffset): CalendarValue {\n const k = kindOf(value);\n if (k === 'date') return value as string;\n if (k === 'floating') return (value as string).slice(0, 10);\n return toWall(value, zone).slice(0, 10);\n}\n\n/** Calendar-day arithmetic on a `YYYY-MM-DD` value (no zone involved). */\nexport function addDays(date: CalendarValue, days: number): CalendarValue {\n if (kindOf(date) !== 'date') throw new RangeError(`addDays needs a date, got ${date}`);\n return formatWall(wallMillis(date) + days * 86_400_000).slice(0, 10);\n}\n\n/**\n * The UTC fields of a `Date` as floating text. For reading a `Date` that some library has\n * produced in a \"UTC-as-wall-clock\" convention (FullCalendar with `timeZone: 'UTC'`).\n */\nexport function utcFieldsToWall(d: Date): CalendarValue {\n return formatWall(d.getTime());\n}\n\n/** Normalise floating text to seconds precision (`…T09:00` → `…T09:00:00`). */\nexport function normaliseFloating(value: CalendarValue): CalendarValue {\n return formatWall(wallMillis(value));\n}\n","/**\n * Event colours.\n *\n * ★ Without FullCalendar Premium there are no resource lanes, so \"appointments by location\"\n * is expressed as ONE COLOUR PER GROUP. `groupColor` is deterministic (same key → same colour\n * on every load and every machine), which is what lets a person learn the legend.\n * ★ Every palette entry carries white text at >= 4.5:1 (WCAG AA, normal text) — asserted by a\n * test, not by eye. Host-supplied colours (Dataverse option colours are often pale) get their\n * text colour from `readableTextOn` instead of assuming white; a well-known client control\n * hard-coded white on yellow, which is ~1.4:1.\n */\nexport const GROUP_PALETTE: readonly string[] = [\n '#0f6cbd', // blue\n '#107c10', // green\n '#c50f1f', // red\n '#8764b8', // lavender\n '#ae4b00', // dark orange\n '#03787c', // teal\n '#4f52b2', // indigo\n '#8a5b00', // dark gold\n '#c0077d', // magenta\n '#5c2e91', // purple\n];\n\n/** FNV-1a — tiny, stable across engines, good enough spread for a 10-colour palette. */\nfunction hash(key: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < key.length; i++) {\n h ^= key.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\nexport function groupColor(key: string, palette: readonly string[] = GROUP_PALETTE): string {\n return palette[hash(key) % palette.length];\n}\n\n/** `#rgb` / `#rrggbb` → [r, g, b] 0-255, or null for anything else (named colours, var()). */\nexport function parseHex(color: string): [number, number, number] | null {\n const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color.trim());\n if (!m) return null;\n const h = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];\n return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16)) as [number, number, number];\n}\n\nfunction luminance([r, g, b]: [number, number, number]): number {\n const lin = (c: number): number => {\n const s = c / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);\n}\n\n/** WCAG contrast ratio between two hex colours, or null if either cannot be parsed. */\nexport function contrastRatio(a: string, b: string): number | null {\n const pa = parseHex(a);\n const pb = parseHex(b);\n if (!pa || !pb) return null;\n const [hi, lo] = [luminance(pa), luminance(pb)].sort((x, y) => y - x);\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/**\n * Black or white text, whichever reads better on `background`. Unparseable backgrounds\n * (a CSS variable, a named colour) get `undefined`, i.e. the theme's own event text colour.\n */\nexport function readableTextOn(background: string): '#000000' | '#ffffff' | undefined {\n const onWhite = contrastRatio(background, '#ffffff');\n const onBlack = contrastRatio(background, '#000000');\n if (onWhite === null || onBlack === null) return undefined;\n return onWhite >= onBlack ? '#ffffff' : '#000000';\n}\n","import { readableTextOn } from '../core/colors';\nimport { addDays, fromWall, kindOf, normaliseFloating, toInstant, toWall, utcFieldsToWall } from '../core/datetime';\nimport type {\n CalendarEvent,\n CalendarEventChange,\n CalendarValue,\n SlotSelection,\n ValueKind,\n VisibleRange,\n ZoneOffset,\n} from '../core/types';\n\n/**\n * The translation between the kit's contract and FullCalendar's. Pure (types only from here),\n * so every rule is unit-testable without rendering a calendar.\n *\n * ★★★ THE ZONE STRATEGY. FullCalendar is always run with `timeZone: 'UTC'` and fed WALL-CLOCK\n * values already shifted into the calendar's zone. Its `Date`s then carry the wall clock in\n * their UTC fields, and we shift back on the way out.\n *\n * Why not give FullCalendar the zone? Because a PCF host does not expose one. It exposes\n * `getTimeZoneOffsetMinutes(date)` — an offset per instant, no IANA name — and MEASURED on an\n * org, a user set to Tokyo in a Pacific browser saw an event FullCalendar placed on Monday 9:00\n * that the grid beside it said was Tuesday 1:00. The browser's zone is simply the wrong zone.\n * Shifting per instant with that function is also daylight-saving correct, which a fixed\n * `'+09:00'`-style offset would not be.\n */\n\nexport interface FcEventInput {\n id: string;\n title: string;\n start: string;\n end?: string;\n allDay: boolean;\n color?: string;\n textColor?: string;\n editable?: boolean;\n durationEditable?: boolean;\n /**\n * `kind` is how FullCalendar holds the values (`date` when shown all-day); `originalKind` is\n * what the host SENT, and what a change is converted back into — the \"an event keeps its kind\"\n * contract. They differ only for an instant/floating start forced all-day with `allDay: true`.\n */\n extendedProps: { source: CalendarEvent; kind: ValueKind; originalKind: ValueKind };\n}\n\nfunction toFcValue(value: CalendarValue | Date, zone: ZoneOffset): string {\n const k = kindOf(value);\n if (k === 'instant') return toWall(value, zone);\n if (k === 'floating') return normaliseFloating(value as string);\n return value as string;\n}\n\n/** The exclusive all-day end for any end value: a timed end that is not midnight rounds UP a day. */\nfunction exclusiveAllDayEnd(end: CalendarValue | Date, zone: ZoneOffset): string {\n const wall = toFcValue(end, zone);\n if (wall.length === 10) return wall;\n return wall.slice(11, 19) === '00:00:00' ? wall.slice(0, 10) : addDays(wall.slice(0, 10), 1);\n}\n\nexport function toFcEvent(event: CalendarEvent, zone: ZoneOffset): FcEventInput {\n const originalKind = kindOf(event.start);\n const allDay = event.allDay ?? originalKind === 'date';\n const start = allDay ? toFcValue(event.start, zone).slice(0, 10) : toFcValue(event.start, zone);\n const out: FcEventInput = {\n id: event.id,\n title: event.title,\n start,\n allDay,\n extendedProps: { source: event, kind: allDay ? 'date' : originalKind, originalKind },\n };\n // ★★ Optional keys are OMITTED, never set to `undefined`. FullCalendar treats a PRESENT key as a\n // value: `editable: undefined` resolves to `startEditable === false` (read back through\n // FullCalendar's API — see fullcalendar-contract.test.tsx), which locked EVERY event in an\n // editable calendar. Same family as the `slotMinTime={undefined}` crash in CalendarView.\n if (event.end !== undefined) out.end = allDay ? exclusiveAllDayEnd(event.end, zone) : toFcValue(event.end, zone);\n if (event.color) {\n out.color = event.color;\n const text = readableTextOn(event.color);\n if (text) out.textColor = text;\n }\n if (event.readOnly) out.editable = false;\n // ★ An event with no END is not resizable: a resize would invent an end the host has nowhere\n // to store (a map with no end column), and the calendar would keep showing a length that\n // was never saved. Moving it is still fine.\n else if (event.end === undefined) out.durationEditable = false;\n return out;\n}\n\n/** A FullCalendar `Date` (UTC fields = wall clock) → a value of `kind`. */\nexport function fromFcDate(d: Date, kind: ValueKind, zone: ZoneOffset): CalendarValue {\n const wall = utcFieldsToWall(d);\n if (kind === 'date') return wall.slice(0, 10);\n if (kind === 'floating') return wall;\n return fromWall(wall, zone);\n}\n\nexport interface FcChangedEventLike {\n start: Date | null;\n end: Date | null;\n allDay: boolean;\n extendedProps: Record<string, unknown>;\n}\n\n/** A calendar value of `kind` → the same moment in `target` kind (all-day midnight for dates). */\nfunction asKind(value: CalendarValue, from: ValueKind, target: ValueKind, zone: ZoneOffset): CalendarValue {\n if (from === target || from !== 'date') return value;\n return target === 'instant' ? fromWall(`${value}T00:00:00`, zone) : `${value}T00:00:00`;\n}\n\n/** The host's original value, canonicalised, for fields a change did not touch. */\nfunction canonical(value: CalendarValue | Date): CalendarValue {\n const k = kindOf(value);\n return k === 'instant' ? toInstant(value) : k === 'floating' ? normaliseFloating(value as string) : (value as string);\n}\n\n/** A dropped/resized FullCalendar event → a change in the ORIGINAL event's kind. */\nexport function toChange(\n fcEvent: FcChangedEventLike,\n kind: CalendarEventChange['kind'],\n zone: ZoneOffset\n): CalendarEventChange {\n const { source, kind: valueKind, originalKind } = fcEvent.extendedProps as FcEventInput['extendedProps'];\n if (!fcEvent.start) throw new Error(`FullCalendar returned no start for event ${source.id}`);\n const back = (d: Date): CalendarValue => asKind(fromFcDate(d, valueKind, zone), valueKind, originalKind ?? valueKind, zone);\n return {\n event: source,\n kind,\n // ★ A RESIZE never moves the start. Recomputing it from FullCalendar's wall-clock Date is\n // lossy on the fall-back day (the second 01:30 reads back as the first), and toWebApiPatch\n // writes `start` unconditionally — so resizing an end silently moved the start an hour.\n start: kind === 'resize' ? canonical(source.start) : back(fcEvent.start),\n end: fcEvent.end ? back(fcEvent.end) : undefined,\n allDay: fcEvent.allDay,\n };\n}\n\nexport function toSelection(start: Date, end: Date, allDay: boolean, zone: ZoneOffset): SlotSelection {\n const kind: ValueKind = allDay ? 'date' : 'instant';\n return { start: fromFcDate(start, kind, zone), end: fromFcDate(end, kind, zone), allDay };\n}\n\nexport function toVisibleRange(start: Date, end: Date, view: string, zone: ZoneOffset): VisibleRange {\n return {\n start: fromFcDate(start, 'instant', zone),\n end: fromFcDate(end, 'instant', zone),\n view,\n startDate: utcFieldsToWall(start).slice(0, 10),\n endDate: utcFieldsToWall(end).slice(0, 10),\n };\n}\n\n/** \"Now\" in the calendar's zone, for FullCalendar's `now` option (drives today + now-line). */\nexport const nowWall = (zone: ZoneOffset): string => toWall(new Date(), zone);\n\n/**\n * ★ A drag may not change an event's KIND. Dropping a timed appointment on the all-day row\n * would have to invent a date-only value for a date-time column (or the reverse), and there\n * is no right answer to invent. FullCalendar allows it by default; this forbids it.\n */\nexport const sameAllDayness = (spanAllDay: boolean, eventAllDay: boolean): boolean => spanAllDay === eventAllDay;\n\n/* ── Optimistic overrides ────────────────────────────────────────────────────────────────────\n * FullCalendar moves an event the moment it is dropped. The host's `events` prop still has the\n * OLD position until its save resolves AND it re-renders with fresh data — and a PCF host\n * re-renders on anything (resize, focus), so without this the event would snap back mid-save\n * and jump forward again later. An override holds the new position until the props agree.\n */\nexport interface PendingMove {\n start: CalendarValue;\n end: CalendarValue | undefined;\n allDay: boolean;\n /** Increments per change; only the newest change for an event may settle or revert it. */\n token: number;\n /**\n * Set when the save RESOLVED: the `events` prop at that moment. The move is dropped on the\n * first `events` that is a different array — whether or not it matches. Without this a save\n * whose value never comes back verbatim (no end column mapped, a plugin adjusting the time)\n * kept its override forever, showing a position that was never stored.\n */\n savedWith?: readonly CalendarEvent[];\n}\n\nconst same = (a: CalendarValue | Date | undefined, b: CalendarValue | undefined): boolean => {\n if (a === undefined || b === undefined) return a === b;\n const ka = kindOf(a);\n if (ka !== kindOf(b)) return false;\n if (ka === 'instant') return new Date(a).getTime() === new Date(b).getTime();\n return ka === 'floating' ? normaliseFloating(a as string) === normaliseFloating(b) : a === b;\n};\n\n/** Events with pending moves applied. */\nexport function applyPending(events: readonly CalendarEvent[], pending: ReadonlyMap<string, PendingMove>): CalendarEvent[] {\n if (pending.size === 0) return events as CalendarEvent[];\n return events.map((e) => {\n const p = pending.get(e.id);\n return p ? { ...e, start: p.start, end: p.end, allDay: p.allDay } : e;\n });\n}\n\n/**\n * Ids whose pending move can be dropped: the props now agree, the event vanished (deleted\n * elsewhere — no ghost), or the save resolved and the host has since re-rendered.\n */\nexport function settledIds(events: readonly CalendarEvent[], pending: ReadonlyMap<string, PendingMove>): string[] {\n const byId = new Map(events.map((e) => [e.id, e]));\n const done: string[] = [];\n for (const [id, p] of pending) {\n const e = byId.get(id);\n if (!e || (same(e.start, p.start) && same(e.end, p.end)) || (p.savedWith !== undefined && p.savedWith !== events)) done.push(id);\n }\n return done;\n}\n","import * as React from 'react';\nimport type { CalendarEvent, CalendarEventChange, ZoneOffset } from '../core/types';\nimport { settledIds, toChange, type FcChangedEventLike, type PendingMove } from './fcBridge';\n\n/** The part of FullCalendar's EventChangeInfo this hook uses — small enough to fake in a test. */\nexport interface ChangeInfoLike {\n event: FcChangedEventLike;\n revert(): void;\n}\n\nexport type ChangeHandler = (change: CalendarEventChange) => void | Promise<void>;\n\nfunction describe(error: unknown): string {\n const m = (error as { message?: unknown } | null)?.message;\n return typeof m === 'string' && m ? m : String(error);\n}\n\n/**\n * The optimistic-save state machine behind drag and resize, separated from the calendar so\n * every transition is testable without driving a real drag (which synthetic input could not do\n * against FullCalendar v7 — see CLAUDE.md).\n *\n * drop ──▶ pending(token) ──resolve──▶ saved (held until the host's next `events`)\n * └─reject/throw─▶ revert + banner (only if still the NEWEST change)\n */\nexport function useOptimisticChanges(\n events: readonly CalendarEvent[],\n onEventChange: ChangeHandler | undefined,\n onChangeError: ((error: unknown, change: CalendarEventChange) => void) | undefined,\n zoneRef: React.MutableRefObject<ZoneOffset>\n): {\n pending: ReadonlyMap<string, PendingMove>;\n error: string | null;\n dismissError: () => void;\n handleChange: (kind: CalendarEventChange['kind']) => (info: ChangeInfoLike) => void;\n} {\n const [pending, setPending] = React.useState<ReadonlyMap<string, PendingMove>>(() => new Map());\n const [error, setError] = React.useState<string | null>(null);\n const eventsRef = React.useRef(events);\n eventsRef.current = events;\n // ★ The newest token per event, held in a REF so a settling promise can ask \"am I still the\n // current change?\" synchronously. Asking inside a setState updater would be a side effect in\n // an updater, which React may run twice or late.\n const latest = React.useRef(new Map<string, number>());\n const seq = React.useRef(0);\n const mounted = React.useRef(true);\n React.useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n React.useEffect(() => {\n const done = settledIds(events, pending);\n if (done.length === 0) return;\n for (const id of done) if (latest.current.get(id) === pending.get(id)?.token) latest.current.delete(id);\n setPending((prev) => {\n const next = new Map(prev);\n for (const id of done) next.delete(id);\n return next;\n });\n }, [events, pending]);\n\n const handleChange = React.useCallback(\n (kind: CalendarEventChange['kind']) =>\n (info: ChangeInfoLike): void => {\n if (!onEventChange) {\n info.revert();\n return;\n }\n let change: CalendarEventChange;\n try {\n change = toChange(info.event, kind, zoneRef.current);\n } catch (e) {\n info.revert();\n setError(describe(e));\n return;\n }\n setError(null);\n const id = change.event.id;\n const token = ++seq.current;\n\n const fail = (err: unknown): void => {\n const current = latest.current.get(id) === token;\n if (current) latest.current.delete(id);\n // ★ onChangeError BEFORE revert, so a revert that throws (calendar already destroyed)\n // cannot swallow the host's error callback.\n onChangeError?.(err, change);\n if (!mounted.current) return;\n setError(`Could not save \"${change.event.title}\": ${describe(err)}`);\n if (!current) return; // a newer move of this event owns its position now\n setPending((prev) => {\n if (!prev.has(id)) return prev;\n const next = new Map(prev);\n next.delete(id);\n return next;\n });\n try {\n info.revert();\n } catch {\n /* the calendar was torn down under us; nothing left to put back */\n }\n };\n\n // ★ Registered BEFORE the handler runs: a synchronous throw calls `fail`, which asks \"am I\n // the current change?\" — registering after the call answered \"no\" and skipped the\n // revert (caught by the sync-throw test).\n latest.current.set(id, token);\n let result: void | Promise<void>;\n try {\n result = onEventChange(change);\n } catch (err) {\n // A handler that throws synchronously is a failed save, not an unhandled error that\n // leaves the event where it was dropped.\n fail(err);\n return;\n }\n if (!result || typeof (result as Promise<void>).then !== 'function') {\n // Fire-and-forget handler: nothing to hold; the host owns the position from here.\n if (latest.current.get(id) === token) latest.current.delete(id);\n return;\n }\n\n setPending((prev) => new Map(prev).set(id, { start: change.start, end: change.end, allDay: change.allDay, token }));\n (result as Promise<void>).then(\n () => {\n if (!mounted.current || latest.current.get(id) !== token) return;\n const savedWith = eventsRef.current;\n setPending((prev) => {\n const p = prev.get(id);\n if (!p || p.token !== token) return prev;\n return new Map(prev).set(id, { ...p, savedWith });\n });\n },\n fail\n );\n },\n [onEventChange, onChangeError, zoneRef]\n );\n\n return { pending, error, dismissError: React.useCallback(() => setError(null), []), handleChange };\n}\n","import type * as React from 'react';\n\n/**\n * The colours a calendar can be themed with, as plain CSS values (hex, rgb(), or `var(--x)`).\n * Each maps onto one of FullCalendar v7's classic-theme custom properties, so theming is just\n * setting variables on the calendar's own container — no stylesheet override, nothing global.\n *\n * ★ Scoped to the container on purpose: FullCalendar's palette declares its defaults on `:root`,\n * and a PCF control shares `:root` with the whole form. Two calendars on one form each set\n * their own, and neither touches the host.\n */\nexport interface CalendarTheme {\n primary?: string;\n primaryForeground?: string;\n background?: string;\n foreground?: string;\n mutedForeground?: string;\n faint?: string;\n muted?: string;\n strong?: string;\n border?: string;\n strongBorder?: string;\n today?: string;\n highlight?: string;\n now?: string;\n button?: string;\n buttonForeground?: string;\n buttonBorder?: string;\n buttonStrong?: string;\n buttonStrongBorder?: string;\n fontFamily?: string;\n fontSize?: string;\n /** Rendered as a banner above the calendar when a save fails. */\n error?: string;\n}\n\nconst VAR: Record<Exclude<keyof CalendarTheme, 'fontFamily' | 'fontSize' | 'error'>, string> = {\n primary: '--fc-classic-primary',\n primaryForeground: '--fc-classic-primary-foreground',\n background: '--fc-classic-background',\n foreground: '--fc-classic-foreground',\n mutedForeground: '--fc-classic-muted-foreground',\n faint: '--fc-classic-faint',\n muted: '--fc-classic-muted',\n strong: '--fc-classic-strong',\n border: '--fc-classic-border',\n strongBorder: '--fc-classic-strong-border',\n today: '--fc-classic-today',\n highlight: '--fc-classic-highlight',\n now: '--fc-classic-now',\n button: '--fc-classic-button',\n buttonForeground: '--fc-classic-button-foreground',\n buttonBorder: '--fc-classic-button-border',\n buttonStrong: '--fc-classic-button-strong',\n buttonStrongBorder: '--fc-classic-button-strong-border',\n};\n\n/** The container style that applies `theme`. Unset keys keep FullCalendar's defaults. */\nexport function themeStyle(theme: CalendarTheme | undefined): React.CSSProperties {\n const style: Record<string, string> = {};\n if (!theme) return style;\n for (const [key, cssVar] of Object.entries(VAR)) {\n const v = theme[key as keyof typeof VAR];\n if (v) style[cssVar] = v;\n }\n if (theme.fontFamily) style.fontFamily = theme.fontFamily;\n if (theme.fontSize) style.fontSize = theme.fontSize;\n if (theme.background) style.background = theme.background;\n if (theme.foreground) style.color = theme.foreground;\n return style as React.CSSProperties;\n}\n","import * as React from 'react';\nimport type { CalendarDataSource, CalendarEvent, CalendarEventChange, SlotSelection, VisibleRange } from '../core/types';\n\nexport interface CalendarSourceBinding {\n events: readonly CalendarEvent[];\n loading: boolean;\n /** The last load failure, or null. A failed load keeps the previous events on screen. */\n error: string | null;\n /** Re-run the current range (e.g. after booking a slot elsewhere on the page). */\n reload: () => void;\n /** Spread onto `<CalendarView>` / `<FluentCalendar>`. */\n props: {\n events: readonly CalendarEvent[];\n loading: boolean;\n editable: boolean;\n selectable: boolean;\n onRangeChange: (range: VisibleRange) => void;\n onEventChange?: (change: CalendarEventChange) => Promise<void>;\n onEventClick?: (event: CalendarEvent) => void;\n onSelectRange?: (slot: SlotSelection) => void;\n };\n}\n\nfunction describe(error: unknown): string {\n const m = (error as { message?: unknown } | null)?.message;\n return typeof m === 'string' && m ? m : String(error);\n}\n\nconst isAbort = (e: unknown): boolean => (e as { name?: unknown } | null)?.name === 'AbortError';\n\n/**\n * Drive a calendar from a PULLED `CalendarDataSource` (code app, Power Pages): load on every range\n * change, save a drag through the source, then reload so the calendar shows what the server kept.\n *\n * ★ Only the NEWEST load may land. Ranges change faster than networks answer (click \"next\" three\n * times), so an older response arriving last must not overwrite the newer week. Each load aborts\n * its predecessor AND is sequence-checked — a source that ignores `signal` is still safe.\n * ★ A save that rejects is RE-THROWN, so the calendar's optimistic layer reverts the event and shows\n * the message; swallowing it here would leave the event where it was dropped, unsaved.\n */\nexport function useCalendarSource(source: CalendarDataSource | null | undefined): CalendarSourceBinding {\n const [events, setEvents] = React.useState<readonly CalendarEvent[]>([]);\n const [loading, setLoading] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n const range = React.useRef<VisibleRange | null>(null);\n const seq = React.useRef(0);\n const inflight = React.useRef<AbortController | null>(null);\n // The source the current (or last) load was issued for — see the mount effect below.\n const loadedFor = React.useRef<CalendarDataSource | null>(null);\n\n const load = React.useCallback(async (): Promise<void> => {\n const r = range.current;\n if (!source || !r) return;\n loadedFor.current = source;\n inflight.current?.abort();\n const controller = new AbortController();\n inflight.current = controller;\n const mine = ++seq.current;\n setLoading(true);\n try {\n const next = await source.load(r, controller.signal);\n if (mine !== seq.current) return;\n setEvents(next);\n setError(null);\n } catch (e) {\n if (mine !== seq.current || isAbort(e)) return;\n setError(describe(e));\n } finally {\n if (mine === seq.current) setLoading(false);\n }\n }, [source]);\n\n // A new source (different filter, different table) re-queries the range already on screen.\n // ★ MEASURED on a live code app and Power Pages site: the calendar reports its range from a CHILD\n // effect, which runs BEFORE this one — so on mount a load for THIS source is already in flight,\n // and unconditionally loading again doubled every mount (2 calls; the portal bills per call).\n // Skip when the load in flight is already for this source. The cleanup clears the marker, so a\n // StrictMode fake-unmount (which aborts the in-flight load) re-queries on the re-run instead of\n // stranding the calendar empty.\n React.useEffect(() => {\n if (loadedFor.current !== source) {\n setEvents([]);\n void load();\n }\n return () => {\n // `seq` is a counter, not a DOM ref: bumping the CURRENT value is the point (it retires any\n // load still in flight), so the rule's \"copy it first\" advice would defeat the cleanup.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n seq.current++;\n inflight.current?.abort();\n loadedFor.current = null;\n };\n }, [load, source]);\n\n const onRangeChange = React.useCallback(\n (r: VisibleRange) => {\n const prev = range.current;\n range.current = r;\n if (prev && prev.start === r.start && prev.end === r.end) return;\n void load();\n },\n [load]\n );\n\n const reload = React.useCallback(() => void load(), [load]);\n\n const onEventChange = React.useMemo(\n () =>\n source?.capabilities.update && source.update\n ? async (change: CalendarEventChange): Promise<void> => {\n await source.update!(change);\n await load();\n }\n : undefined,\n [source, load]\n );\n\n const open = React.useMemo(\n () => (source?.capabilities.open && source.open ? (e: CalendarEvent) => source.open!(e) : undefined),\n [source]\n );\n const onSelectRange = React.useMemo(\n () =>\n source?.capabilities.create && source.create\n ? (slot: SlotSelection) => {\n void Promise.resolve()\n .then(() => source.create!(slot))\n .then(\n () => load(),\n (e: unknown) => setError(describe(e))\n );\n }\n : undefined,\n [source, load]\n );\n\n return {\n events,\n loading,\n error,\n reload,\n props: {\n events,\n loading,\n editable: !!onEventChange,\n selectable: !!onSelectRange,\n onRangeChange,\n ...(onEventChange ? { onEventChange } : {}),\n ...(open ? { onEventClick: open } : {}),\n ...(onSelectRange ? { onSelectRange } : {}),\n },\n };\n}\n"],"mappings":"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","names":["React","t","React","describe"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dataverse-kit/calendar-kit",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "description": "A calendar for the Microsoft stack, built on FullCalendar v7 (MIT views only). Fed plain event objects, it knows nothing about Dataverse; ./core holds the date rules measured against a live org (three DateTime behaviours, Web API vs form-parameter formats, user time zone), and ./adapters/pcf-dataset maps a PCF dataset onto it. The root entry is Fluent-free (for Power Pages); ./v9 adds Fluent UI v9 styling.",
6
6
  "type": "module",
@@ -82,8 +82,8 @@
82
82
  },
83
83
  "peerDependencies": {
84
84
  "@fluentui/react-components": "^9.46.2",
85
- "react": "^18.2.0",
86
- "react-dom": "^18.2.0"
85
+ "react": "^18.2.0 || ^19.0.0",
86
+ "react-dom": "^18.2.0 || ^19.0.0"
87
87
  },
88
88
  "peerDependenciesMeta": {
89
89
  "@fluentui/react-components": {