@joewinke/jatui 0.41.1 → 0.42.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.
Files changed (34) hide show
  1. package/CLAUDE.md +9 -3
  2. package/dist/components/BookingScheduler.svelte +412 -0
  3. package/dist/components/BookingScheduler.svelte.d.ts +66 -0
  4. package/dist/components/CalendarPicker.svelte +190 -205
  5. package/dist/components/CalendarPicker.svelte.d.ts +19 -1
  6. package/dist/components/DatePicker.svelte +419 -0
  7. package/dist/components/DatePicker.svelte.d.ts +44 -0
  8. package/dist/components/DateRangePicker.svelte +471 -5
  9. package/dist/components/DateRangePicker.svelte.d.ts +32 -3
  10. package/dist/components/EventCalendar.svelte +1324 -0
  11. package/dist/components/EventCalendar.svelte.d.ts +101 -0
  12. package/dist/components/TimeSlotPicker.svelte +174 -108
  13. package/dist/components/TimeSlotPicker.svelte.d.ts +27 -0
  14. package/dist/components/calendar/CalendarHeader.svelte +98 -0
  15. package/dist/components/calendar/CalendarHeader.svelte.d.ts +24 -0
  16. package/dist/components/calendar/MonthGrid.svelte +361 -0
  17. package/dist/components/calendar/MonthGrid.svelte.d.ts +55 -0
  18. package/dist/components/calendar/calendarGrid.d.ts +219 -0
  19. package/dist/components/calendar/calendarGrid.js +494 -0
  20. package/dist/components/calendar/createCalendarGrid.d.ts +25 -0
  21. package/dist/components/calendar/createCalendarGrid.js +57 -0
  22. package/dist/components/calendar/dayOverlap.d.ts +92 -0
  23. package/dist/components/calendar/dayOverlap.js +147 -0
  24. package/dist/components/calendar/eventCalendar.d.ts +75 -0
  25. package/dist/components/calendar/eventCalendar.js +187 -0
  26. package/dist/components/calendar/index.d.ts +32 -0
  27. package/dist/components/calendar/index.js +25 -0
  28. package/dist/components/calendar/monthPacking.d.ts +97 -0
  29. package/dist/components/calendar/monthPacking.js +254 -0
  30. package/dist/components/calendar/timeSlots.d.ts +57 -0
  31. package/dist/components/calendar/timeSlots.js +163 -0
  32. package/dist/index.d.ts +17 -0
  33. package/dist/index.js +11 -0
  34. package/package.json +6 -1
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Week/day-view overlap packing.
3
+ *
4
+ * Upstreamed from MOS `lib/components/calendar/shared/dayOverlap.ts`
5
+ * (jatui-lfxyy) with its tests; generic over any `PackableEvent`.
6
+ *
7
+ * Month view stacks intervals across seven discrete day columns into vertical
8
+ * lanes (`monthPacking.ts`). Week and day view have the same interval-graph
9
+ * problem on the other axis: events run over continuous time inside ONE day
10
+ * column, so concurrent events have to be sliced into side-by-side
11
+ * sub-columns. Same shape, different axis — which is why this is a sibling
12
+ * module rather than a mode flag on `packWeek`.
13
+ *
14
+ * The allocator hands out a horizontal slot for every timed event and the grid
15
+ * renders it as a percentage `left`/`width`, so two meetings at 2pm sit beside
16
+ * each other instead of one hiding the other.
17
+ *
18
+ * The intervals compared here are RENDERED extents, not literal ones. The grid
19
+ * gives an event with no end a one-hour block and never draws anything shorter
20
+ * than half an hour, so two five-minute events ten minutes apart really do
21
+ * collide on screen even though their times do not. Packing the literal times
22
+ * would call that pair disjoint and stack them back on top of each other.
23
+ */
24
+ import { parseLocalDate } from "./monthPacking";
25
+ const MS_PER_MIN = 60_000;
26
+ /** Grid block for an event with no end — matches `eventHeightPx`'s 48px. */
27
+ export const DEFAULT_DURATION_MIN = 60;
28
+ /** Shortest block the grid will draw — matches `eventHeightPx`'s 24px floor. */
29
+ export const MIN_DURATION_MIN = 30;
30
+ /**
31
+ * The vertical extent the grid will actually draw for `ev`, in minutes.
32
+ *
33
+ * `start` is read in local time and measured from its own midnight, so an
34
+ * event running past midnight simply reports an `endMin` above 1440 rather
35
+ * than wrapping — the comparisons below only ever need it to be monotonic.
36
+ */
37
+ export function eventInterval(ev, opts = {}) {
38
+ const defaultDuration = opts.defaultDurationMin ?? DEFAULT_DURATION_MIN;
39
+ const minDuration = opts.minDurationMin ?? MIN_DURATION_MIN;
40
+ const start = parseLocalDate(ev.start);
41
+ const startMin = start.getHours() * 60 + start.getMinutes();
42
+ let duration = defaultDuration;
43
+ if (ev.end) {
44
+ const end = parseLocalDate(ev.end);
45
+ const ms = end.getTime() - start.getTime();
46
+ // A missing, unparseable or backwards end is no information at all, so
47
+ // fall back to the same default block a null end gets.
48
+ if (Number.isFinite(ms) && ms > 0)
49
+ duration = ms / MS_PER_MIN;
50
+ }
51
+ return { startMin, endMin: startMin + Math.max(duration, minDuration) };
52
+ }
53
+ /** Half-open overlap: two blocks that merely touch do NOT overlap. */
54
+ export function intervalsOverlap(a, b) {
55
+ return a.startMin < b.endMin && b.startMin < a.endMin;
56
+ }
57
+ /**
58
+ * Split one day's timed events into side-by-side sub-columns.
59
+ *
60
+ * Three passes over the events sorted by start time:
61
+ *
62
+ * 1. **Cluster.** Walk in start order carrying the furthest end seen; an event
63
+ * starting at or after that end can share no space with anything before it,
64
+ * so it opens a new cluster. This is exactly the transitive closure of
65
+ * "overlaps" — A and C never touching still share a cluster if B bridges
66
+ * them, and they must, or the two halves would be scaled differently and
67
+ * B would collide with one of them.
68
+ * 2. **Assign columns.** Greedily take the first sub-column whose last event
69
+ * has already ended. On an interval graph this uses exactly as many columns
70
+ * as the busiest instant needs — no cluster is ever sliced thinner than the
71
+ * real peak concurrency.
72
+ * 3. **Widen.** Let each event grow rightward while no event in the next
73
+ * sub-column overlaps it, so a lone 4pm event beside a busy 2pm stack still
74
+ * reads full width. This cannot reintroduce a collision: for A to widen
75
+ * past column c it must be disjoint from everything in column c, and any B
76
+ * widening out of column c is in column c.
77
+ */
78
+ export function layoutDay(events, opts = {}) {
79
+ const expand = opts.expand ?? true;
80
+ const items = events.map((ev) => ({
81
+ ev,
82
+ ...eventInterval(ev, opts),
83
+ cluster: 0,
84
+ column: 0,
85
+ span: 1,
86
+ }));
87
+ // Earliest first, then longest, then title/id — so the same day always packs
88
+ // the same way regardless of the order the events arrived in.
89
+ items.sort((a, b) => a.startMin - b.startMin ||
90
+ b.endMin - a.endMin ||
91
+ a.ev.title.localeCompare(b.ev.title) ||
92
+ a.ev.id.localeCompare(b.ev.id));
93
+ const clusters = [];
94
+ let current = [];
95
+ let clusterEnd = -Infinity;
96
+ for (const it of items) {
97
+ if (current.length > 0 && it.startMin >= clusterEnd) {
98
+ clusters.push(current);
99
+ current = [];
100
+ clusterEnd = -Infinity;
101
+ }
102
+ it.cluster = clusters.length;
103
+ current.push(it);
104
+ clusterEnd = Math.max(clusterEnd, it.endMin);
105
+ }
106
+ if (current.length > 0)
107
+ clusters.push(current);
108
+ const placements = [];
109
+ for (const cluster of clusters) {
110
+ // Last end time per sub-column. Within a column every event starts at or
111
+ // after the previous one ended, so this is that column's max end.
112
+ const columnEnds = [];
113
+ for (const it of cluster) {
114
+ let c = 0;
115
+ while (c < columnEnds.length && columnEnds[c] > it.startMin)
116
+ c++;
117
+ it.column = c;
118
+ columnEnds[c] = it.endMin;
119
+ }
120
+ const columns = columnEnds.length;
121
+ const unit = 100 / columns;
122
+ for (const it of cluster) {
123
+ let span = 1;
124
+ if (expand) {
125
+ for (let c = it.column + 1; c < columns; c++) {
126
+ const blocked = cluster.some((other) => other.column === c && intervalsOverlap(other, it));
127
+ if (blocked)
128
+ break;
129
+ span++;
130
+ }
131
+ }
132
+ it.span = span;
133
+ placements.push({
134
+ ev: it.ev,
135
+ startMin: it.startMin,
136
+ endMin: it.endMin,
137
+ cluster: it.cluster,
138
+ column: it.column,
139
+ columns,
140
+ span,
141
+ leftPct: it.column * unit,
142
+ widthPct: span * unit,
143
+ });
144
+ }
145
+ }
146
+ return placements;
147
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * EventCalendar — the pure layer.
3
+ *
4
+ * Everything the month / week / day / agenda views compute that is not
5
+ * markup: event colour tokens, view titles and stepping, the visible range a
6
+ * host should load, the month and week matrices with events placed on them
7
+ * (lane packing is `monthPacking.ts`, timed overlap is `dayOverlap.ts`, both
8
+ * upstreamed from MOS), and the agenda grouping.
9
+ */
10
+ import { type Weekday } from './calendarGrid';
11
+ import { type MonthDay, type PackableEvent } from './monthPacking';
12
+ import type { HourCycle } from './timeSlots';
13
+ /** A jatui event hue (1–6 → `--jatui-event-N`) or a DaisyUI semantic name. */
14
+ export type EventColor = 1 | 2 | 3 | 4 | 5 | 6 | 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error' | 'neutral';
15
+ export interface CalendarEventItem extends PackableEvent {
16
+ id: string;
17
+ title: string;
18
+ /** ISO datetime, `YYYY-MM-DD` for an all-day day, or a Date. */
19
+ start: string | Date;
20
+ /** Inclusive last day for all-day events; the end instant for timed ones. */
21
+ end?: string | Date | null;
22
+ allDay?: boolean;
23
+ color?: EventColor;
24
+ description?: string;
25
+ location?: string;
26
+ /** Set false to pin this event when drag-to-move is on. */
27
+ draggable?: boolean;
28
+ /** Anything the host wants back in callbacks. */
29
+ meta?: Record<string, unknown>;
30
+ }
31
+ export type EventCalendarView = 'month' | 'week' | 'day' | 'agenda';
32
+ /**
33
+ * Inline custom properties for one event: `--ev` (the hue: dots, stripes),
34
+ * `--ev-bg` (chip fill) and `--ev-fg` (time / secondary text). All from the
35
+ * `--jatui-event-*` triplets in tokens.css, so they flip with the theme.
36
+ * Titles stay `base-content` on the tinted fill, which keeps contrast in
37
+ * every theme (a saturated fill with `-content` text does not).
38
+ */
39
+ export declare function eventColorStyle(color: EventColor | undefined): string;
40
+ /** Move the focus date one view-step (month, week, day; agenda pages by month). */
41
+ export declare function stepDate(view: EventCalendarView, date: Date, dir: -1 | 1): Date;
42
+ /** The first and last day a view shows — what a host should load events for. */
43
+ export declare function visibleRange(view: EventCalendarView, date: Date, weekStart?: Weekday): {
44
+ start: Date;
45
+ end: Date;
46
+ };
47
+ /** "March 2026", "Mar 1 – 7, 2026", "Wednesday, March 4, 2026". */
48
+ export declare function viewTitle(view: EventCalendarView, date: Date, weekStart?: Weekday, locale?: string): string;
49
+ /** True when an event belongs on the all-day row / a filled bar (all-day or crossing midnight). */
50
+ export declare function isAllDayLike(ev: PackableEvent): boolean;
51
+ /**
52
+ * Month weeks with every event recorded on each day it touches (the week
53
+ * packer collapses the repeats into one spanning segment).
54
+ */
55
+ export declare function buildMonthWeeks<E extends PackableEvent>(date: Date, weekStart: Weekday, events: E[]): MonthDay<E>[][];
56
+ /** Seven days from the week start, each with its all-day-like and timed events. */
57
+ export interface TimeGridDay<E extends PackableEvent> extends MonthDay<E> {
58
+ timed: E[];
59
+ }
60
+ export declare function buildTimeGridDays<E extends PackableEvent>(days: Date[], events: E[]): TimeGridDay<E>[];
61
+ export declare function weekDates(date: Date, weekStart: Weekday): Date[];
62
+ /** Agenda: the days in [start, end] that have events, each sorted all-day first then by time. */
63
+ export declare function agendaDays<E extends PackableEvent>(events: E[], start: Date, end: Date): {
64
+ date: Date;
65
+ events: E[];
66
+ }[];
67
+ /** Compact chip time: "9a" / "9:30a" (h12) or "09:30" (h23). Empty for all-day. */
68
+ export declare function chipTime(ev: PackableEvent, hourCycle?: HourCycle): string;
69
+ /** "Mon, Aug 10 · 9:00 AM – 10:30 AM", "Aug 10 – Aug 16", "Mon, Aug 10 · All day". */
70
+ export declare function eventWhen(ev: PackableEvent, opts?: {
71
+ hourCycle?: HourCycle;
72
+ locale?: string;
73
+ }): string;
74
+ /** Hour-row label: "6 AM" (h12) / "06:00" (h23). */
75
+ export declare function hourLabel(h: number, hourCycle?: HourCycle): string;
@@ -0,0 +1,187 @@
1
+ /**
2
+ * EventCalendar — the pure layer.
3
+ *
4
+ * Everything the month / week / day / agenda views compute that is not
5
+ * markup: event colour tokens, view titles and stepping, the visible range a
6
+ * host should load, the month and week matrices with events placed on them
7
+ * (lane packing is `monthPacking.ts`, timed overlap is `dayOverlap.ts`, both
8
+ * upstreamed from MOS), and the agenda grouping.
9
+ */
10
+ import { addDays, addMonths, buildMonthMatrix, endOfMonth, formatMonthYear, isSameDay, startOfDay, startOfMonth, startOfWeek } from './calendarGrid';
11
+ import { eventDayRange, parseLocalDate } from './monthPacking';
12
+ // ── Colour ─────────────────────────────────────────────────────────────────
13
+ const SEMANTIC_HUE = { primary: 1, secondary: 2, accent: 3, info: 4, success: 5, warning: 6 };
14
+ /**
15
+ * Inline custom properties for one event: `--ev` (the hue: dots, stripes),
16
+ * `--ev-bg` (chip fill) and `--ev-fg` (time / secondary text). All from the
17
+ * `--jatui-event-*` triplets in tokens.css, so they flip with the theme.
18
+ * Titles stay `base-content` on the tinted fill, which keeps contrast in
19
+ * every theme (a saturated fill with `-content` text does not).
20
+ */
21
+ export function eventColorStyle(color) {
22
+ if (color === 'error') {
23
+ return [
24
+ '--ev: var(--jatui-status-negative, var(--color-error))',
25
+ '--ev-bg: color-mix(in oklab, var(--color-error) 16%, var(--color-base-100))',
26
+ '--ev-fg: color-mix(in oklab, var(--color-error) 55%, var(--color-base-content))'
27
+ ].join('; ');
28
+ }
29
+ if (color === 'neutral') {
30
+ return [
31
+ '--ev: color-mix(in oklab, var(--color-base-content) 45%, transparent)',
32
+ '--ev-bg: color-mix(in oklab, var(--color-base-content) 9%, var(--color-base-100))',
33
+ '--ev-fg: color-mix(in oklab, var(--color-base-content) 65%, transparent)'
34
+ ].join('; ');
35
+ }
36
+ const n = typeof color === 'number' ? Math.min(6, Math.max(1, Math.round(color))) : (SEMANTIC_HUE[color ?? 'primary'] ?? 1);
37
+ return `--ev: var(--jatui-event-${n}); --ev-bg: var(--jatui-event-${n}-bg); --ev-fg: var(--jatui-event-${n}-fg)`;
38
+ }
39
+ // ── Views ──────────────────────────────────────────────────────────────────
40
+ /** Move the focus date one view-step (month, week, day; agenda pages by month). */
41
+ export function stepDate(view, date, dir) {
42
+ switch (view) {
43
+ case 'week':
44
+ return addDays(date, 7 * dir);
45
+ case 'day':
46
+ return addDays(date, dir);
47
+ default:
48
+ return addMonths(date, dir);
49
+ }
50
+ }
51
+ /** The first and last day a view shows — what a host should load events for. */
52
+ export function visibleRange(view, date, weekStart = 0) {
53
+ switch (view) {
54
+ case 'week': {
55
+ const start = startOfWeek(date, weekStart);
56
+ return { start, end: addDays(start, 6) };
57
+ }
58
+ case 'day':
59
+ return { start: startOfDay(date), end: startOfDay(date) };
60
+ case 'agenda':
61
+ return { start: startOfMonth(date), end: endOfMonth(date) };
62
+ default: {
63
+ const weeks = buildMonthMatrix(date, weekStart);
64
+ return { start: weeks[0][0].date, end: weeks[weeks.length - 1][6].date };
65
+ }
66
+ }
67
+ }
68
+ /** "March 2026", "Mar 1 – 7, 2026", "Wednesday, March 4, 2026". */
69
+ export function viewTitle(view, date, weekStart = 0, locale) {
70
+ if (view === 'day') {
71
+ return new Intl.DateTimeFormat(locale, { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }).format(date);
72
+ }
73
+ if (view === 'week') {
74
+ const { start, end } = visibleRange('week', date, weekStart);
75
+ const fmt = new Intl.DateTimeFormat(locale, { month: 'short', day: 'numeric', year: 'numeric' });
76
+ return typeof fmt.formatRange === 'function' ? fmt.formatRange(start, end) : `${fmt.format(start)} – ${fmt.format(end)}`;
77
+ }
78
+ return formatMonthYear(date, locale);
79
+ }
80
+ // ── Placement ──────────────────────────────────────────────────────────────
81
+ /** True when an event belongs on the all-day row / a filled bar (all-day or crossing midnight). */
82
+ export function isAllDayLike(ev) {
83
+ if (ev.allDay)
84
+ return true;
85
+ const [s, e] = eventDayRange(ev);
86
+ return !isSameDay(s, e);
87
+ }
88
+ /**
89
+ * Month weeks with every event recorded on each day it touches (the week
90
+ * packer collapses the repeats into one spanning segment).
91
+ */
92
+ export function buildMonthWeeks(date, weekStart, events) {
93
+ const matrix = buildMonthMatrix(date, weekStart);
94
+ const weeks = matrix.map((w) => w.map((c) => ({ date: c.date, inMonth: c.inMonth, events: [] })));
95
+ const first = weeks[0][0].date;
96
+ const last = weeks[weeks.length - 1][6].date;
97
+ for (const ev of events) {
98
+ const [s, e] = eventDayRange(ev);
99
+ if (e < first || s > last)
100
+ continue;
101
+ for (const week of weeks) {
102
+ for (const day of week) {
103
+ if (day.date >= s && day.date <= e)
104
+ day.events.push(ev);
105
+ }
106
+ }
107
+ }
108
+ return weeks;
109
+ }
110
+ export function buildTimeGridDays(days, events) {
111
+ const out = days.map((date) => ({ date, inMonth: true, events: [], timed: [] }));
112
+ for (const ev of events) {
113
+ if (isAllDayLike(ev)) {
114
+ const [s, e] = eventDayRange(ev);
115
+ for (const d of out)
116
+ if (d.date >= s && d.date <= e)
117
+ d.events.push(ev);
118
+ }
119
+ else {
120
+ const start = parseLocalDate(ev.start);
121
+ const d = out.find((x) => isSameDay(x.date, start));
122
+ d?.timed.push(ev);
123
+ }
124
+ }
125
+ return out;
126
+ }
127
+ export function weekDates(date, weekStart) {
128
+ const start = startOfWeek(date, weekStart);
129
+ return Array.from({ length: 7 }, (_, i) => addDays(start, i));
130
+ }
131
+ /** Agenda: the days in [start, end] that have events, each sorted all-day first then by time. */
132
+ export function agendaDays(events, start, end) {
133
+ const out = [];
134
+ for (let d = startOfDay(start); d <= end; d = addDays(d, 1)) {
135
+ const onDay = events.filter((ev) => {
136
+ const [s, e] = eventDayRange(ev);
137
+ return d >= s && d <= e;
138
+ });
139
+ if (onDay.length === 0)
140
+ continue;
141
+ onDay.sort((a, b) => {
142
+ if (!!a.allDay !== !!b.allDay)
143
+ return a.allDay ? -1 : 1;
144
+ return parseLocalDate(a.start).getTime() - parseLocalDate(b.start).getTime() || a.title.localeCompare(b.title);
145
+ });
146
+ out.push({ date: d, events: onDay });
147
+ }
148
+ return out;
149
+ }
150
+ // ── Labels ─────────────────────────────────────────────────────────────────
151
+ /** Compact chip time: "9a" / "9:30a" (h12) or "09:30" (h23). Empty for all-day. */
152
+ export function chipTime(ev, hourCycle = 'h12') {
153
+ if (ev.allDay)
154
+ return '';
155
+ const d = parseLocalDate(ev.start);
156
+ const h = d.getHours();
157
+ const m = d.getMinutes();
158
+ if (hourCycle === 'h23')
159
+ return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
160
+ const h12 = h % 12 === 0 ? 12 : h % 12;
161
+ const suffix = h < 12 ? 'a' : 'p';
162
+ return m === 0 ? `${h12}${suffix}` : `${h12}:${String(m).padStart(2, '0')}${suffix}`;
163
+ }
164
+ /** "Mon, Aug 10 · 9:00 AM – 10:30 AM", "Aug 10 – Aug 16", "Mon, Aug 10 · All day". */
165
+ export function eventWhen(ev, opts = {}) {
166
+ const { hourCycle = 'h12', locale } = opts;
167
+ const [s, e] = eventDayRange(ev);
168
+ const day = (d) => new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric' }).format(d);
169
+ if (!isSameDay(s, e))
170
+ return `${day(s)} – ${day(e)}`;
171
+ if (ev.allDay)
172
+ return `${day(s)} · All day`;
173
+ const t = (v) => new Intl.DateTimeFormat(locale, { hour: 'numeric', minute: '2-digit', hourCycle }).format(parseLocalDate(v));
174
+ return ev.end ? `${day(s)} · ${t(ev.start)} – ${t(ev.end)}` : `${day(s)} · ${t(ev.start)}`;
175
+ }
176
+ /** Hour-row label: "6 AM" (h12) / "06:00" (h23). */
177
+ export function hourLabel(h, hourCycle = 'h12') {
178
+ if (hourCycle === 'h23')
179
+ return `${String(h).padStart(2, '0')}:00`;
180
+ if (h === 0)
181
+ return '12 AM';
182
+ if (h < 12)
183
+ return `${h} AM`;
184
+ if (h === 12)
185
+ return '12 PM';
186
+ return `${h - 12} PM`;
187
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * jatui/calendar — the dates + calendar surface (BoardUI wave 4, jatui-lfxyy).
3
+ *
4
+ * Components (also exported from the `jatui` root): DatePicker, the
5
+ * calendar-mode DateRangePicker, CalendarPicker, TimeSlotPicker,
6
+ * BookingScheduler, EventCalendar and the shared CalendarMonthGrid panel.
7
+ * This subpath adds every pure helper behind them: date math, the APG grid
8
+ * keyboard model, range/preset logic, typed-entry parsing, time-slot zone
9
+ * handling, and the event engine upstreamed from MOS (month lane packing,
10
+ * day overlap layout).
11
+ */
12
+ export { default as CalendarMonthGrid } from './MonthGrid.svelte';
13
+ export type { MonthGridDay, MonthGridProps } from './MonthGrid.svelte';
14
+ export { default as CalendarHeader } from './CalendarHeader.svelte';
15
+ export { default as CalendarPicker } from '../CalendarPicker.svelte';
16
+ export { default as DatePicker } from '../DatePicker.svelte';
17
+ export { default as DateRangePicker } from '../DateRangePicker.svelte';
18
+ export { default as TimeSlotPicker } from '../TimeSlotPicker.svelte';
19
+ export { default as BookingScheduler } from '../BookingScheduler.svelte';
20
+ export { default as EventCalendar } from '../EventCalendar.svelte';
21
+ export { createCalendarGrid } from './createCalendarGrid';
22
+ export type { CalendarGrid, CalendarGridOptions } from './createCalendarGrid';
23
+ export { startOfDay, isSameDay, isSameMonth, addDays, addMonths, startOfMonth, endOfMonth, daysInMonth, dayDiff, startOfWeek, endOfWeek, compareDays, clampDay, toIsoDate, parseIsoDate, toDay, weekStartForLocale, resolveWeekStart, weekdayLabels, formatMonthYear, formatLongDate, formatMediumDate, buildMonthMatrix, nextFocusDate, makeIsDisabled, normalizeRange, isInRange, rangeDayCount, rangeCellState, pickRangeDate, toIsoRange, fromIsoRange, resolvePreset, defaultRangePresets, defaultRangePresetsWithAll, matchPreset, formatRangeLabel, dateFieldOrder, dateSeparator, datePlaceholder, formatTypedDate, parseTypedDate, calendarAccentStyle, followFocus, canStepMonth } from './calendarGrid';
24
+ export type { Weekday, DateRange, IsoDateRange, MonthCell, GridKeyOptions, DateRules, RangeCellState, RangePreset, CalendarAccent } from './calendarGrid';
25
+ export { hourInZone, dayPart, groupSlotsByDayPart, formatSlotTime, formatSlotLabel, localeHourCycle, localTimeZone, timeZoneOffsetLabel, timeZoneCity, commonTimeZones, nextSlotIndex } from './timeSlots';
26
+ export type { HourCycle, DayPart, SlotGroup } from './timeSlots';
27
+ export { parseLocalDate, eventDayRange, packWeek, computeVisibility } from './monthPacking';
28
+ export type { PackableEvent, MonthDay, WeekSegment, PackedWeek, WeekVisibility } from './monthPacking';
29
+ export { eventInterval, intervalsOverlap, layoutDay, DEFAULT_DURATION_MIN, MIN_DURATION_MIN } from './dayOverlap';
30
+ export type { DayOverlapOptions, DayInterval, DayPlacement } from './dayOverlap';
31
+ export { eventColorStyle, stepDate, visibleRange, viewTitle, isAllDayLike, buildMonthWeeks, buildTimeGridDays, weekDates, agendaDays, chipTime, eventWhen, hourLabel } from './eventCalendar';
32
+ export type { CalendarEventItem, EventColor, EventCalendarView, TimeGridDay } from './eventCalendar';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * jatui/calendar — the dates + calendar surface (BoardUI wave 4, jatui-lfxyy).
3
+ *
4
+ * Components (also exported from the `jatui` root): DatePicker, the
5
+ * calendar-mode DateRangePicker, CalendarPicker, TimeSlotPicker,
6
+ * BookingScheduler, EventCalendar and the shared CalendarMonthGrid panel.
7
+ * This subpath adds every pure helper behind them: date math, the APG grid
8
+ * keyboard model, range/preset logic, typed-entry parsing, time-slot zone
9
+ * handling, and the event engine upstreamed from MOS (month lane packing,
10
+ * day overlap layout).
11
+ */
12
+ export { default as CalendarMonthGrid } from './MonthGrid.svelte';
13
+ export { default as CalendarHeader } from './CalendarHeader.svelte';
14
+ export { default as CalendarPicker } from '../CalendarPicker.svelte';
15
+ export { default as DatePicker } from '../DatePicker.svelte';
16
+ export { default as DateRangePicker } from '../DateRangePicker.svelte';
17
+ export { default as TimeSlotPicker } from '../TimeSlotPicker.svelte';
18
+ export { default as BookingScheduler } from '../BookingScheduler.svelte';
19
+ export { default as EventCalendar } from '../EventCalendar.svelte';
20
+ export { createCalendarGrid } from './createCalendarGrid';
21
+ export { startOfDay, isSameDay, isSameMonth, addDays, addMonths, startOfMonth, endOfMonth, daysInMonth, dayDiff, startOfWeek, endOfWeek, compareDays, clampDay, toIsoDate, parseIsoDate, toDay, weekStartForLocale, resolveWeekStart, weekdayLabels, formatMonthYear, formatLongDate, formatMediumDate, buildMonthMatrix, nextFocusDate, makeIsDisabled, normalizeRange, isInRange, rangeDayCount, rangeCellState, pickRangeDate, toIsoRange, fromIsoRange, resolvePreset, defaultRangePresets, defaultRangePresetsWithAll, matchPreset, formatRangeLabel, dateFieldOrder, dateSeparator, datePlaceholder, formatTypedDate, parseTypedDate, calendarAccentStyle, followFocus, canStepMonth } from './calendarGrid';
22
+ export { hourInZone, dayPart, groupSlotsByDayPart, formatSlotTime, formatSlotLabel, localeHourCycle, localTimeZone, timeZoneOffsetLabel, timeZoneCity, commonTimeZones, nextSlotIndex } from './timeSlots';
23
+ export { parseLocalDate, eventDayRange, packWeek, computeVisibility } from './monthPacking';
24
+ export { eventInterval, intervalsOverlap, layoutDay, DEFAULT_DURATION_MIN, MIN_DURATION_MIN } from './dayOverlap';
25
+ export { eventColorStyle, stepDate, visibleRange, viewTitle, isAllDayLike, buildMonthWeeks, buildTimeGridDays, weekDates, agendaDays, chipTime, eventWhen, hourLabel } from './eventCalendar';
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Month-view lane packing.
3
+ *
4
+ * Upstreamed from MOS `lib/components/calendar/shared/monthPacking.ts`
5
+ * (jatui-lfxyy) with its tests. The only change is the event type: any
6
+ * `PackableEvent` (id, title, start, end?, allDay?) works, and start/end may
7
+ * be ISO strings or `Date`s. The MOS event type satisfies it as-is.
8
+ *
9
+ * A month row has to place two kinds of thing — bars that span several days and
10
+ * chips that sit on one — and the only way to guarantee they never collide is to
11
+ * let a single allocator hand out every slot. This module is that allocator. The
12
+ * grid then renders each segment as a CSS grid item at (column span, lane), so
13
+ * overlap becomes structurally impossible instead of a matter of keeping two
14
+ * pixel offsets in agreement.
15
+ */
16
+ /** The fields packing reads. Extra fields ride along untouched. */
17
+ export interface PackableEvent {
18
+ id: string;
19
+ title: string;
20
+ /** ISO datetime, `YYYY-MM-DD` (local day) or a Date. */
21
+ start: string | Date;
22
+ end?: string | Date | null;
23
+ allDay?: boolean;
24
+ }
25
+ /** Parse "YYYY-MM-DD" as local midnight, not the UTC midnight `new Date` gives. */
26
+ export declare function parseLocalDate(str: string | Date): Date;
27
+ export declare function isSameDay(d1: Date, d2: Date): boolean;
28
+ /** Whole days between two dates, ignoring time of day. */
29
+ export declare function dayDiff(from: Date, to: Date): number;
30
+ export declare function startOfDay(d: Date): Date;
31
+ /**
32
+ * Inclusive first and last day an event occupies, both at local midnight.
33
+ *
34
+ * Flooring is load-bearing: month cells are midnight dates, so a 9am event
35
+ * whose range kept its time would fail `cell >= start` and disappear from the
36
+ * grid rather than render an hour late.
37
+ */
38
+ export declare function eventDayRange(ev: PackableEvent): [Date, Date];
39
+ export interface MonthDay<E extends PackableEvent = PackableEvent> {
40
+ date: Date;
41
+ inMonth: boolean;
42
+ events: E[];
43
+ }
44
+ export interface WeekSegment<E extends PackableEvent = PackableEvent> {
45
+ ev: E;
46
+ /** 0-6, clamped into this week */
47
+ startCol: number;
48
+ /** 0-6, clamped into this week */
49
+ endCol: number;
50
+ /** 0-based vertical slot */
51
+ lane: number;
52
+ /** Began before this week's first day */
53
+ continuesLeft: boolean;
54
+ /** Ends after this week's last day */
55
+ continuesRight: boolean;
56
+ /** Solid bar (all-day or spanning) rather than a dot-and-label chip */
57
+ filled: boolean;
58
+ /** Finished before today */
59
+ past: boolean;
60
+ }
61
+ export interface PackedWeek<E extends PackableEvent = PackableEvent> {
62
+ segments: WeekSegment<E>[];
63
+ laneCount: number;
64
+ }
65
+ /**
66
+ * Assign every event touching `week` a lane, greedily, widest span first.
67
+ *
68
+ * `week` must be exactly seven consecutive days, each carrying the events that
69
+ * touch it (an event covering three days appears on all three). Duplicates are
70
+ * collapsed into one segment spanning the columns it covers.
71
+ */
72
+ export declare function packWeek<E extends PackableEvent>(week: MonthDay<E>[], now?: Date): PackedWeek<E>;
73
+ export interface WeekVisibility {
74
+ /** Lanes actually rendered. */
75
+ visibleLanes: number;
76
+ /** Event ids folded into a "+N more" marker. */
77
+ hiddenIds: Set<string>;
78
+ /** Per-column hidden count; 0 means no marker for that day. */
79
+ moreCounts: number[];
80
+ /** Lane to render a segment in, when recovery moved it off its packed lane. */
81
+ laneOverrides: Map<string, number>;
82
+ }
83
+ /**
84
+ * Decide what a row shows given how many lanes fit.
85
+ *
86
+ * The "+N more" marker lives in the LAST VISIBLE lane rather than in a lane of
87
+ * its own. A dedicated marker row costs every column a slot because one busy
88
+ * day overflowed, which on a short window collapsed whole rows to a single
89
+ * event. Here only the columns that actually overflow give up their last lane,
90
+ * so a quiet day beside a busy one keeps showing its events.
91
+ *
92
+ * A segment spanning several columns is one grid item, so it cannot be shown
93
+ * for some of its days and hidden for others: if any column it covers needs the
94
+ * marker, the whole segment folds in. That can push a further column over, so
95
+ * this iterates to a fixed point.
96
+ */
97
+ export declare function computeVisibility<E extends PackableEvent>(segments: WeekSegment<E>[], capacity: number): WeekVisibility;