@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.
- package/README.md +257 -0
- package/dist/__test-utils__/event-fixtures.d.ts +4 -0
- package/dist/__test-utils__/event-fixtures.d.ts.map +1 -0
- package/dist/builders/calendar-builder-internals.d.ts +14 -0
- package/dist/builders/calendar-builder-internals.d.ts.map +1 -0
- package/dist/builders/calendar-builder.d.ts +296 -0
- package/dist/builders/calendar-builder.d.ts.map +1 -0
- package/dist/builders/event-zone-hints.d.ts +20 -0
- package/dist/builders/event-zone-hints.d.ts.map +1 -0
- package/dist/builders/render-helpers.d.ts +19 -0
- package/dist/builders/render-helpers.d.ts.map +1 -0
- package/dist/builders/types.d.ts +175 -0
- package/dist/builders/types.d.ts.map +1 -0
- package/dist/components/CoarCalendar.vue.d.ts +150 -0
- package/dist/components/CoarCalendar.vue.d.ts.map +1 -0
- package/dist/components/CoarDisplayZoneSwitcher.vue.d.ts +28 -0
- package/dist/components/CoarDisplayZoneSwitcher.vue.d.ts.map +1 -0
- package/dist/components/VirtualizedSurface1DY.vue.d.ts +90 -0
- package/dist/components/VirtualizedSurface1DY.vue.d.ts.map +1 -0
- package/dist/components/VirtualizedSurface2D.vue.d.ts +71 -0
- package/dist/components/VirtualizedSurface2D.vue.d.ts.map +1 -0
- package/dist/components/internal/CoarEventDecorations.vue.d.ts +13 -0
- package/dist/components/internal/CoarEventDecorations.vue.d.ts.map +1 -0
- package/dist/components/internal/RenderEventFallback.d.ts +17 -0
- package/dist/components/internal/RenderEventFallback.d.ts.map +1 -0
- package/dist/components/internal/agenda/CoarAgendaDayHeader.vue.d.ts +54 -0
- package/dist/components/internal/agenda/CoarAgendaDayHeader.vue.d.ts.map +1 -0
- package/dist/components/internal/month/CoarMonthBar.vue.d.ts +80 -0
- package/dist/components/internal/month/CoarMonthBar.vue.d.ts.map +1 -0
- package/dist/components/internal/month/CoarMonthCell.vue.d.ts +74 -0
- package/dist/components/internal/month/CoarMonthCell.vue.d.ts.map +1 -0
- package/dist/components/internal/month/CoarMonthGrid.vue.d.ts +50 -0
- package/dist/components/internal/month/CoarMonthGrid.vue.d.ts.map +1 -0
- package/dist/components/internal/month/CoarMonthPill.vue.d.ts +83 -0
- package/dist/components/internal/month/CoarMonthPill.vue.d.ts.map +1 -0
- package/dist/components/internal/month/CoarMonthRow.vue.d.ts +44 -0
- package/dist/components/internal/month/CoarMonthRow.vue.d.ts.map +1 -0
- package/dist/components/internal/time-grid/CoarTimeGridAllDayBand.vue.d.ts +39 -0
- package/dist/components/internal/time-grid/CoarTimeGridAllDayBand.vue.d.ts.map +1 -0
- package/dist/components/internal/time-grid/CoarTimeGridColumn.vue.d.ts +44 -0
- package/dist/components/internal/time-grid/CoarTimeGridColumn.vue.d.ts.map +1 -0
- package/dist/components/internal/time-grid/CoarTimeGridHeader.vue.d.ts +44 -0
- package/dist/components/internal/time-grid/CoarTimeGridHeader.vue.d.ts.map +1 -0
- package/dist/components/internal/time-grid/CoarTimeGridNowMarker.vue.d.ts +18 -0
- package/dist/components/internal/time-grid/CoarTimeGridNowMarker.vue.d.ts.map +1 -0
- package/dist/composables/useA11yAnnouncer.d.ts +9 -0
- package/dist/composables/useA11yAnnouncer.d.ts.map +1 -0
- package/dist/composables/useCalendarDnd.d.ts +212 -0
- package/dist/composables/useCalendarDnd.d.ts.map +1 -0
- package/dist/composables/useCoarDrag.d.ts +97 -0
- package/dist/composables/useCoarDrag.d.ts.map +1 -0
- package/dist/composables/useMonthDnd.d.ts +89 -0
- package/dist/composables/useMonthDnd.d.ts.map +1 -0
- package/dist/composables/useMonthExpansion.d.ts +35 -0
- package/dist/composables/useMonthExpansion.d.ts.map +1 -0
- package/dist/composables/useTimeGridDnd.d.ts +104 -0
- package/dist/composables/useTimeGridDnd.d.ts.map +1 -0
- package/dist/composables/useViewWindow.d.ts +14 -0
- package/dist/composables/useViewWindow.d.ts.map +1 -0
- package/dist/core/agendaLayout.d.ts +47 -0
- package/dist/core/agendaLayout.d.ts.map +1 -0
- package/dist/core/dnd/move-math.d.ts +136 -0
- package/dist/core/dnd/move-math.d.ts.map +1 -0
- package/dist/core/dragHitTest.d.ts +74 -0
- package/dist/core/dragHitTest.d.ts.map +1 -0
- package/dist/core/eventIndex.d.ts +106 -0
- package/dist/core/eventIndex.d.ts.map +1 -0
- package/dist/core/index.d.ts +32 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/measurementCache.d.ts +87 -0
- package/dist/core/measurementCache.d.ts.map +1 -0
- package/dist/core/monthGridLayout.d.ts +68 -0
- package/dist/core/monthGridLayout.d.ts.map +1 -0
- package/dist/core/overlapLayout.d.ts +88 -0
- package/dist/core/overlapLayout.d.ts.map +1 -0
- package/dist/core/recurrence-public.d.ts +59 -0
- package/dist/core/recurrence-public.d.ts.map +1 -0
- package/dist/core/recurrence.d.ts +112 -0
- package/dist/core/recurrence.d.ts.map +1 -0
- package/dist/core/recurrenceWorker.d.ts +62 -0
- package/dist/core/recurrenceWorker.d.ts.map +1 -0
- package/dist/core/temporal.d.ts +214 -0
- package/dist/core/temporal.d.ts.map +1 -0
- package/dist/core/timeGridLayout.d.ts +109 -0
- package/dist/core/timeGridLayout.d.ts.map +1 -0
- package/dist/core/types.d.ts +211 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/viewWindow.d.ts +53 -0
- package/dist/core/viewWindow.d.ts.map +1 -0
- package/dist/core/virtualScroll.d.ts +91 -0
- package/dist/core/virtualScroll.d.ts.map +1 -0
- package/dist/core-DK63eHat.js +959 -0
- package/dist/core.js +2 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4221 -0
- package/dist/useAgendaView.d.ts +6 -0
- package/dist/useAgendaView.d.ts.map +1 -0
- package/dist/useCalendar.d.ts +6 -0
- package/dist/useCalendar.d.ts.map +1 -0
- package/dist/useDayView.d.ts +6 -0
- package/dist/useDayView.d.ts.map +1 -0
- package/dist/useMonthView.d.ts +6 -0
- package/dist/useMonthView.d.ts.map +1 -0
- package/dist/useWeekView.d.ts +6 -0
- package/dist/useWeekView.d.ts.map +1 -0
- package/dist/vue-calendar.css +2 -0
- 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"}
|