@dataverse-kit/calendar-kit 0.1.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/dist/fake.cjs ADDED
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/fake/index.ts
21
+ var fake_exports = {};
22
+ __export(fake_exports, {
23
+ createFakeDataSource: () => createFakeDataSource,
24
+ createSampleEvents: () => createSampleEvents
25
+ });
26
+ module.exports = __toCommonJS(fake_exports);
27
+
28
+ // src/core/datetime.ts
29
+ var DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
30
+ var FLOATING_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,9})?)?$/;
31
+ var INSTANT_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:?\d{2})$/i;
32
+ var pad = (n, w = 2) => String(n).padStart(w, "0");
33
+ function kindOf(value) {
34
+ if (value instanceof Date) {
35
+ if (Number.isNaN(value.getTime())) throw new RangeError("Invalid Date");
36
+ return "instant";
37
+ }
38
+ const local = DATE_RE.test(value) ? "date" : FLOATING_RE.test(value) ? "floating" : null;
39
+ if (local) {
40
+ const m = FLOATING_RE.exec(value) ?? DATE_RE.exec(value);
41
+ const [, y, mo, d, h = "0", mi = "0", sec = "0"] = m;
42
+ const t = new Date(Date.UTC(+y, +mo - 1, +d, +h, +mi, +sec));
43
+ if (t.getUTCFullYear() !== +y || t.getUTCMonth() !== +mo - 1 || t.getUTCDate() !== +d || +h > 23 || +mi > 59 || +sec > 59) {
44
+ throw new RangeError(`Not a real calendar date or time: ${JSON.stringify(value)}`);
45
+ }
46
+ return local;
47
+ }
48
+ if (INSTANT_RE.test(value) && !Number.isNaN(new Date(value).getTime())) return "instant";
49
+ throw new RangeError(`Not an ISO-8601 date, wall-clock time or instant: ${JSON.stringify(value)}`);
50
+ }
51
+ function toInstant(value) {
52
+ if (value instanceof Date) return value.toISOString();
53
+ if (kindOf(value) !== "instant") throw new RangeError(`Not an instant: ${value}`);
54
+ return new Date(value).toISOString();
55
+ }
56
+ function wallMillis(wall) {
57
+ const m = FLOATING_RE.exec(wall) ?? DATE_RE.exec(wall);
58
+ if (!m) throw new RangeError(`Not a date or wall-clock time: ${wall}`);
59
+ const [, y, mo, d, h = "0", mi = "0", s = "0"] = m;
60
+ return Date.UTC(+y, +mo - 1, +d, +h, +mi, +s);
61
+ }
62
+ function formatWall(ms) {
63
+ const d = new Date(ms);
64
+ return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
65
+ }
66
+ function fromWall(wall, zone) {
67
+ const w = wallMillis(wall);
68
+ const before = zone(new Date(w - 864e5));
69
+ const after = zone(new Date(w + 864e5));
70
+ const fits = (offset) => {
71
+ const t2 = w - offset * 6e4;
72
+ return zone(new Date(t2)) === offset ? t2 : null;
73
+ };
74
+ const candidates = [fits(before), fits(after)].filter((t2) => t2 !== null);
75
+ const t = candidates.length > 0 ? Math.min(...candidates) : w - before * 6e4;
76
+ return new Date(t).toISOString();
77
+ }
78
+ function addDays(date, days) {
79
+ if (kindOf(date) !== "date") throw new RangeError(`addDays needs a date, got ${date}`);
80
+ return formatWall(wallMillis(date) + days * 864e5).slice(0, 10);
81
+ }
82
+
83
+ // src/core/colors.ts
84
+ var GROUP_PALETTE = [
85
+ "#0f6cbd",
86
+ // blue
87
+ "#107c10",
88
+ // green
89
+ "#c50f1f",
90
+ // red
91
+ "#8764b8",
92
+ // lavender
93
+ "#ae4b00",
94
+ // dark orange
95
+ "#03787c",
96
+ // teal
97
+ "#4f52b2",
98
+ // indigo
99
+ "#8a5b00",
100
+ // dark gold
101
+ "#c0077d",
102
+ // magenta
103
+ "#5c2e91"
104
+ // purple
105
+ ];
106
+ function hash(key) {
107
+ let h = 2166136261;
108
+ for (let i = 0; i < key.length; i++) {
109
+ h ^= key.charCodeAt(i);
110
+ h = Math.imul(h, 16777619);
111
+ }
112
+ return h >>> 0;
113
+ }
114
+ function groupColor(key, palette = GROUP_PALETTE) {
115
+ return palette[hash(key) % palette.length];
116
+ }
117
+
118
+ // src/fake/index.ts
119
+ function rng(seed) {
120
+ let a = seed >>> 0;
121
+ return () => {
122
+ a = a + 1831565813 >>> 0;
123
+ let t = a;
124
+ t = Math.imul(t ^ t >>> 15, t | 1);
125
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
126
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
127
+ };
128
+ }
129
+ var TITLES = ["Consultation", "Follow-up", "Initial assessment", "Review", "Team sync", "Check-in", "Planning"];
130
+ var pad2 = (n) => String(n).padStart(2, "0");
131
+ function createSampleEvents(opts) {
132
+ const next = rng(opts.seed ?? 42);
133
+ const count = opts.count ?? 40;
134
+ const span = opts.spanDays ?? 21;
135
+ const groups = opts.groups ?? ["North clinic", "South clinic", "Remote"];
136
+ const events = [];
137
+ for (let i = 0; i < count; i++) {
138
+ const day = addDays(opts.anchor, Math.floor(next() * span * 2) - span);
139
+ const group = groups[Math.floor(next() * groups.length)];
140
+ const title = TITLES[Math.floor(next() * TITLES.length)];
141
+ const id = `sample-${i + 1}`;
142
+ if (i % 13 === 0) {
143
+ const len = 1 + Math.floor(next() * 3);
144
+ events.push({ id, title: `${title} (all day)`, start: day, end: addDays(day, len), resourceId: group, color: groupColor(group) });
145
+ continue;
146
+ }
147
+ const hour = 8 + Math.floor(next() * 9);
148
+ const minute = next() < 0.5 ? 0 : 30;
149
+ const mins = [30, 45, 60, 90][Math.floor(next() * 4)];
150
+ const wall = `${day}T${pad2(hour)}:${pad2(minute)}:00`;
151
+ const start = fromWall(wall, opts.zone);
152
+ const end = new Date(new Date(start).getTime() + mins * 6e4).toISOString();
153
+ events.push({ id, title, start, end, resourceId: group, color: groupColor(group) });
154
+ }
155
+ events.push({
156
+ id: "sample-floating",
157
+ title: "Floating 09:00 (same face in every zone)",
158
+ start: `${opts.anchor}T09:00:00`,
159
+ end: `${opts.anchor}T10:00:00`,
160
+ color: "#5c2e91"
161
+ });
162
+ return events;
163
+ }
164
+ function createFakeDataSource(events, opts = {}) {
165
+ const wait = () => new Promise((r) => setTimeout(r, opts.latencyMs ?? 300));
166
+ return {
167
+ events,
168
+ capabilities: { update: true, create: false, open: false },
169
+ async load(range) {
170
+ await wait();
171
+ const zone = opts.zone ?? (() => 0);
172
+ const ms = (v) => {
173
+ const k = kindOf(v);
174
+ if (k === "instant") return new Date(toInstant(v)).getTime();
175
+ return new Date(fromWall(k === "date" ? `${v}T00:00:00` : v, zone)).getTime();
176
+ };
177
+ const lo = ms(range.start);
178
+ const hi = ms(range.end);
179
+ return events.filter((e) => {
180
+ const s = ms(e.start);
181
+ const en = e.end !== void 0 ? ms(e.end) : s;
182
+ return s < hi && (e.end !== void 0 ? en > lo : s >= lo);
183
+ });
184
+ },
185
+ async update(change) {
186
+ await wait();
187
+ if (opts.failIds?.includes(change.event.id)) throw new Error("Simulated save failure");
188
+ const i = events.findIndex((e) => e.id === change.event.id);
189
+ if (i < 0) throw new Error("The requested record was not found.");
190
+ events[i] = { ...events[i], start: change.start, end: change.end, allDay: change.allDay };
191
+ }
192
+ };
193
+ }
194
+ // Annotate the CommonJS export names for ESM import in node:
195
+ 0 && (module.exports = {
196
+ createFakeDataSource,
197
+ createSampleEvents
198
+ });
199
+ //# sourceMappingURL=fake.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fake/index.ts","../src/core/datetime.ts","../src/core/colors.ts"],"sourcesContent":["import { addDays, fromWall, kindOf, toInstant } from '../core/datetime';\nimport type { CalendarDataSource, CalendarEvent, CalendarEventChange, ZoneOffset } from '../core/types';\nimport { groupColor } from '../core/colors';\n\n/**\n * `@dataverse-kit/calendar-kit/fake` — sample events and an in-memory data source, for\n * Storybook, harnesses and tests. Deterministic: the same options give the same calendar.\n */\n\n/** mulberry32 — a 32-bit seeded PRNG. */\nfunction rng(seed: number): () => number {\n let a = seed >>> 0;\n return () => {\n a = (a + 0x6d2b79f5) >>> 0;\n let t = a;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\nexport interface SampleOptions {\n /** `YYYY-MM-DD` the samples cluster around (±`spanDays`). */\n anchor: string;\n zone: ZoneOffset;\n count?: number;\n spanDays?: number;\n seed?: number;\n groups?: readonly string[];\n}\n\nconst TITLES = ['Consultation', 'Follow-up', 'Initial assessment', 'Review', 'Team sync', 'Check-in', 'Planning'];\nconst pad = (n: number): string => String(n).padStart(2, '0');\n\n/**\n * Timed events as INSTANTS (the common case), a few all-day DATES, and one FLOATING event, so\n * every value kind is on screen in a story.\n */\nexport function createSampleEvents(opts: SampleOptions): CalendarEvent[] {\n const next = rng(opts.seed ?? 42);\n const count = opts.count ?? 40;\n const span = opts.spanDays ?? 21;\n const groups = opts.groups ?? ['North clinic', 'South clinic', 'Remote'];\n const events: CalendarEvent[] = [];\n for (let i = 0; i < count; i++) {\n const day = addDays(opts.anchor, Math.floor(next() * span * 2) - span);\n const group = groups[Math.floor(next() * groups.length)];\n const title = TITLES[Math.floor(next() * TITLES.length)];\n const id = `sample-${i + 1}`;\n if (i % 13 === 0) {\n const len = 1 + Math.floor(next() * 3);\n events.push({ id, title: `${title} (all day)`, start: day, end: addDays(day, len), resourceId: group, color: groupColor(group) });\n continue;\n }\n const hour = 8 + Math.floor(next() * 9);\n const minute = next() < 0.5 ? 0 : 30;\n const mins = [30, 45, 60, 90][Math.floor(next() * 4)];\n const wall = `${day}T${pad(hour)}:${pad(minute)}:00`;\n const start = fromWall(wall, opts.zone);\n const end = new Date(new Date(start).getTime() + mins * 60_000).toISOString();\n events.push({ id, title, start, end, resourceId: group, color: groupColor(group) });\n }\n events.push({\n id: 'sample-floating',\n title: 'Floating 09:00 (same face in every zone)',\n start: `${opts.anchor}T09:00:00`,\n end: `${opts.anchor}T10:00:00`,\n color: '#5c2e91',\n });\n return events;\n}\n\nexport interface FakeSourceOptions {\n latencyMs?: number;\n /** The zone floating and date values are placed in when filtering by range. Default UTC. */\n zone?: ZoneOffset;\n /** Reject every change for these event ids — to exercise rollback. */\n failIds?: readonly string[];\n}\n\n/** A `CalendarDataSource` over an array, mutated in place on successful changes. */\nexport function createFakeDataSource(events: CalendarEvent[], opts: FakeSourceOptions = {}): CalendarDataSource & {\n events: CalendarEvent[];\n} {\n const wait = (): Promise<void> => new Promise((r) => setTimeout(r, opts.latencyMs ?? 300));\n return {\n events,\n capabilities: { update: true, create: false, open: false },\n async load(range) {\n await wait();\n // ★ Every value through the kit's own conversion — never `new Date(text)`, which parses an\n // offset-less value in the PROCESS zone (a reviewer caught this in the first version).\n const zone = opts.zone ?? (() => 0);\n const ms = (v: string | Date): number => {\n const k = kindOf(v);\n if (k === 'instant') return new Date(toInstant(v)).getTime();\n return new Date(fromWall(k === 'date' ? `${v as string}T00:00:00` : (v as string), zone)).getTime();\n };\n const lo = ms(range.start);\n const hi = ms(range.end);\n return events.filter((e) => {\n const s = ms(e.start);\n const en = e.end !== undefined ? ms(e.end) : s;\n // Overlap: starts before the range ends AND ends after it starts (exclusive ends).\n return s < hi && (e.end !== undefined ? en > lo : s >= lo);\n });\n },\n async update(change: CalendarEventChange) {\n await wait();\n if (opts.failIds?.includes(change.event.id)) throw new Error('Simulated save failure');\n const i = events.findIndex((e) => e.id === change.event.id);\n if (i < 0) throw new Error('The requested record was not found.');\n events[i] = { ...events[i], start: change.start, end: change.end, allDay: change.allDay };\n },\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"],"mappings":"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","names":["t","pad"]}
@@ -0,0 +1,29 @@
1
+ import { Z as ZoneOffset, a as CalendarEvent, C as CalendarDataSource } from './types-u3Gpxk53.cjs';
2
+
3
+ interface SampleOptions {
4
+ /** `YYYY-MM-DD` the samples cluster around (±`spanDays`). */
5
+ anchor: string;
6
+ zone: ZoneOffset;
7
+ count?: number;
8
+ spanDays?: number;
9
+ seed?: number;
10
+ groups?: readonly string[];
11
+ }
12
+ /**
13
+ * Timed events as INSTANTS (the common case), a few all-day DATES, and one FLOATING event, so
14
+ * every value kind is on screen in a story.
15
+ */
16
+ declare function createSampleEvents(opts: SampleOptions): CalendarEvent[];
17
+ interface FakeSourceOptions {
18
+ latencyMs?: number;
19
+ /** The zone floating and date values are placed in when filtering by range. Default UTC. */
20
+ zone?: ZoneOffset;
21
+ /** Reject every change for these event ids — to exercise rollback. */
22
+ failIds?: readonly string[];
23
+ }
24
+ /** A `CalendarDataSource` over an array, mutated in place on successful changes. */
25
+ declare function createFakeDataSource(events: CalendarEvent[], opts?: FakeSourceOptions): CalendarDataSource & {
26
+ events: CalendarEvent[];
27
+ };
28
+
29
+ export { type FakeSourceOptions, type SampleOptions, createFakeDataSource, createSampleEvents };
package/dist/fake.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { Z as ZoneOffset, a as CalendarEvent, C as CalendarDataSource } from './types-u3Gpxk53.js';
2
+
3
+ interface SampleOptions {
4
+ /** `YYYY-MM-DD` the samples cluster around (±`spanDays`). */
5
+ anchor: string;
6
+ zone: ZoneOffset;
7
+ count?: number;
8
+ spanDays?: number;
9
+ seed?: number;
10
+ groups?: readonly string[];
11
+ }
12
+ /**
13
+ * Timed events as INSTANTS (the common case), a few all-day DATES, and one FLOATING event, so
14
+ * every value kind is on screen in a story.
15
+ */
16
+ declare function createSampleEvents(opts: SampleOptions): CalendarEvent[];
17
+ interface FakeSourceOptions {
18
+ latencyMs?: number;
19
+ /** The zone floating and date values are placed in when filtering by range. Default UTC. */
20
+ zone?: ZoneOffset;
21
+ /** Reject every change for these event ids — to exercise rollback. */
22
+ failIds?: readonly string[];
23
+ }
24
+ /** A `CalendarDataSource` over an array, mutated in place on successful changes. */
25
+ declare function createFakeDataSource(events: CalendarEvent[], opts?: FakeSourceOptions): CalendarDataSource & {
26
+ events: CalendarEvent[];
27
+ };
28
+
29
+ export { type FakeSourceOptions, type SampleOptions, createFakeDataSource, createSampleEvents };
package/dist/fake.mjs ADDED
@@ -0,0 +1,171 @@
1
+ // src/core/datetime.ts
2
+ var DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
3
+ var FLOATING_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,9})?)?$/;
4
+ var INSTANT_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:?\d{2})$/i;
5
+ var pad = (n, w = 2) => String(n).padStart(w, "0");
6
+ function kindOf(value) {
7
+ if (value instanceof Date) {
8
+ if (Number.isNaN(value.getTime())) throw new RangeError("Invalid Date");
9
+ return "instant";
10
+ }
11
+ const local = DATE_RE.test(value) ? "date" : FLOATING_RE.test(value) ? "floating" : null;
12
+ if (local) {
13
+ const m = FLOATING_RE.exec(value) ?? DATE_RE.exec(value);
14
+ const [, y, mo, d, h = "0", mi = "0", sec = "0"] = m;
15
+ const t = new Date(Date.UTC(+y, +mo - 1, +d, +h, +mi, +sec));
16
+ if (t.getUTCFullYear() !== +y || t.getUTCMonth() !== +mo - 1 || t.getUTCDate() !== +d || +h > 23 || +mi > 59 || +sec > 59) {
17
+ throw new RangeError(`Not a real calendar date or time: ${JSON.stringify(value)}`);
18
+ }
19
+ return local;
20
+ }
21
+ if (INSTANT_RE.test(value) && !Number.isNaN(new Date(value).getTime())) return "instant";
22
+ throw new RangeError(`Not an ISO-8601 date, wall-clock time or instant: ${JSON.stringify(value)}`);
23
+ }
24
+ function toInstant(value) {
25
+ if (value instanceof Date) return value.toISOString();
26
+ if (kindOf(value) !== "instant") throw new RangeError(`Not an instant: ${value}`);
27
+ return new Date(value).toISOString();
28
+ }
29
+ function wallMillis(wall) {
30
+ const m = FLOATING_RE.exec(wall) ?? DATE_RE.exec(wall);
31
+ if (!m) throw new RangeError(`Not a date or wall-clock time: ${wall}`);
32
+ const [, y, mo, d, h = "0", mi = "0", s = "0"] = m;
33
+ return Date.UTC(+y, +mo - 1, +d, +h, +mi, +s);
34
+ }
35
+ function formatWall(ms) {
36
+ const d = new Date(ms);
37
+ return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
38
+ }
39
+ function fromWall(wall, zone) {
40
+ const w = wallMillis(wall);
41
+ const before = zone(new Date(w - 864e5));
42
+ const after = zone(new Date(w + 864e5));
43
+ const fits = (offset) => {
44
+ const t2 = w - offset * 6e4;
45
+ return zone(new Date(t2)) === offset ? t2 : null;
46
+ };
47
+ const candidates = [fits(before), fits(after)].filter((t2) => t2 !== null);
48
+ const t = candidates.length > 0 ? Math.min(...candidates) : w - before * 6e4;
49
+ return new Date(t).toISOString();
50
+ }
51
+ function addDays(date, days) {
52
+ if (kindOf(date) !== "date") throw new RangeError(`addDays needs a date, got ${date}`);
53
+ return formatWall(wallMillis(date) + days * 864e5).slice(0, 10);
54
+ }
55
+
56
+ // src/core/colors.ts
57
+ var GROUP_PALETTE = [
58
+ "#0f6cbd",
59
+ // blue
60
+ "#107c10",
61
+ // green
62
+ "#c50f1f",
63
+ // red
64
+ "#8764b8",
65
+ // lavender
66
+ "#ae4b00",
67
+ // dark orange
68
+ "#03787c",
69
+ // teal
70
+ "#4f52b2",
71
+ // indigo
72
+ "#8a5b00",
73
+ // dark gold
74
+ "#c0077d",
75
+ // magenta
76
+ "#5c2e91"
77
+ // purple
78
+ ];
79
+ function hash(key) {
80
+ let h = 2166136261;
81
+ for (let i = 0; i < key.length; i++) {
82
+ h ^= key.charCodeAt(i);
83
+ h = Math.imul(h, 16777619);
84
+ }
85
+ return h >>> 0;
86
+ }
87
+ function groupColor(key, palette = GROUP_PALETTE) {
88
+ return palette[hash(key) % palette.length];
89
+ }
90
+
91
+ // src/fake/index.ts
92
+ function rng(seed) {
93
+ let a = seed >>> 0;
94
+ return () => {
95
+ a = a + 1831565813 >>> 0;
96
+ let t = a;
97
+ t = Math.imul(t ^ t >>> 15, t | 1);
98
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
99
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
100
+ };
101
+ }
102
+ var TITLES = ["Consultation", "Follow-up", "Initial assessment", "Review", "Team sync", "Check-in", "Planning"];
103
+ var pad2 = (n) => String(n).padStart(2, "0");
104
+ function createSampleEvents(opts) {
105
+ const next = rng(opts.seed ?? 42);
106
+ const count = opts.count ?? 40;
107
+ const span = opts.spanDays ?? 21;
108
+ const groups = opts.groups ?? ["North clinic", "South clinic", "Remote"];
109
+ const events = [];
110
+ for (let i = 0; i < count; i++) {
111
+ const day = addDays(opts.anchor, Math.floor(next() * span * 2) - span);
112
+ const group = groups[Math.floor(next() * groups.length)];
113
+ const title = TITLES[Math.floor(next() * TITLES.length)];
114
+ const id = `sample-${i + 1}`;
115
+ if (i % 13 === 0) {
116
+ const len = 1 + Math.floor(next() * 3);
117
+ events.push({ id, title: `${title} (all day)`, start: day, end: addDays(day, len), resourceId: group, color: groupColor(group) });
118
+ continue;
119
+ }
120
+ const hour = 8 + Math.floor(next() * 9);
121
+ const minute = next() < 0.5 ? 0 : 30;
122
+ const mins = [30, 45, 60, 90][Math.floor(next() * 4)];
123
+ const wall = `${day}T${pad2(hour)}:${pad2(minute)}:00`;
124
+ const start = fromWall(wall, opts.zone);
125
+ const end = new Date(new Date(start).getTime() + mins * 6e4).toISOString();
126
+ events.push({ id, title, start, end, resourceId: group, color: groupColor(group) });
127
+ }
128
+ events.push({
129
+ id: "sample-floating",
130
+ title: "Floating 09:00 (same face in every zone)",
131
+ start: `${opts.anchor}T09:00:00`,
132
+ end: `${opts.anchor}T10:00:00`,
133
+ color: "#5c2e91"
134
+ });
135
+ return events;
136
+ }
137
+ function createFakeDataSource(events, opts = {}) {
138
+ const wait = () => new Promise((r) => setTimeout(r, opts.latencyMs ?? 300));
139
+ return {
140
+ events,
141
+ capabilities: { update: true, create: false, open: false },
142
+ async load(range) {
143
+ await wait();
144
+ const zone = opts.zone ?? (() => 0);
145
+ const ms = (v) => {
146
+ const k = kindOf(v);
147
+ if (k === "instant") return new Date(toInstant(v)).getTime();
148
+ return new Date(fromWall(k === "date" ? `${v}T00:00:00` : v, zone)).getTime();
149
+ };
150
+ const lo = ms(range.start);
151
+ const hi = ms(range.end);
152
+ return events.filter((e) => {
153
+ const s = ms(e.start);
154
+ const en = e.end !== void 0 ? ms(e.end) : s;
155
+ return s < hi && (e.end !== void 0 ? en > lo : s >= lo);
156
+ });
157
+ },
158
+ async update(change) {
159
+ await wait();
160
+ if (opts.failIds?.includes(change.event.id)) throw new Error("Simulated save failure");
161
+ const i = events.findIndex((e) => e.id === change.event.id);
162
+ if (i < 0) throw new Error("The requested record was not found.");
163
+ events[i] = { ...events[i], start: change.start, end: change.end, allDay: change.allDay };
164
+ }
165
+ };
166
+ }
167
+ export {
168
+ createFakeDataSource,
169
+ createSampleEvents
170
+ };
171
+ //# sourceMappingURL=fake.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/datetime.ts","../src/core/colors.ts","../src/fake/index.ts"],"sourcesContent":["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 { addDays, fromWall, kindOf, toInstant } from '../core/datetime';\nimport type { CalendarDataSource, CalendarEvent, CalendarEventChange, ZoneOffset } from '../core/types';\nimport { groupColor } from '../core/colors';\n\n/**\n * `@dataverse-kit/calendar-kit/fake` — sample events and an in-memory data source, for\n * Storybook, harnesses and tests. Deterministic: the same options give the same calendar.\n */\n\n/** mulberry32 — a 32-bit seeded PRNG. */\nfunction rng(seed: number): () => number {\n let a = seed >>> 0;\n return () => {\n a = (a + 0x6d2b79f5) >>> 0;\n let t = a;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\nexport interface SampleOptions {\n /** `YYYY-MM-DD` the samples cluster around (±`spanDays`). */\n anchor: string;\n zone: ZoneOffset;\n count?: number;\n spanDays?: number;\n seed?: number;\n groups?: readonly string[];\n}\n\nconst TITLES = ['Consultation', 'Follow-up', 'Initial assessment', 'Review', 'Team sync', 'Check-in', 'Planning'];\nconst pad = (n: number): string => String(n).padStart(2, '0');\n\n/**\n * Timed events as INSTANTS (the common case), a few all-day DATES, and one FLOATING event, so\n * every value kind is on screen in a story.\n */\nexport function createSampleEvents(opts: SampleOptions): CalendarEvent[] {\n const next = rng(opts.seed ?? 42);\n const count = opts.count ?? 40;\n const span = opts.spanDays ?? 21;\n const groups = opts.groups ?? ['North clinic', 'South clinic', 'Remote'];\n const events: CalendarEvent[] = [];\n for (let i = 0; i < count; i++) {\n const day = addDays(opts.anchor, Math.floor(next() * span * 2) - span);\n const group = groups[Math.floor(next() * groups.length)];\n const title = TITLES[Math.floor(next() * TITLES.length)];\n const id = `sample-${i + 1}`;\n if (i % 13 === 0) {\n const len = 1 + Math.floor(next() * 3);\n events.push({ id, title: `${title} (all day)`, start: day, end: addDays(day, len), resourceId: group, color: groupColor(group) });\n continue;\n }\n const hour = 8 + Math.floor(next() * 9);\n const minute = next() < 0.5 ? 0 : 30;\n const mins = [30, 45, 60, 90][Math.floor(next() * 4)];\n const wall = `${day}T${pad(hour)}:${pad(minute)}:00`;\n const start = fromWall(wall, opts.zone);\n const end = new Date(new Date(start).getTime() + mins * 60_000).toISOString();\n events.push({ id, title, start, end, resourceId: group, color: groupColor(group) });\n }\n events.push({\n id: 'sample-floating',\n title: 'Floating 09:00 (same face in every zone)',\n start: `${opts.anchor}T09:00:00`,\n end: `${opts.anchor}T10:00:00`,\n color: '#5c2e91',\n });\n return events;\n}\n\nexport interface FakeSourceOptions {\n latencyMs?: number;\n /** The zone floating and date values are placed in when filtering by range. Default UTC. */\n zone?: ZoneOffset;\n /** Reject every change for these event ids — to exercise rollback. */\n failIds?: readonly string[];\n}\n\n/** A `CalendarDataSource` over an array, mutated in place on successful changes. */\nexport function createFakeDataSource(events: CalendarEvent[], opts: FakeSourceOptions = {}): CalendarDataSource & {\n events: CalendarEvent[];\n} {\n const wait = (): Promise<void> => new Promise((r) => setTimeout(r, opts.latencyMs ?? 300));\n return {\n events,\n capabilities: { update: true, create: false, open: false },\n async load(range) {\n await wait();\n // ★ Every value through the kit's own conversion — never `new Date(text)`, which parses an\n // offset-less value in the PROCESS zone (a reviewer caught this in the first version).\n const zone = opts.zone ?? (() => 0);\n const ms = (v: string | Date): number => {\n const k = kindOf(v);\n if (k === 'instant') return new Date(toInstant(v)).getTime();\n return new Date(fromWall(k === 'date' ? `${v as string}T00:00:00` : (v as string), zone)).getTime();\n };\n const lo = ms(range.start);\n const hi = ms(range.end);\n return events.filter((e) => {\n const s = ms(e.start);\n const en = e.end !== undefined ? ms(e.end) : s;\n // Overlap: starts before the range ends AND ends after it starts (exclusive ends).\n return s < hi && (e.end !== undefined ? en > lo : s >= lo);\n });\n },\n async update(change: CalendarEventChange) {\n await wait();\n if (opts.failIds?.includes(change.event.id)) throw new Error('Simulated save failure');\n const i = events.findIndex((e) => e.id === change.event.id);\n if (i < 0) throw new Error('The requested record was not found.');\n events[i] = { ...events[i], start: change.start, end: change.end, allDay: change.allDay };\n },\n };\n}\n"],"mappings":"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","names":["t","pad"]}