@cocoar/vue-calendar 1.16.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 (108) hide show
  1. package/README.md +257 -0
  2. package/dist/__test-utils__/event-fixtures.d.ts +4 -0
  3. package/dist/__test-utils__/event-fixtures.d.ts.map +1 -0
  4. package/dist/builders/calendar-builder-internals.d.ts +14 -0
  5. package/dist/builders/calendar-builder-internals.d.ts.map +1 -0
  6. package/dist/builders/calendar-builder.d.ts +296 -0
  7. package/dist/builders/calendar-builder.d.ts.map +1 -0
  8. package/dist/builders/event-zone-hints.d.ts +20 -0
  9. package/dist/builders/event-zone-hints.d.ts.map +1 -0
  10. package/dist/builders/render-helpers.d.ts +19 -0
  11. package/dist/builders/render-helpers.d.ts.map +1 -0
  12. package/dist/builders/types.d.ts +175 -0
  13. package/dist/builders/types.d.ts.map +1 -0
  14. package/dist/components/CoarCalendar.vue.d.ts +150 -0
  15. package/dist/components/CoarCalendar.vue.d.ts.map +1 -0
  16. package/dist/components/CoarDisplayZoneSwitcher.vue.d.ts +28 -0
  17. package/dist/components/CoarDisplayZoneSwitcher.vue.d.ts.map +1 -0
  18. package/dist/components/VirtualizedSurface1DY.vue.d.ts +90 -0
  19. package/dist/components/VirtualizedSurface1DY.vue.d.ts.map +1 -0
  20. package/dist/components/VirtualizedSurface2D.vue.d.ts +71 -0
  21. package/dist/components/VirtualizedSurface2D.vue.d.ts.map +1 -0
  22. package/dist/components/internal/CoarEventDecorations.vue.d.ts +13 -0
  23. package/dist/components/internal/CoarEventDecorations.vue.d.ts.map +1 -0
  24. package/dist/components/internal/RenderEventFallback.d.ts +17 -0
  25. package/dist/components/internal/RenderEventFallback.d.ts.map +1 -0
  26. package/dist/components/internal/agenda/CoarAgendaDayHeader.vue.d.ts +54 -0
  27. package/dist/components/internal/agenda/CoarAgendaDayHeader.vue.d.ts.map +1 -0
  28. package/dist/components/internal/month/CoarMonthBar.vue.d.ts +80 -0
  29. package/dist/components/internal/month/CoarMonthBar.vue.d.ts.map +1 -0
  30. package/dist/components/internal/month/CoarMonthCell.vue.d.ts +74 -0
  31. package/dist/components/internal/month/CoarMonthCell.vue.d.ts.map +1 -0
  32. package/dist/components/internal/month/CoarMonthGrid.vue.d.ts +50 -0
  33. package/dist/components/internal/month/CoarMonthGrid.vue.d.ts.map +1 -0
  34. package/dist/components/internal/month/CoarMonthPill.vue.d.ts +83 -0
  35. package/dist/components/internal/month/CoarMonthPill.vue.d.ts.map +1 -0
  36. package/dist/components/internal/month/CoarMonthRow.vue.d.ts +44 -0
  37. package/dist/components/internal/month/CoarMonthRow.vue.d.ts.map +1 -0
  38. package/dist/components/internal/time-grid/CoarTimeGridAllDayBand.vue.d.ts +39 -0
  39. package/dist/components/internal/time-grid/CoarTimeGridAllDayBand.vue.d.ts.map +1 -0
  40. package/dist/components/internal/time-grid/CoarTimeGridColumn.vue.d.ts +44 -0
  41. package/dist/components/internal/time-grid/CoarTimeGridColumn.vue.d.ts.map +1 -0
  42. package/dist/components/internal/time-grid/CoarTimeGridHeader.vue.d.ts +44 -0
  43. package/dist/components/internal/time-grid/CoarTimeGridHeader.vue.d.ts.map +1 -0
  44. package/dist/components/internal/time-grid/CoarTimeGridNowMarker.vue.d.ts +18 -0
  45. package/dist/components/internal/time-grid/CoarTimeGridNowMarker.vue.d.ts.map +1 -0
  46. package/dist/composables/useA11yAnnouncer.d.ts +9 -0
  47. package/dist/composables/useA11yAnnouncer.d.ts.map +1 -0
  48. package/dist/composables/useCalendarDnd.d.ts +212 -0
  49. package/dist/composables/useCalendarDnd.d.ts.map +1 -0
  50. package/dist/composables/useCoarDrag.d.ts +97 -0
  51. package/dist/composables/useCoarDrag.d.ts.map +1 -0
  52. package/dist/composables/useMonthDnd.d.ts +89 -0
  53. package/dist/composables/useMonthDnd.d.ts.map +1 -0
  54. package/dist/composables/useMonthExpansion.d.ts +35 -0
  55. package/dist/composables/useMonthExpansion.d.ts.map +1 -0
  56. package/dist/composables/useTimeGridDnd.d.ts +104 -0
  57. package/dist/composables/useTimeGridDnd.d.ts.map +1 -0
  58. package/dist/composables/useViewWindow.d.ts +14 -0
  59. package/dist/composables/useViewWindow.d.ts.map +1 -0
  60. package/dist/core/agendaLayout.d.ts +47 -0
  61. package/dist/core/agendaLayout.d.ts.map +1 -0
  62. package/dist/core/dnd/move-math.d.ts +136 -0
  63. package/dist/core/dnd/move-math.d.ts.map +1 -0
  64. package/dist/core/dragHitTest.d.ts +74 -0
  65. package/dist/core/dragHitTest.d.ts.map +1 -0
  66. package/dist/core/eventIndex.d.ts +106 -0
  67. package/dist/core/eventIndex.d.ts.map +1 -0
  68. package/dist/core/index.d.ts +32 -0
  69. package/dist/core/index.d.ts.map +1 -0
  70. package/dist/core/measurementCache.d.ts +87 -0
  71. package/dist/core/measurementCache.d.ts.map +1 -0
  72. package/dist/core/monthGridLayout.d.ts +68 -0
  73. package/dist/core/monthGridLayout.d.ts.map +1 -0
  74. package/dist/core/overlapLayout.d.ts +88 -0
  75. package/dist/core/overlapLayout.d.ts.map +1 -0
  76. package/dist/core/recurrence-public.d.ts +59 -0
  77. package/dist/core/recurrence-public.d.ts.map +1 -0
  78. package/dist/core/recurrence.d.ts +112 -0
  79. package/dist/core/recurrence.d.ts.map +1 -0
  80. package/dist/core/recurrenceWorker.d.ts +62 -0
  81. package/dist/core/recurrenceWorker.d.ts.map +1 -0
  82. package/dist/core/temporal.d.ts +214 -0
  83. package/dist/core/temporal.d.ts.map +1 -0
  84. package/dist/core/timeGridLayout.d.ts +109 -0
  85. package/dist/core/timeGridLayout.d.ts.map +1 -0
  86. package/dist/core/types.d.ts +211 -0
  87. package/dist/core/types.d.ts.map +1 -0
  88. package/dist/core/viewWindow.d.ts +53 -0
  89. package/dist/core/viewWindow.d.ts.map +1 -0
  90. package/dist/core/virtualScroll.d.ts +91 -0
  91. package/dist/core/virtualScroll.d.ts.map +1 -0
  92. package/dist/core-DK63eHat.js +959 -0
  93. package/dist/core.js +2 -0
  94. package/dist/index.d.ts +40 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +4221 -0
  97. package/dist/useAgendaView.d.ts +6 -0
  98. package/dist/useAgendaView.d.ts.map +1 -0
  99. package/dist/useCalendar.d.ts +6 -0
  100. package/dist/useCalendar.d.ts.map +1 -0
  101. package/dist/useDayView.d.ts +6 -0
  102. package/dist/useDayView.d.ts.map +1 -0
  103. package/dist/useMonthView.d.ts +6 -0
  104. package/dist/useMonthView.d.ts.map +1 -0
  105. package/dist/useWeekView.d.ts +6 -0
  106. package/dist/useWeekView.d.ts.map +1 -0
  107. package/dist/vue-calendar.css +2 -0
  108. package/package.json +68 -0
