@liteforge/calendar 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +395 -0
- package/dist/calendar.d.ts +6 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +318 -0
- package/dist/calendar.js.map +1 -0
- package/dist/components/toolbar.d.ts +18 -0
- package/dist/components/toolbar.d.ts.map +1 -0
- package/dist/components/toolbar.js +84 -0
- package/dist/components/toolbar.js.map +1 -0
- package/dist/date-utils.d.ts +104 -0
- package/dist/date-utils.d.ts.map +1 -0
- package/dist/date-utils.js +323 -0
- package/dist/date-utils.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/interactions/drag-drop.d.ts +30 -0
- package/dist/interactions/drag-drop.d.ts.map +1 -0
- package/dist/interactions/drag-drop.js +207 -0
- package/dist/interactions/drag-drop.js.map +1 -0
- package/dist/interactions/index.d.ts +10 -0
- package/dist/interactions/index.d.ts.map +1 -0
- package/dist/interactions/index.js +7 -0
- package/dist/interactions/index.js.map +1 -0
- package/dist/interactions/resize.d.ts +25 -0
- package/dist/interactions/resize.d.ts.map +1 -0
- package/dist/interactions/resize.js +116 -0
- package/dist/interactions/resize.js.map +1 -0
- package/dist/interactions/slot-selection.d.ts +25 -0
- package/dist/interactions/slot-selection.d.ts.map +1 -0
- package/dist/interactions/slot-selection.js +151 -0
- package/dist/interactions/slot-selection.js.map +1 -0
- package/dist/recurring.d.ts +15 -0
- package/dist/recurring.d.ts.map +1 -0
- package/dist/recurring.js +158 -0
- package/dist/recurring.js.map +1 -0
- package/dist/styles.d.ts +6 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +632 -0
- package/dist/styles.js.map +1 -0
- package/dist/types.d.ts +169 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/views/agenda-view.d.ts +19 -0
- package/dist/views/agenda-view.d.ts.map +1 -0
- package/dist/views/agenda-view.js +114 -0
- package/dist/views/agenda-view.js.map +1 -0
- package/dist/views/day-view.d.ts +30 -0
- package/dist/views/day-view.d.ts.map +1 -0
- package/dist/views/day-view.js +432 -0
- package/dist/views/day-view.js.map +1 -0
- package/dist/views/month-view.d.ts +17 -0
- package/dist/views/month-view.d.ts.map +1 -0
- package/dist/views/month-view.js +123 -0
- package/dist/views/month-view.js.map +1 -0
- package/dist/views/shared.d.ts +30 -0
- package/dist/views/shared.d.ts.map +1 -0
- package/dist/views/shared.js +281 -0
- package/dist/views/shared.js.map +1 -0
- package/dist/views/week-view.d.ts +24 -0
- package/dist/views/week-view.d.ts.map +1 -0
- package/dist/views/week-view.js +377 -0
- package/dist/views/week-view.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/interactions/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAGxD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAG/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @liteforge/calendar - Event Resize Interaction
|
|
3
|
+
*
|
|
4
|
+
* Enables resizing events by dragging the bottom edge.
|
|
5
|
+
*/
|
|
6
|
+
import type { CalendarEvent, ResolvedTimeConfig } from '../types.js';
|
|
7
|
+
export interface ResizeOptions<T extends CalendarEvent> {
|
|
8
|
+
dayColumn: HTMLElement;
|
|
9
|
+
day: Date;
|
|
10
|
+
config: ResolvedTimeConfig;
|
|
11
|
+
onEventResize?: (event: T, newEnd: Date) => void;
|
|
12
|
+
}
|
|
13
|
+
export interface ResizeState<T extends CalendarEvent> {
|
|
14
|
+
isResizing: boolean;
|
|
15
|
+
event: T | null;
|
|
16
|
+
eventElement: HTMLElement | null;
|
|
17
|
+
originalEnd: Date | null;
|
|
18
|
+
startY: number;
|
|
19
|
+
cleanup: () => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Set up resize for an event element.
|
|
23
|
+
*/
|
|
24
|
+
export declare function setupEventResize<T extends CalendarEvent>(eventElement: HTMLElement, resizeHandle: HTMLElement, event: T, options: ResizeOptions<T>): ResizeState<T>;
|
|
25
|
+
//# sourceMappingURL=resize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resize.d.ts","sourceRoot":"","sources":["../../src/interactions/resize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAGpE,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,aAAa;IACpD,SAAS,EAAE,WAAW,CAAA;IACtB,GAAG,EAAE,IAAI,CAAA;IACT,MAAM,EAAE,kBAAkB,CAAA;IAC1B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,KAAK,IAAI,CAAA;CACjD;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,aAAa;IAClD,UAAU,EAAE,OAAO,CAAA;IACnB,KAAK,EAAE,CAAC,GAAG,IAAI,CAAA;IACf,YAAY,EAAE,WAAW,GAAG,IAAI,CAAA;IAChC,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAsCD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,aAAa,EACtD,YAAY,EAAE,WAAW,EACzB,YAAY,EAAE,WAAW,EACzB,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GACxB,WAAW,CAAC,CAAC,CAAC,CAsGhB"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @liteforge/calendar - Event Resize Interaction
|
|
3
|
+
*
|
|
4
|
+
* Enables resizing events by dragging the bottom edge.
|
|
5
|
+
*/
|
|
6
|
+
import { addMinutes } from '../date-utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Calculate new end time based on resize position.
|
|
9
|
+
*/
|
|
10
|
+
function calculateNewEnd(y, dayColumn, day, config, eventStart) {
|
|
11
|
+
const rect = dayColumn.getBoundingClientRect();
|
|
12
|
+
const relativeY = y - rect.top;
|
|
13
|
+
const slotHeight = 40; // CSS variable --lf-cal-slot-height default
|
|
14
|
+
const totalSlots = (config.dayEnd - config.dayStart) * (60 / config.slotDuration);
|
|
15
|
+
// Calculate which slot index we're in
|
|
16
|
+
const slotIndex = Math.floor(relativeY / slotHeight);
|
|
17
|
+
const clampedIndex = Math.max(0, Math.min(slotIndex, totalSlots - 1));
|
|
18
|
+
// Convert to time
|
|
19
|
+
const minutesFromStart = (clampedIndex + 1) * config.slotDuration;
|
|
20
|
+
const hours = config.dayStart + Math.floor(minutesFromStart / 60);
|
|
21
|
+
const minutes = minutesFromStart % 60;
|
|
22
|
+
const newEnd = new Date(day);
|
|
23
|
+
newEnd.setHours(hours, minutes, 0, 0);
|
|
24
|
+
// Ensure minimum duration of 1 slot
|
|
25
|
+
const minEnd = addMinutes(eventStart, config.slotDuration);
|
|
26
|
+
if (newEnd < minEnd) {
|
|
27
|
+
return minEnd;
|
|
28
|
+
}
|
|
29
|
+
return newEnd;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Set up resize for an event element.
|
|
33
|
+
*/
|
|
34
|
+
export function setupEventResize(eventElement, resizeHandle, event, options) {
|
|
35
|
+
const { dayColumn, day, config, onEventResize } = options;
|
|
36
|
+
const state = {
|
|
37
|
+
isResizing: false,
|
|
38
|
+
event: null,
|
|
39
|
+
eventElement: null,
|
|
40
|
+
originalEnd: null,
|
|
41
|
+
startY: 0,
|
|
42
|
+
cleanup: () => { },
|
|
43
|
+
};
|
|
44
|
+
let previewEndTime = null;
|
|
45
|
+
const handlePointerDown = (e) => {
|
|
46
|
+
// Only handle left click
|
|
47
|
+
if (e.button !== 0)
|
|
48
|
+
return;
|
|
49
|
+
e.stopPropagation();
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
state.isResizing = true;
|
|
52
|
+
state.event = event;
|
|
53
|
+
state.eventElement = eventElement;
|
|
54
|
+
state.originalEnd = new Date(event.end);
|
|
55
|
+
state.startY = e.clientY;
|
|
56
|
+
// Mark as resizing
|
|
57
|
+
eventElement.classList.add('lf-cal-event--resizing');
|
|
58
|
+
// Prevent text selection
|
|
59
|
+
document.body.style.userSelect = 'none';
|
|
60
|
+
document.body.style.cursor = 'ns-resize';
|
|
61
|
+
// Add listeners
|
|
62
|
+
document.addEventListener('pointermove', handlePointerMove);
|
|
63
|
+
document.addEventListener('pointerup', handlePointerUp);
|
|
64
|
+
};
|
|
65
|
+
const handlePointerMove = (e) => {
|
|
66
|
+
if (!state.isResizing || !state.event || !state.eventElement)
|
|
67
|
+
return;
|
|
68
|
+
// Calculate new end time
|
|
69
|
+
previewEndTime = calculateNewEnd(e.clientY, dayColumn, day, config, state.event.start);
|
|
70
|
+
// Update visual height
|
|
71
|
+
const slotHeight = 40;
|
|
72
|
+
const startMinutes = (state.event.start.getHours() - config.dayStart) * 60 + state.event.start.getMinutes();
|
|
73
|
+
const endMinutes = (previewEndTime.getHours() - config.dayStart) * 60 + previewEndTime.getMinutes();
|
|
74
|
+
const durationMinutes = endMinutes - startMinutes;
|
|
75
|
+
const newHeight = (durationMinutes / config.slotDuration) * slotHeight;
|
|
76
|
+
state.eventElement.style.height = `${Math.max(newHeight, slotHeight / 2)}px`;
|
|
77
|
+
};
|
|
78
|
+
const handlePointerUp = (_e) => {
|
|
79
|
+
// Clean up listeners
|
|
80
|
+
document.removeEventListener('pointermove', handlePointerMove);
|
|
81
|
+
document.removeEventListener('pointerup', handlePointerUp);
|
|
82
|
+
if (!state.isResizing || !state.event || !previewEndTime) {
|
|
83
|
+
resetState();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Remove resizing class
|
|
87
|
+
if (state.eventElement) {
|
|
88
|
+
state.eventElement.classList.remove('lf-cal-event--resizing');
|
|
89
|
+
}
|
|
90
|
+
// Call resize handler
|
|
91
|
+
if (onEventResize && state.event) {
|
|
92
|
+
onEventResize(state.event, previewEndTime);
|
|
93
|
+
}
|
|
94
|
+
resetState();
|
|
95
|
+
};
|
|
96
|
+
const resetState = () => {
|
|
97
|
+
state.isResizing = false;
|
|
98
|
+
state.event = null;
|
|
99
|
+
state.eventElement = null;
|
|
100
|
+
state.originalEnd = null;
|
|
101
|
+
document.body.style.userSelect = '';
|
|
102
|
+
document.body.style.cursor = '';
|
|
103
|
+
previewEndTime = null;
|
|
104
|
+
};
|
|
105
|
+
// Attach listener to resize handle
|
|
106
|
+
resizeHandle.addEventListener('pointerdown', handlePointerDown);
|
|
107
|
+
// Store cleanup
|
|
108
|
+
state.cleanup = () => {
|
|
109
|
+
resizeHandle.removeEventListener('pointerdown', handlePointerDown);
|
|
110
|
+
document.removeEventListener('pointermove', handlePointerMove);
|
|
111
|
+
document.removeEventListener('pointerup', handlePointerUp);
|
|
112
|
+
resetState();
|
|
113
|
+
};
|
|
114
|
+
return state;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=resize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resize.js","sourceRoot":"","sources":["../../src/interactions/resize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @liteforge/calendar - Slot Selection Interaction
|
|
3
|
+
*
|
|
4
|
+
* Enables selecting time ranges by clicking and dragging on empty slots.
|
|
5
|
+
*/
|
|
6
|
+
import type { ResolvedTimeConfig } from '../types.js';
|
|
7
|
+
export interface SlotSelectionOptions {
|
|
8
|
+
slotsContainer: HTMLElement;
|
|
9
|
+
day: Date;
|
|
10
|
+
config: ResolvedTimeConfig;
|
|
11
|
+
resourceId?: string | undefined;
|
|
12
|
+
onSlotClick?: ((start: Date, end: Date, resourceId?: string) => void) | undefined;
|
|
13
|
+
onSlotSelect?: ((start: Date, end: Date, resourceId?: string) => void) | undefined;
|
|
14
|
+
}
|
|
15
|
+
export interface SlotSelectionState {
|
|
16
|
+
isSelecting: boolean;
|
|
17
|
+
startSlot: Date | null;
|
|
18
|
+
currentSlot: Date | null;
|
|
19
|
+
cleanup: () => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Set up slot selection interaction on a slots container.
|
|
23
|
+
*/
|
|
24
|
+
export declare function setupSlotSelection(options: SlotSelectionOptions): SlotSelectionState;
|
|
25
|
+
//# sourceMappingURL=slot-selection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slot-selection.d.ts","sourceRoot":"","sources":["../../src/interactions/slot-selection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAGrD,MAAM,WAAW,oBAAoB;IACnC,cAAc,EAAE,WAAW,CAAA;IAC3B,GAAG,EAAE,IAAI,CAAA;IACT,MAAM,EAAE,kBAAkB,CAAA;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACjF,YAAY,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;CACnF;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAqED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG,kBAAkB,CAgHpF"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @liteforge/calendar - Slot Selection Interaction
|
|
3
|
+
*
|
|
4
|
+
* Enables selecting time ranges by clicking and dragging on empty slots.
|
|
5
|
+
*/
|
|
6
|
+
import { addMinutes } from '../date-utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Calculate slot time from Y position within the slots container.
|
|
9
|
+
*/
|
|
10
|
+
function getSlotTimeFromY(y, containerRect, day, config) {
|
|
11
|
+
const relativeY = y - containerRect.top;
|
|
12
|
+
const slotHeight = 40; // CSS variable --lf-cal-slot-height default
|
|
13
|
+
const totalSlots = (config.dayEnd - config.dayStart) * (60 / config.slotDuration);
|
|
14
|
+
// Calculate which slot index we're in
|
|
15
|
+
const slotIndex = Math.floor(relativeY / slotHeight);
|
|
16
|
+
const clampedIndex = Math.max(0, Math.min(slotIndex, totalSlots - 1));
|
|
17
|
+
// Convert to time
|
|
18
|
+
const minutesFromStart = clampedIndex * config.slotDuration;
|
|
19
|
+
const hours = config.dayStart + Math.floor(minutesFromStart / 60);
|
|
20
|
+
const minutes = minutesFromStart % 60;
|
|
21
|
+
const result = new Date(day);
|
|
22
|
+
result.setHours(hours, minutes, 0, 0);
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Highlight slots between start and current selection.
|
|
27
|
+
*/
|
|
28
|
+
function highlightSlots(slotsContainer, startSlot, endSlot, config) {
|
|
29
|
+
// Clear existing highlights
|
|
30
|
+
clearHighlights(slotsContainer);
|
|
31
|
+
const slots = slotsContainer.children;
|
|
32
|
+
const startMinutes = (startSlot.getHours() - config.dayStart) * 60 + startSlot.getMinutes();
|
|
33
|
+
const endMinutes = (endSlot.getHours() - config.dayStart) * 60 + endSlot.getMinutes();
|
|
34
|
+
const minMinutes = Math.min(startMinutes, endMinutes);
|
|
35
|
+
const maxMinutes = Math.max(startMinutes, endMinutes);
|
|
36
|
+
const startIndex = Math.floor(minMinutes / config.slotDuration);
|
|
37
|
+
const endIndex = Math.floor(maxMinutes / config.slotDuration);
|
|
38
|
+
for (let i = startIndex; i <= endIndex && i < slots.length; i++) {
|
|
39
|
+
const slot = slots[i];
|
|
40
|
+
if (slot) {
|
|
41
|
+
slot.classList.add('lf-cal-time-slot--selected');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Clear all slot highlights.
|
|
47
|
+
*/
|
|
48
|
+
function clearHighlights(slotsContainer) {
|
|
49
|
+
const slots = slotsContainer.querySelectorAll('.lf-cal-time-slot--selected');
|
|
50
|
+
slots.forEach((slot) => {
|
|
51
|
+
slot.classList.remove('lf-cal-time-slot--selected');
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Set up slot selection interaction on a slots container.
|
|
56
|
+
*/
|
|
57
|
+
export function setupSlotSelection(options) {
|
|
58
|
+
const { slotsContainer, day, config, resourceId, onSlotClick, onSlotSelect } = options;
|
|
59
|
+
const state = {
|
|
60
|
+
isSelecting: false,
|
|
61
|
+
startSlot: null,
|
|
62
|
+
currentSlot: null,
|
|
63
|
+
cleanup: () => { },
|
|
64
|
+
};
|
|
65
|
+
let containerRect = null;
|
|
66
|
+
const handlePointerDown = (e) => {
|
|
67
|
+
// Only handle left click
|
|
68
|
+
if (e.button !== 0)
|
|
69
|
+
return;
|
|
70
|
+
// Ignore clicks on events
|
|
71
|
+
const target = e.target;
|
|
72
|
+
if (target.closest('.lf-cal-event'))
|
|
73
|
+
return;
|
|
74
|
+
containerRect = slotsContainer.getBoundingClientRect();
|
|
75
|
+
const startTime = getSlotTimeFromY(e.clientY, containerRect, day, config);
|
|
76
|
+
state.isSelecting = true;
|
|
77
|
+
state.startSlot = startTime;
|
|
78
|
+
state.currentSlot = startTime;
|
|
79
|
+
// Highlight initial slot
|
|
80
|
+
highlightSlots(slotsContainer, startTime, startTime, config);
|
|
81
|
+
// Prevent text selection during drag
|
|
82
|
+
document.body.style.userSelect = 'none';
|
|
83
|
+
// Capture pointer for tracking (not available in all test environments)
|
|
84
|
+
if (target.setPointerCapture) {
|
|
85
|
+
target.setPointerCapture(e.pointerId);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const handlePointerMove = (e) => {
|
|
89
|
+
if (!state.isSelecting || !state.startSlot || !containerRect)
|
|
90
|
+
return;
|
|
91
|
+
const currentTime = getSlotTimeFromY(e.clientY, containerRect, day, config);
|
|
92
|
+
state.currentSlot = currentTime;
|
|
93
|
+
// Update highlight
|
|
94
|
+
highlightSlots(slotsContainer, state.startSlot, currentTime, config);
|
|
95
|
+
};
|
|
96
|
+
const handlePointerUp = (_e) => {
|
|
97
|
+
if (!state.isSelecting || !state.startSlot) {
|
|
98
|
+
state.isSelecting = false;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const endTime = state.currentSlot ?? state.startSlot;
|
|
102
|
+
// Determine start and end (swap if needed)
|
|
103
|
+
let finalStart = state.startSlot;
|
|
104
|
+
let finalEnd = addMinutes(endTime, config.slotDuration);
|
|
105
|
+
if (finalStart > endTime) {
|
|
106
|
+
finalStart = endTime;
|
|
107
|
+
finalEnd = addMinutes(state.startSlot, config.slotDuration);
|
|
108
|
+
}
|
|
109
|
+
// Clear selection state
|
|
110
|
+
state.isSelecting = false;
|
|
111
|
+
state.startSlot = null;
|
|
112
|
+
state.currentSlot = null;
|
|
113
|
+
document.body.style.userSelect = '';
|
|
114
|
+
// Clear highlights
|
|
115
|
+
clearHighlights(slotsContainer);
|
|
116
|
+
// Determine if this was a click or drag
|
|
117
|
+
const wasClick = finalStart.getTime() === endTime.getTime();
|
|
118
|
+
if (wasClick && onSlotClick) {
|
|
119
|
+
onSlotClick(finalStart, finalEnd, resourceId);
|
|
120
|
+
}
|
|
121
|
+
else if (!wasClick && onSlotSelect) {
|
|
122
|
+
onSlotSelect(finalStart, finalEnd, resourceId);
|
|
123
|
+
}
|
|
124
|
+
else if (onSlotClick) {
|
|
125
|
+
// Fallback: treat drag as click for the range
|
|
126
|
+
onSlotClick(finalStart, finalEnd, resourceId);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const handlePointerCancel = () => {
|
|
130
|
+
state.isSelecting = false;
|
|
131
|
+
state.startSlot = null;
|
|
132
|
+
state.currentSlot = null;
|
|
133
|
+
document.body.style.userSelect = '';
|
|
134
|
+
clearHighlights(slotsContainer);
|
|
135
|
+
};
|
|
136
|
+
// Attach listeners
|
|
137
|
+
slotsContainer.addEventListener('pointerdown', handlePointerDown);
|
|
138
|
+
slotsContainer.addEventListener('pointermove', handlePointerMove);
|
|
139
|
+
slotsContainer.addEventListener('pointerup', handlePointerUp);
|
|
140
|
+
slotsContainer.addEventListener('pointercancel', handlePointerCancel);
|
|
141
|
+
// Store cleanup function
|
|
142
|
+
state.cleanup = () => {
|
|
143
|
+
slotsContainer.removeEventListener('pointerdown', handlePointerDown);
|
|
144
|
+
slotsContainer.removeEventListener('pointermove', handlePointerMove);
|
|
145
|
+
slotsContainer.removeEventListener('pointerup', handlePointerUp);
|
|
146
|
+
slotsContainer.removeEventListener('pointercancel', handlePointerCancel);
|
|
147
|
+
clearHighlights(slotsContainer);
|
|
148
|
+
};
|
|
149
|
+
return state;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=slot-selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slot-selection.js","sourceRoot":"","sources":["../../src/interactions/slot-selection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @liteforge/calendar - Recurring Event Expansion
|
|
3
|
+
*
|
|
4
|
+
* Expands recurring events into individual occurrences within a date range.
|
|
5
|
+
*/
|
|
6
|
+
import type { CalendarEvent } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Expand a recurring event into individual occurrences within a date range.
|
|
9
|
+
*/
|
|
10
|
+
export declare function expandRecurring<T extends CalendarEvent>(event: T, rangeStart: Date, rangeEnd: Date): T[];
|
|
11
|
+
/**
|
|
12
|
+
* Expand all recurring events in an array
|
|
13
|
+
*/
|
|
14
|
+
export declare function expandAllRecurring<T extends CalendarEvent>(events: T[], rangeStart: Date, rangeEnd: Date): T[];
|
|
15
|
+
//# sourceMappingURL=recurring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recurring.d.ts","sourceRoot":"","sources":["../src/recurring.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAA;AA+E9D;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,aAAa,EACrD,KAAK,EAAE,CAAC,EACR,UAAU,EAAE,IAAI,EAChB,QAAQ,EAAE,IAAI,GACb,CAAC,EAAE,CA4FL;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,aAAa,EACxD,MAAM,EAAE,CAAC,EAAE,EACX,UAAU,EAAE,IAAI,EAChB,QAAQ,EAAE,IAAI,GACb,CAAC,EAAE,CAeL"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @liteforge/calendar - Recurring Event Expansion
|
|
3
|
+
*
|
|
4
|
+
* Expands recurring events into individual occurrences within a date range.
|
|
5
|
+
*/
|
|
6
|
+
import { addDays, addWeeks, addMonths, isSameDay, isBefore, isAfter, startOfDay, } from './date-utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Check if a date is in the exceptions list
|
|
9
|
+
*/
|
|
10
|
+
function isException(date, exceptions) {
|
|
11
|
+
if (!exceptions || exceptions.length === 0)
|
|
12
|
+
return false;
|
|
13
|
+
return exceptions.some((ex) => isSameDay(date, ex));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get the next occurrence date based on frequency
|
|
17
|
+
*/
|
|
18
|
+
function getNextOccurrence(current, rule, eventStart) {
|
|
19
|
+
const interval = rule.interval ?? 1;
|
|
20
|
+
switch (rule.frequency) {
|
|
21
|
+
case 'daily':
|
|
22
|
+
return addDays(current, interval);
|
|
23
|
+
case 'weekly':
|
|
24
|
+
return addWeeks(current, interval);
|
|
25
|
+
case 'biweekly':
|
|
26
|
+
return addWeeks(current, 2 * interval);
|
|
27
|
+
case 'monthly': {
|
|
28
|
+
// Keep the same day of month
|
|
29
|
+
const next = addMonths(current, interval);
|
|
30
|
+
const targetDay = eventStart.getDate();
|
|
31
|
+
const maxDay = new Date(next.getFullYear(), next.getMonth() + 1, 0).getDate();
|
|
32
|
+
next.setDate(Math.min(targetDay, maxDay));
|
|
33
|
+
return next;
|
|
34
|
+
}
|
|
35
|
+
default:
|
|
36
|
+
return addDays(current, interval);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get all days of week for a weekly recurring event
|
|
41
|
+
*/
|
|
42
|
+
function getWeeklyOccurrences(weekStart, daysOfWeek, eventStart) {
|
|
43
|
+
const occurrences = [];
|
|
44
|
+
const startHour = eventStart.getHours();
|
|
45
|
+
const startMinute = eventStart.getMinutes();
|
|
46
|
+
for (let i = 0; i < 7; i++) {
|
|
47
|
+
const day = addDays(weekStart, i);
|
|
48
|
+
const dayOfWeek = day.getDay();
|
|
49
|
+
if (daysOfWeek.includes(dayOfWeek)) {
|
|
50
|
+
const occurrence = new Date(day);
|
|
51
|
+
occurrence.setHours(startHour, startMinute, 0, 0);
|
|
52
|
+
occurrences.push(occurrence);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return occurrences;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Expand a recurring event into individual occurrences within a date range.
|
|
59
|
+
*/
|
|
60
|
+
export function expandRecurring(event, rangeStart, rangeEnd) {
|
|
61
|
+
const rule = event.recurring;
|
|
62
|
+
if (!rule)
|
|
63
|
+
return [event];
|
|
64
|
+
const occurrences = [];
|
|
65
|
+
const eventDuration = event.end.getTime() - event.start.getTime();
|
|
66
|
+
let count = 0;
|
|
67
|
+
const maxCount = rule.count ?? 1000; // Safety limit
|
|
68
|
+
const endDate = rule.endDate ? startOfDay(rule.endDate) : null;
|
|
69
|
+
// For weekly events with specific days
|
|
70
|
+
if (rule.frequency === 'weekly' && rule.daysOfWeek && rule.daysOfWeek.length > 0) {
|
|
71
|
+
// Start from the week containing event.start
|
|
72
|
+
let currentWeekStart = startOfDay(event.start);
|
|
73
|
+
currentWeekStart.setDate(currentWeekStart.getDate() - currentWeekStart.getDay());
|
|
74
|
+
while (count < maxCount) {
|
|
75
|
+
// Check if we've passed the end date
|
|
76
|
+
if (endDate && isAfter(currentWeekStart, endDate))
|
|
77
|
+
break;
|
|
78
|
+
// Get all occurrences for this week
|
|
79
|
+
const weekOccurrences = getWeeklyOccurrences(currentWeekStart, rule.daysOfWeek, event.start);
|
|
80
|
+
for (const occurrenceStart of weekOccurrences) {
|
|
81
|
+
// Skip if before event start
|
|
82
|
+
if (isBefore(occurrenceStart, event.start))
|
|
83
|
+
continue;
|
|
84
|
+
// Check end conditions
|
|
85
|
+
if (endDate && isAfter(occurrenceStart, endDate))
|
|
86
|
+
break;
|
|
87
|
+
if (count >= maxCount)
|
|
88
|
+
break;
|
|
89
|
+
// Skip exceptions
|
|
90
|
+
if (isException(occurrenceStart, rule.exceptions))
|
|
91
|
+
continue;
|
|
92
|
+
// Check if within view range
|
|
93
|
+
const occurrenceEnd = new Date(occurrenceStart.getTime() + eventDuration);
|
|
94
|
+
if (occurrenceEnd > rangeStart && occurrenceStart < rangeEnd) {
|
|
95
|
+
occurrences.push({
|
|
96
|
+
...event,
|
|
97
|
+
id: `${event.id}_${occurrenceStart.toISOString().split('T')[0]}`,
|
|
98
|
+
start: occurrenceStart,
|
|
99
|
+
end: occurrenceEnd,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
count++;
|
|
103
|
+
}
|
|
104
|
+
// Move to next week (accounting for interval)
|
|
105
|
+
const interval = rule.interval ?? 1;
|
|
106
|
+
currentWeekStart = addWeeks(currentWeekStart, interval);
|
|
107
|
+
// Safety: stop if we're way past the range
|
|
108
|
+
if (isAfter(currentWeekStart, addWeeks(rangeEnd, 52)))
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
return occurrences;
|
|
112
|
+
}
|
|
113
|
+
// For other frequencies (daily, biweekly, monthly)
|
|
114
|
+
let currentStart = new Date(event.start);
|
|
115
|
+
while (count < maxCount) {
|
|
116
|
+
// Check end conditions
|
|
117
|
+
if (endDate && isAfter(currentStart, endDate))
|
|
118
|
+
break;
|
|
119
|
+
// Skip exceptions
|
|
120
|
+
if (!isException(currentStart, rule.exceptions)) {
|
|
121
|
+
// Check if within view range
|
|
122
|
+
const currentEnd = new Date(currentStart.getTime() + eventDuration);
|
|
123
|
+
if (currentEnd > rangeStart && currentStart < rangeEnd) {
|
|
124
|
+
occurrences.push({
|
|
125
|
+
...event,
|
|
126
|
+
id: `${event.id}_${currentStart.toISOString().split('T')[0]}`,
|
|
127
|
+
start: new Date(currentStart),
|
|
128
|
+
end: currentEnd,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
count++;
|
|
133
|
+
currentStart = getNextOccurrence(currentStart, rule, event.start);
|
|
134
|
+
// Safety: stop if we're way past the range
|
|
135
|
+
if (isAfter(currentStart, addMonths(rangeEnd, 12)))
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
return occurrences;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Expand all recurring events in an array
|
|
142
|
+
*/
|
|
143
|
+
export function expandAllRecurring(events, rangeStart, rangeEnd) {
|
|
144
|
+
const result = [];
|
|
145
|
+
for (const event of events) {
|
|
146
|
+
if (event.recurring) {
|
|
147
|
+
result.push(...expandRecurring(event, rangeStart, rangeEnd));
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// Non-recurring: just check if in range
|
|
151
|
+
if (event.end > rangeStart && event.start < rangeEnd) {
|
|
152
|
+
result.push(event);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=recurring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recurring.js","sourceRoot":"","sources":["../src/recurring.ts"],"names":[],"mappings":"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"}
|
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsmBH,wBAAgB,oBAAoB,IAAI,IAAI,CAU3C;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAMnD"}
|