@@ -0,0 +1,136 @@
1
+ import { Temporal } from '@js-temporal/polyfill';
2
+ import { CalendarEvent } from '../types';
3
+ import { DstPolicy } from '../temporal';
4
+ /** Minimum duration of a resized timed event, in minutes. */
5
+ export declare const MIN_RESIZE_MINUTES = 15;
6
+ /**
7
+ * One-of for every drag-mode the calendar surfaces. Imported by
8
+ * the move-math entry point so consumers don't need to depend on
9
+ * the `useCalendarDnd` composable to call this function.
10
+ */
11
+ export type CalendarDragMode = 'timed' | 'timed-resize-start' | 'timed-resize-end' | 'allDay' | 'allDay-resize-start' | 'allDay-resize-end' | 'month' | 'month-resize-start' | 'month-resize-end';
12
+ /**
13
+ * DST disambiguation outcome — set by `applyMoveToEvent` whenever
14
+ * the resolved target date+time hits a spring-forward gap or a
15
+ * fall-back overlap in the event's source zone.
16
+ *
17
+ * - `null` — clean conversion, no DST event happened.
18
+ * - `'gap'` — date+time doesn't exist (e.g. 02:30 on 2026-03-29
19
+ * Vienna). Result has been resolved per the active
20
+ * `DstPolicy`; consumers can show a "we shifted
21
+ * your meeting" toast.
22
+ * - `'overlap'` — date+time exists twice (e.g. 02:30 on 2026-10-25
23
+ * Vienna). Result picked one per `DstPolicy`.
24
+ *
25
+ * **Note (Article 3 — fairness contract):** the flag reflects the DST
26
+ * situation in the DISPLAY zone — i.e. what the user actually saw and
27
+ * clicked. For a cross-zone event (Tokyo source rendered in Vienna),
28
+ * a drop on the Vienna spring-forward gap reports `'gap'` because
29
+ * that's the user's experience; the resulting `next.start` lives in
30
+ * the source zone (Tokyo) where there is no gap. This is intentional:
31
+ * the consumer's UI ("we shifted your meeting") is anchored to the
32
+ * user's perception, not to the storage zone.
33
+ */
34
+ export type DstDisambiguation = null | 'gap' | 'overlap';
35
+ /** Snapped drop target produced by hit-tests. */
36
+ export interface CalendarDropTarget {
37
+ /** ISO date string `'YYYY-MM-DD'` of the drop day. */
38
+ date: string;
39
+ /**
40
+ * Minute-of-day in the DISPLAY zone (`displayZone`), or `null`
41
+ * when the target is in an all-day band / month cell with no
42
+ * time component.
43
+ */
44
+ minutes: number | null;
45
+ /**
46
+ * IANA timezone the calendar renders in — the one `target.minutes`
47
+ * is anchored to. Required for timed targets so move-math can
48
+ * resolve the slot in the right zone (Article-4 fix for the bug
49
+ * where targets were resolved as UTC).
50
+ */
51
+ displayZone: string;
52
+ /** Whether `canDrop()` (if any) accepted this target. */
53
+ valid: boolean;
54
+ }
55
+ /**
56
+ * Re-exported `DstPolicy` — single source of truth lives in
57
+ * `../temporal.ts`. Code MUST import from one of the public paths
58
+ * and never re-declare it (drift hazard).
59
+ */
60
+ export type { DstPolicy } from '../temporal';
61
+ /**
62
+ * Thrown by `applyMoveToEvent` when `dstPolicy === 'reject'` and the
63
+ * target falls in a DST gap or overlap. The drop layer translates
64
+ * this into `valid=false` so the snap-back animation fires.
65
+ */
66
+ export declare class DstResolutionError extends Error {
67
+ readonly disambiguation: 'gap' | 'overlap';
68
+ constructor(disambiguation: 'gap' | 'overlap', message: string);
69
+ }
70
+ /**
71
+ * Result shape of `applyMoveToEvent`. The shape mirrors the input
72
+ * event: `ZonedDateTime` for timed events, `PlainDate` for all-day.
73
+ *
74
+ * `disambiguation` is set whenever the resolved start landed on a DST
75
+ * gap or overlap in the event's source zone (or, for resize modes,
76
+ * the moved end). `null` for clean conversions, all-day moves, or
77
+ * when the policy was `'reject'` (in which case the function throws
78
+ * before returning).
79
+ */
80
+ export type MoveResult = {
81
+ start: Temporal.ZonedDateTime;
82
+ end?: Temporal.ZonedDateTime;
83
+ disambiguation?: DstDisambiguation;
84
+ } | {
85
+ start: Temporal.PlainDate;
86
+ end?: Temporal.PlainDate;
87
+ disambiguation?: DstDisambiguation;
88
+ };
89
+ /**
90
+ * Shape of the payload fired by `onEventDrop`. Same layout for every
91
+ * drag mode (mouse / keyboard / touch) and every drop site (time-grid
92
+ * / month / agenda) — produced by `buildDropPayload` exactly once per
93
+ * commit.
94
+ */
95
+ export interface EventDropPayload<TMeta extends Record<string, unknown> = Record<string, unknown>> {
96
+ event: CalendarEvent<TMeta>;
97
+ /** Snapshot at drag-start. `displayZone` is the zone the user
98
+ * STARTED viewing in (a mid-drag `.timezone()` swap is allowed). */
99
+ original: {
100
+ start: Temporal.ZonedDateTime | Temporal.PlainDate;
101
+ end?: Temporal.ZonedDateTime | Temporal.PlainDate;
102
+ displayZone: string;
103
+ };
104
+ /** Resolved next event state — per-endpoint source zones preserved
105
+ * (C3). */
106
+ next: MoveResult;
107
+ /** Drop slot the cursor was over. `displayZone` here is the
108
+ * CURRENT display zone (may differ from `original.displayZone`). */
109
+ target: {
110
+ date: string;
111
+ minutes: number | null;
112
+ displayZone: string;
113
+ disambiguation: DstDisambiguation;
114
+ };
115
+ native: PointerEvent | null;
116
+ }
117
+ /**
118
+ * The ONE function that produces an `EventDropPayload` from a drop.
119
+ *
120
+ * **C2 — every drop path reaches THIS function exactly once.** Mouse
121
+ * drop, keyboard Enter, touch end — they all funnel here. Outside of
122
+ * this file, `applyMoveToEvent` is only called for preview ghost
123
+ * geometry (which produces identical math, so preview-vs-commit
124
+ * drift is structurally impossible).
125
+ *
126
+ * **Throws** `DstResolutionError` if `dstPolicy === 'reject'` and the
127
+ * wall-time falls in a DST gap. The caller (composable) catches it
128
+ * and translates to "drop suppressed".
129
+ */
130
+ export declare function buildDropPayload<TMeta extends Record<string, unknown> = Record<string, unknown>>(dstPolicy: DstPolicy, event: CalendarEvent<TMeta>, originalSnapshot: {
131
+ start: Temporal.ZonedDateTime | Temporal.PlainDate;
132
+ end?: Temporal.ZonedDateTime | Temporal.PlainDate;
133
+ displayZone: string;
134
+ }, target: CalendarDropTarget, mode: CalendarDragMode, native: PointerEvent | null): EventDropPayload<TMeta>;
135
+ export declare function applyMoveToEvent(event: CalendarEvent, target: CalendarDropTarget, mode: CalendarDragMode | null, dstPolicy?: DstPolicy): MoveResult;
136
+ //# sourceMappingURL=move-math.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"move-math.d.ts","sourceRoot":"","sources":["../../../src/core/dnd/move-math.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import { MeasurementCache } from './measurementCache';
2
+ export interface VerticalHit {
3
+ /** 0-based item index, or -1 if the pointer is outside the item region. */
4
+ itemIndex: number;
5
+ /**
6
+ * Position within the item, 0..1. 0 = at the top edge, 1 = at the
7
+ * bottom edge. Useful for "drop above/below" semantics: < 0.5 is
8
+ * the top half, ≥ 0.5 is the bottom half.
9
+ *
10
+ * NaN if `itemIndex === -1`.
11
+ */
12
+ ratio: number;
13
+ /**
14
+ * Pixel offset within the item, 0..size. Cheaper than recomputing
15
+ * for callers that need it.
16
+ */
17
+ pixelInItem: number;
18
+ }
19
+ /**
20
+ * Hit-test a vertical surface (`<VirtualizedSurface1DY>` or any
21
+ * scroll container with measured items). Returns which item the
22
+ * pointer is over, plus the position within that item.
23
+ *
24
+ * @param pointerScreenY the pointer's screen y-coordinate
25
+ * (from `event.clientY`)
26
+ * @param surfaceTopScreenY the surface's top-edge screen y-coordinate
27
+ * (from `surfaceEl.getBoundingClientRect().top`)
28
+ * @param scrollTop the surface's current `scrollTop`
29
+ * @param measurements the surface's measurement cache (or compatible
30
+ * stand-in for fixed-size mode)
31
+ * @param surfaceHeight optional viewport height; if provided, points
32
+ * outside `[surfaceTopScreenY, surfaceTopScreenY +
33
+ * surfaceHeight)` return `itemIndex = -1`
34
+ */
35
+ export declare function hitTestVerticalSurface(pointerScreenY: number, surfaceTopScreenY: number, scrollTop: number, measurements: MeasurementCache, surfaceHeight?: number): VerticalHit;
36
+ export interface AutoScrollOptions {
37
+ /** Hot-zone thickness near each edge in pixels. Default 30. */
38
+ hotZone?: number;
39
+ /** Max scroll velocity in px/frame. Default 24 (≈ 1440 px/sec @ 60fps). */
40
+ maxVelocity?: number;
41
+ /**
42
+ * Curve applied to the linear penetration ratio (0..1) before
43
+ * scaling to velocity. `'linear'` is the default; `'quadratic'`
44
+ * (the natural feeling — slow near the edge, fast at the corner)
45
+ * applies `r^2`.
46
+ */
47
+ curve?: 'linear' | 'quadratic';
48
+ }
49
+ export interface AutoScrollResult {
50
+ /**
51
+ * Velocity in px/frame for the X axis. Negative = scroll up, 0 =
52
+ * idle, positive = scroll down. (For X axis: negative = scroll
53
+ * left, positive = scroll right.)
54
+ */
55
+ velocityX: number;
56
+ velocityY: number;
57
+ }
58
+ /**
59
+ * Compute auto-scroll velocity for a pointer position relative to the
60
+ * surface viewport. Pure function — caller applies the velocity each
61
+ * frame.
62
+ *
63
+ * @param pointerScreenX pointer's screen-x (from `clientX`)
64
+ * @param pointerScreenY pointer's screen-y (from `clientY`)
65
+ * @param surfaceRect surface's `getBoundingClientRect()`-shaped
66
+ * bounds (just the four numbers)
67
+ */
68
+ export declare function computeAutoScrollVelocity(pointerScreenX: number, pointerScreenY: number, surfaceRect: {
69
+ left: number;
70
+ top: number;
71
+ right: number;
72
+ bottom: number;
73
+ }, opts?: AutoScrollOptions): AutoScrollResult;
74
+ //# sourceMappingURL=dragHitTest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dragHitTest.d.ts","sourceRoot":"","sources":["../../src/core/dragHitTest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,MAAM,EACtB,iBAAiB,EAAE,MAAM,EACzB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,gBAAgB,EAC9B,aAAa,CAAC,EAAE,MAAM,GACrB,WAAW,CAiCb;AAID,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACzE,IAAI,GAAE,iBAAsB,GAC3B,gBAAgB,CA2ClB"}
@@ -0,0 +1,106 @@
1
+ import { Temporal } from '@js-temporal/polyfill';
2
+ import { CalendarEvent, ViewWindow } from './types';
3
+ /**
4
+ * What changed in the index. Granular kinds let consumers update
5
+ * only affected views: changing one event's color need not
6
+ * re-render the whole month.
7
+ */
8
+ export type IndexInvalidation = {
9
+ kind: 'day';
10
+ date: string;
11
+ } | {
12
+ kind: 'range';
13
+ start: string;
14
+ end: string;
15
+ } | {
16
+ kind: 'series';
17
+ seriesId: string;
18
+ } | {
19
+ kind: 'all';
20
+ };
21
+ export type IndexListener = (invalidation: IndexInvalidation) => void;
22
+ export interface EventIndexOptions {
23
+ /**
24
+ * Display timezone used to bucket timed events into calendar days.
25
+ * **Required** since Phase 8.10-D. Article 4: defaults are not
26
+ * decisions. Pass the user's IANA zone — the same one resolved in
27
+ * `ResolvedLocale.timezone`. Empty string is rejected.
28
+ */
29
+ timezone: string;
30
+ }
31
+ export declare class EventIndex<TMeta extends Record<string, unknown> = Record<string, unknown>> {
32
+ private buckets;
33
+ /**
34
+ * Reverse-index from event id to the day-keys it currently
35
+ * occupies. Used so `update` / `remove` can locate the buckets
36
+ * without scanning.
37
+ */
38
+ private byId;
39
+ private listeners;
40
+ private timezone;
41
+ constructor(opts: EventIndexOptions);
42
+ /**
43
+ * Switch the display zone used to bucket TIMED events into days,
44
+ * and rebuild every bucket. Required when `builder.timezone()`
45
+ * changes at runtime — without a rebuild, near-midnight events
46
+ * stay in the wrong day's bucket and `byDay` / `byRange` lie.
47
+ *
48
+ * Cheap on small data, O(N · avg_span_days) on large data; emits
49
+ * a single `'all'` invalidation so views fully re-render once.
50
+ * No-op when `tz` equals the current zone.
51
+ */
52
+ setTimezone(tz: string): void;
53
+ /** Read the active display zone. Useful for tests / diagnostics. */
54
+ getTimezone(): string;
55
+ /**
56
+ * Insert (or replace) an event. If the event's id already exists,
57
+ * the old version is removed first.
58
+ */
59
+ insert(event: CalendarEvent<TMeta>): void;
60
+ /**
61
+ * Update an existing event. Equivalent to `insert(event)` but
62
+ * emits a `series` invalidation rather than insert's same kind —
63
+ * semantically identical at this level.
64
+ */
65
+ update(event: CalendarEvent<TMeta>): void;
66
+ /**
67
+ * Remove an event by id. No-op if the id is unknown.
68
+ */
69
+ remove(id: string): void;
70
+ private _removeWithoutEmit;
71
+ /** Empty the index. Emits a single `'all'` invalidation. */
72
+ clear(): void;
73
+ /**
74
+ * Bulk-replace the index from a fresh array. Faster than calling
75
+ * `insert` per event because it builds the buckets in one pass
76
+ * and emits a single `'all'` invalidation.
77
+ */
78
+ replaceAll(events: ReadonlyArray<CalendarEvent<TMeta>>): void;
79
+ /** Events that intersect the given day. Order: insertion order. */
80
+ byDay(date: string | Temporal.PlainDate): readonly CalendarEvent<TMeta>[];
81
+ /**
82
+ * Events that intersect the given window. Each event appears at
83
+ * most once in the result, even if it spans multiple days inside
84
+ * the window.
85
+ */
86
+ byRange(window: ViewWindow): readonly CalendarEvent<TMeta>[];
87
+ /** Total number of distinct events currently indexed. */
88
+ get size(): number;
89
+ /** Subscribe to index changes. Returns an unsubscribe fn. */
90
+ subscribe(listener: IndexListener): () => void;
91
+ private emit;
92
+ /**
93
+ * Compute the list of `YYYY-MM-DD` day-keys an event occupies.
94
+ *
95
+ * For all-day events, the span is straightforward (PlainDate
96
+ * arithmetic). For timed events, we re-zone into the configured
97
+ * display zone and bucket by local day; `end` exactly at midnight
98
+ * counts as ending the previous day (RFC 5545 exclusive-end).
99
+ *
100
+ * Defaults:
101
+ * - `end` missing for all-day → `start + 1 day` exclusive
102
+ * - `end` missing for timed → same day as `start` (single bucket)
103
+ */
104
+ private spanDayKeys;
105
+ }
106
+ //# sourceMappingURL=eventIndex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eventIndex.d.ts","sourceRoot":"","sources":["../../src/core/eventIndex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAMzD;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAEpB,MAAM,MAAM,aAAa,GAAG,CAAC,YAAY,EAAE,iBAAiB,KAAK,IAAI,CAAC;AAItE,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,UAAU,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACrF,OAAO,CAAC,OAAO,CAA6C;IAC5D;;;;OAIG;IACH,OAAO,CAAC,IAAI,CAA+B;IAC3C,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,QAAQ,CAAS;gBAEb,IAAI,EAAE,iBAAiB;IAYnC;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IA8B7B,oEAAoE;IACpE,WAAW,IAAI,MAAM;IAMrB;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,IAAI;IAezC;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,IAAI;IAIzC;;OAEG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAMxB,OAAO,CAAC,kBAAkB;IAc1B,4DAA4D;IAC5D,KAAK,IAAI,IAAI;IAMb;;;;OAIG;IACH,UAAU,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI;IAkB7D,mEAAmE;IACnE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,SAAS,GAAG,SAAS,aAAa,CAAC,KAAK,CAAC,EAAE;IAKzE;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,aAAa,CAAC,KAAK,CAAC,EAAE;IAqB5D,yDAAyD;IACzD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAID,6DAA6D;IAC7D,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,IAAI;IAK9C,OAAO,CAAC,IAAI;IAaZ;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,WAAW;CA0CpB"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `@cocoar/vue-calendar/core` — framework-agnostic layer.
3
+ *
4
+ * Files in this directory must NOT import from `../components/` or
5
+ * `../composables/`. The lint rule in the repo root enforces this.
6
+ *
7
+ * Phase 0 contents:
8
+ * - measurementCache.ts — Fenwick-tree-backed variable-size cache
9
+ * - virtualScroll.ts — pure range math + anchor adjustment
10
+ *
11
+ * Phase 1 will add:
12
+ * - temporal.ts — Temporal helpers (lifted from @cocoar/vue-ui)
13
+ * - eventIndex.ts — Map<dateKey, Event[]> with granular invalidation
14
+ * - viewWindow.ts — week/month/agenda window math
15
+ * - overlapLayout.ts — multi-day-bar interval-graph coloring
16
+ * - recurrence.ts — engine abstraction (rrule-rust + rrule.js)
17
+ * - worker/recurrenceWorker.ts — off-main-thread expansion
18
+ */
19
+ export type { CalendarEvent, CalendarView, ViewWindow, ResolvedLocale, RecurringSeries, RecurrencePattern, RecurrenceExpansionWindow, } from './types';
20
+ export { isTimedEvent, isAllDayEvent, validateCalendarEvent, } from './types';
21
+ export { Temporal, type DayOfWeek, detectFirstDayOfWeekFromLocale, detectHour12FromLocale, detectBrowserTimezone, startOfWeek, endOfWeek, startOfMonth, endOfMonth, isoWeekNumber, weekDates, monthGridDates, localizedWeekdayNames, dateKey, eventStartDateInZone, todayInZone, nowInZone, buildFormatOptions, type FormatOverrides, parseScheduledTime, parsePlainDate, formatScheduledTime, type ScheduledTimeWire, type DstPolicy, } from './temporal';
22
+ export { expandSeries, EXPAND_SERIES_NOT_IMPLEMENTED_MESSAGE, } from './recurrence-public';
23
+ export { computeViewWindow, daysInWindow, windowDayCount, windowContainsDate, navigateCursor, type ViewWindowOptions, } from './viewWindow';
24
+ export { layoutDayEvents, layoutAllDayBand, type DayLayoutOptions, type AllDayBandOptions, type PositionedEvent, type AllDayBar, } from './timeGridLayout';
25
+ export { layoutMonthGrid, type MonthLayout, type MonthLayoutOptions, type MonthWeekRow, type MonthMultiDayBar, type MonthCellPill, } from './monthGridLayout';
26
+ export { buildAgendaItems, type AgendaItem, type AgendaHeaderItem, type AgendaEventItem, type AgendaLayoutOptions, } from './agendaLayout';
27
+ export { MeasurementCache } from './measurementCache';
28
+ export { getVisibleRange1D, getVisibleRange2D, computeAnchorAdjustment, type Range1D, type Range2D, } from './virtualScroll';
29
+ export { layoutOverlappingIntervals, type IntervalInput, type IntervalLayout, type LayoutResult, } from './overlapLayout';
30
+ export { hitTestVerticalSurface, computeAutoScrollVelocity, type VerticalHit, type AutoScrollOptions, type AutoScrollResult, } from './dragHitTest';
31
+ export { applyMoveToEvent, buildDropPayload, DstResolutionError, MIN_RESIZE_MINUTES, type CalendarDragMode, type CalendarDropTarget, type DstDisambiguation, type EventDropPayload, type MoveResult, } from './dnd/move-math';
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,YAAY,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EAEd,eAAe,EACf,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,qBAAqB,GACtB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,QAAQ,EACR,KAAK,SAAS,EACd,8BAA8B,EAC9B,sBAAsB,EACtB,qBAAqB,EACrB,WAAW,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,aAAa,EACb,SAAS,EACT,cAAc,EACd,qBAAqB,EACrB,OAAO,EACP,oBAAoB,EACpB,WAAW,EACX,SAAS,EACT,kBAAkB,EAClB,KAAK,eAAe,EAEpB,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,KAAK,iBAAiB,EACtB,KAAK,SAAS,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,YAAY,EACZ,qCAAqC,GACtC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,KAAK,iBAAiB,GACvB,MAAM,cAAc,CAAC;AAYtB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,SAAS,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,aAAa,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AAUxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,uBAAuB,EACvB,KAAK,OAAO,EACZ,KAAK,OAAO,GACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,0BAA0B,EAC1B,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,YAAY,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC;AAOvB,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,UAAU,GAChB,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Variable-size virtualization needs three operations on item sizes, all of
3
+ * them in the hot path of every scroll frame:
4
+ *
5
+ * 1. `prefixSum(i)` — total pixels above item `i`
6
+ * 2. `indexAtOffset(px)` — which item is at scroll offset `px`
7
+ * 3. `set(i, size)` — update size of one item (e.g. on first measure)
8
+ *
9
+ * Naïve implementations are too slow at 10k items. A linear scan for prefix
10
+ * sum is O(n); recomputing every frame burns the entire budget.
11
+ *
12
+ * The implementation here uses a **Binary Indexed Tree (Fenwick tree)** to
13
+ * give all three ops in O(log n). The tree stores per-index *deltas from
14
+ * the size estimate*, so an unmeasured cache is a tree of zeros — no
15
+ * initialization pass needed at construction.
16
+ *
17
+ * prefixSum(i) = i * estimate + fenwickPrefixSum(i)
18
+ * set(i, size) updates fenwick by (size - estimate) - oldDelta
19
+ * indexAtOffset binary-searches over [0, count) using prefixSum
20
+ *
21
+ * For 10.000 items: each op is ≤ 14 fenwick steps × ≤ 14 binary-search
22
+ * steps = ~200 array reads. Sub-microsecond on the dev baseline.
23
+ *
24
+ * The cache is intentionally framework-agnostic. Vue components own
25
+ * `MeasurementCache` instances; they observe size changes via
26
+ * `ResizeObserver` and call `set()` from within a batched rAF.
27
+ */
28
+ export declare class MeasurementCache {
29
+ /** Number of items represented in the cache. */
30
+ private _itemCount;
31
+ /** Estimated size used for any item that has not been measured. */
32
+ private _estimatedSize;
33
+ /**
34
+ * Indices that have a measured (non-estimate) size, mapped to that size.
35
+ * Used to compute the delta on re-measure and to answer `has()`.
36
+ */
37
+ private _measured;
38
+ /**
39
+ * Fenwick tree storing prefix sums of `(measured size - estimate)`. Index
40
+ * 0 is unused per Fenwick convention; tree[i] covers a span ending at i.
41
+ */
42
+ private _fenwick;
43
+ constructor(itemCount: number, estimatedSize: number);
44
+ get itemCount(): number;
45
+ get estimatedSize(): number;
46
+ get measuredCount(): number;
47
+ /** Returns true if `index` has a measured size (not just the estimate). */
48
+ has(index: number): boolean;
49
+ /** Size at `index` — measured value if present, else estimate. */
50
+ get(index: number): number;
51
+ /** Record a measured size for `index`. */
52
+ set(index: number, size: number): void;
53
+ /** Drop a measured value, falling back to the estimate. */
54
+ unset(index: number): void;
55
+ /** Drop everything; the cache reverts to all-estimates. */
56
+ clear(): void;
57
+ /**
58
+ * Change the item count. Existing measured indices that fall outside the
59
+ * new range are dropped. The caller is responsible for re-keying after a
60
+ * splice (inserting/removing in the middle invalidates indices above the
61
+ * splice point and is not handled here — `clear()` and remeasure is the
62
+ * supported path for that).
63
+ */
64
+ resize(newCount: number): void;
65
+ /**
66
+ * Sum of sizes of items in `[0, index)`. Returns 0 for `index <= 0`.
67
+ * Clamps `index > itemCount` to `itemCount`.
68
+ *
69
+ * O(log n).
70
+ */
71
+ prefixSum(index: number): number;
72
+ /** Total size = `prefixSum(itemCount)`. O(log n). */
73
+ totalSize(): number;
74
+ /**
75
+ * Smallest `i` such that `prefixSum(i + 1) > offset`. In other words:
76
+ * which item contains the pixel at `offset`?
77
+ *
78
+ * Returns 0 for `offset <= 0`. Returns `itemCount - 1` if offset is at or
79
+ * past the end (and `itemCount > 0`). Returns 0 if `itemCount === 0`.
80
+ *
81
+ * O(log² n) via binary search over `prefixSum`. Could be reduced to
82
+ * O(log n) with a Fenwick descent, but log² × log = ~200 reads at 10k
83
+ * items, dwarfed by the surrounding work. We keep the simpler form.
84
+ */
85
+ indexAtOffset(offset: number): number;
86
+ }
87
+ //# sourceMappingURL=measurementCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measurementCache.d.ts","sourceRoot":"","sources":["../../src/core/measurementCache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,qBAAa,gBAAgB;IAC3B,gDAAgD;IAChD,OAAO,CAAC,UAAU,CAAS;IAE3B,mEAAmE;IACnE,OAAO,CAAC,cAAc,CAAS;IAE/B;;;OAGG;IACH,OAAO,CAAC,SAAS,CAAsB;IAEvC;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAe;gBAEnB,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM;IAepD,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,2EAA2E;IAC3E,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAI3B,kEAAkE;IAClE,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAS1B,0CAA0C;IAC1C,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAwBtC,2DAA2D;IAC3D,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAiB1B,2DAA2D;IAC3D,KAAK,IAAI,IAAI;IAKb;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IA8B9B;;;;;OAKG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAahC,qDAAqD;IACrD,SAAS,IAAI,MAAM;IAInB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;CAkBtC"}
@@ -0,0 +1,68 @@
1
+ import { CalendarEvent } from './types';
2
+ import { Temporal } from './temporal';
3
+ export interface MonthMultiDayBar<TMeta extends Record<string, unknown> = Record<string, unknown>> {
4
+ event: CalendarEvent<TMeta>;
5
+ /** Lane within the week row. 0 = topmost. */
6
+ lane: number;
7
+ /** Total lanes used in this week row. */
8
+ laneCount: number;
9
+ /** Column 0..6 inclusive — first cell the bar covers. */
10
+ startCol: number;
11
+ /** Column 0..6 inclusive — last cell the bar covers. */
12
+ endCol: number;
13
+ /** True when the event extends earlier than the row's first day. */
14
+ clippedStart: boolean;
15
+ /** True when the event extends later than the row's last day. */
16
+ clippedEnd: boolean;
17
+ }
18
+ /**
19
+ * Single-day event rendered as a pill inside a specific cell.
20
+ * "Single-day" here means the event occupies exactly ONE day in
21
+ * the calendar — either a date-only event with no end, a date-only
22
+ * event with end == start + 1 day (RFC 5545 single-day all-day),
23
+ * or any timed event (timed events always render as pills, even
24
+ * if their wall-clock spans midnight, because their visual home
25
+ * in the month view is the start day).
26
+ */
27
+ export interface MonthCellPill<TMeta extends Record<string, unknown> = Record<string, unknown>> {
28
+ event: CalendarEvent<TMeta>;
29
+ /** Visual order within the cell. Stable across renders for the
30
+ * same input. */
31
+ order: number;
32
+ }
33
+ export interface MonthWeekRow<TMeta extends Record<string, unknown> = Record<string, unknown>> {
34
+ /** 0..5. */
35
+ rowIndex: number;
36
+ /** First date of the row (the cell-0 date). */
37
+ weekStart: Temporal.PlainDate;
38
+ /** All 7 dates of the row, in column order. */
39
+ days: ReadonlyArray<Temporal.PlainDate>;
40
+ /** Multi-day bars on this row, lane-resolved. */
41
+ multiDayBars: ReadonlyArray<MonthMultiDayBar<TMeta>>;
42
+ /** Per-cell single-day pills, indexed by `dateKey()`. */
43
+ cellPills: ReadonlyMap<string, ReadonlyArray<MonthCellPill<TMeta>>>;
44
+ }
45
+ export interface MonthLayout<TMeta extends Record<string, unknown> = Record<string, unknown>> {
46
+ /** Always 6 rows. */
47
+ weekRows: ReadonlyArray<MonthWeekRow<TMeta>>;
48
+ }
49
+ export interface MonthLayoutOptions {
50
+ /**
51
+ * The 42 dates of the month grid, in row-major order
52
+ * (cell 0 = top-left, cell 6 = top-right, cell 7 = next row,
53
+ * etc.). Use `monthGridDates(yearMonth, firstDayOfWeek)` from
54
+ * temporal.ts.
55
+ */
56
+ gridDates: ReadonlyArray<Temporal.PlainDate>;
57
+ /** Timezone for resolving timed events. */
58
+ timezone: string;
59
+ }
60
+ /**
61
+ * Compute the full layout for a month grid.
62
+ *
63
+ * Multi-day events that span across week-row boundaries are
64
+ * rendered as multiple bars (one per affected row), each correctly
65
+ * clipped to its row.
66
+ */
67
+ export declare function layoutMonthGrid<TMeta extends Record<string, unknown> = Record<string, unknown>>(events: ReadonlyArray<CalendarEvent<TMeta>>, opts: MonthLayoutOptions): MonthLayout<TMeta>;
68
+ //# sourceMappingURL=monthGridLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monthGridLayout.d.ts","sourceRoot":"","sources":["../../src/core/monthGridLayout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAM7C,OAAO,EAAE,QAAQ,EAAiC,MAAM,YAAY,CAAC;AAIrE,MAAM,WAAW,gBAAgB,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC/F,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;IAC5B,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,YAAY,EAAE,OAAO,CAAC;IACtB,iEAAiE;IACjE,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC5F,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;IAC5B;sBACkB;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3F,YAAY;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC;IAC9B,+CAA+C;IAC/C,IAAI,EAAE,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACxC,iDAAiD;IACjD,YAAY,EAAE,aAAa,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC;IACrD,yDAAyD;IACzD,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;CACrE;AAED,MAAM,WAAW,WAAW,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1F,qBAAqB;IACrB,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;CAC9C;AAID,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,SAAS,EAAE,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC7C,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7F,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAC3C,IAAI,EAAE,kBAAkB,GACvB,WAAW,CAAC,KAAK,CAAC,CAkJpB"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Multi-day-bar layout via interval-graph coloring.
3
+ *
4
+ * The classic calendar problem: given N events that span ranges of
5
+ * columns (e.g. days within a week row, or hours within a day
6
+ * column), assign each event a lane (vertical row inside the cell)
7
+ * such that no two events share a (lane × column) cell.
8
+ *
9
+ * Interval graphs are *perfect graphs*: their chromatic number
10
+ * equals their clique number. Greedy first-fit coloring sorted by
11
+ * start position is optimal — it uses the minimum number of lanes
12
+ * needed (= maximum overlap depth at any column).
13
+ *
14
+ * ── Algorithm ─────────────────────────────────────────────────────
15
+ *
16
+ * 1. Sort intervals by start ASC, then by end DESC, then by id ASC
17
+ * (the secondary keys break ties deterministically; the primary
18
+ * key is what makes the algorithm work).
19
+ * 2. Maintain a min-heap of `(endCol, laneId)` for intervals currently
20
+ * active at the cursor's column. The heap top is the lane that
21
+ * will free up earliest.
22
+ * 3. For each interval in sorted order:
23
+ * a. Pop from the heap every entry whose `endCol < interval.start`
24
+ * (those lanes are now free).
25
+ * b. Pick the lowest-numbered free lane. We track free lanes in a
26
+ * separate min-heap so this is O(log n).
27
+ * c. Assign the interval that lane and push `(interval.end, lane)`
28
+ * onto the active heap.
29
+ *
30
+ * Total: O(n log n). For n = 1.000 events that's ~ 10.000 heap ops,
31
+ * each a handful of array accesses — sub-millisecond on Tier A.
32
+ *
33
+ * ── Why a separate "free lane" structure ──────────────────────────
34
+ *
35
+ * Without it, "lowest-numbered free lane" would require scanning all
36
+ * possibly-free lanes at each step → O(n²) in the worst case.
37
+ *
38
+ * The free-lane heap stores lane-ids in ascending order. When a lane
39
+ * becomes free (its interval expired), we push it back. When we need
40
+ * a lane, we pop the smallest. If the heap is empty, we allocate a
41
+ * new lane (`laneCount++`).
42
+ *
43
+ * The implementation here uses simple binary heaps in flat arrays —
44
+ * no library dependency, ~ 30 lines, easy to verify.
45
+ */
46
+ export interface IntervalInput {
47
+ /** Stable identifier preserved into the output. */
48
+ id: string;
49
+ /** Inclusive start column. */
50
+ start: number;
51
+ /** Inclusive end column. `end >= start`. */
52
+ end: number;
53
+ }
54
+ export interface IntervalLayout {
55
+ id: string;
56
+ /** Assigned lane. 0-indexed. Stable across calls for the same input. */
57
+ lane: number;
58
+ start: number;
59
+ end: number;
60
+ }
61
+ export interface LayoutResult {
62
+ /** One entry per input interval, in unspecified output order. */
63
+ bars: ReadonlyArray<IntervalLayout>;
64
+ /**
65
+ * Number of distinct lanes used. Equals the maximum overlap depth
66
+ * at any column (= clique number of the interval graph).
67
+ */
68
+ laneCount: number;
69
+ }
70
+ /**
71
+ * Greedy interval-graph coloring. Given N intervals, returns each
72
+ * interval annotated with a lane index such that no two intervals
73
+ * share a (lane, column) cell. The number of lanes used is optimal
74
+ * (= maximum overlap depth).
75
+ *
76
+ * Behavior on edge cases:
77
+ * - Empty input: `{ bars: [], laneCount: 0 }`.
78
+ * - Single interval: assigned lane 0; laneCount 1.
79
+ * - Non-overlapping intervals: all on lane 0; laneCount 1.
80
+ * - All intervals identical: each gets its own lane; laneCount = N.
81
+ * - `end < start` is rejected at input validation.
82
+ *
83
+ * Output order is NOT preserved from input — bars come out in the
84
+ * order they were processed (sorted-by-start). The `id` field is
85
+ * the stable handle.
86
+ */
87
+ export declare function layoutOverlappingIntervals(intervals: ReadonlyArray<IntervalInput>): LayoutResult;
88
+ //# sourceMappingURL=overlapLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlapLayout.d.ts","sourceRoot":"","sources":["../../src/core/overlapLayout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,IAAI,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACpC;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AA0FD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,aAAa,CAAC,aAAa,CAAC,GACtC,YAAY,CAqDd"}