@forcecalendar/interface 1.0.27 → 1.0.28
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 +9 -0
- package/dist/force-calendar-interface.esm.js +102 -55
- package/dist/force-calendar-interface.esm.js.map +1 -1
- package/dist/force-calendar-interface.umd.js.map +1 -1
- package/package.json +3 -1
- package/src/components/EventForm.js +180 -176
- package/src/components/ForceCalendar.js +414 -392
- package/src/core/BaseComponent.js +146 -144
- package/src/core/EventBus.js +197 -197
- package/src/core/StateManager.js +405 -399
- package/src/index.js +3 -3
- package/src/renderers/BaseViewRenderer.js +195 -192
- package/src/renderers/DayViewRenderer.js +133 -118
- package/src/renderers/MonthViewRenderer.js +74 -72
- package/src/renderers/WeekViewRenderer.js +118 -96
- package/src/utils/DOMUtils.js +277 -277
- package/src/utils/DateUtils.js +164 -164
- package/src/utils/StyleUtils.js +286 -249
package/src/core/StateManager.js
CHANGED
|
@@ -9,406 +9,412 @@ import { Calendar } from '@forcecalendar/core';
|
|
|
9
9
|
import eventBus from './EventBus.js';
|
|
10
10
|
|
|
11
11
|
class StateManager {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
12
|
+
constructor(config = {}) {
|
|
13
|
+
// Initialize Core Calendar instance
|
|
14
|
+
this.calendar = new Calendar({
|
|
15
|
+
view: config.view || 'month',
|
|
16
|
+
date: config.date || new Date(),
|
|
17
|
+
weekStartsOn: config.weekStartsOn ?? 0,
|
|
18
|
+
locale: config.locale || 'en-US',
|
|
19
|
+
timeZone: config.timeZone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
20
|
+
...config
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Internal state
|
|
24
|
+
this.state = {
|
|
25
|
+
view: this.calendar.getView(),
|
|
26
|
+
currentDate: this.calendar.getCurrentDate(),
|
|
27
|
+
events: [],
|
|
28
|
+
selectedEvent: null,
|
|
29
|
+
selectedDate: null,
|
|
30
|
+
loading: false,
|
|
31
|
+
error: null,
|
|
32
|
+
config: { ...config }
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// State change subscribers
|
|
36
|
+
this.subscribers = new Set();
|
|
37
|
+
|
|
38
|
+
// Bind methods
|
|
39
|
+
this.subscribe = this.subscribe.bind(this);
|
|
40
|
+
this.unsubscribe = this.unsubscribe.bind(this);
|
|
41
|
+
this.setState = this.setState.bind(this);
|
|
42
|
+
|
|
43
|
+
// Initial sync of events from Core (in case events were pre-loaded)
|
|
44
|
+
this._syncEventsFromCore({ silent: true });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Sync state.events from Core calendar (single source of truth)
|
|
49
|
+
* This ensures state.events always matches Core's event store
|
|
50
|
+
*/
|
|
51
|
+
_syncEventsFromCore(options = {}) {
|
|
52
|
+
const coreEvents = this.calendar.getEvents() || [];
|
|
53
|
+
// Only update if different to avoid unnecessary re-renders
|
|
54
|
+
if (
|
|
55
|
+
this.state.events.length !== coreEvents.length ||
|
|
56
|
+
!this._eventsMatch(this.state.events, coreEvents)
|
|
57
|
+
) {
|
|
58
|
+
this.setState({ events: [...coreEvents] }, options);
|
|
59
|
+
}
|
|
60
|
+
return coreEvents;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if two event arrays have the same events (by id)
|
|
65
|
+
*/
|
|
66
|
+
_eventsMatch(arr1, arr2) {
|
|
67
|
+
if (arr1.length !== arr2.length) return false;
|
|
68
|
+
const ids1 = new Set(arr1.map(e => e.id));
|
|
69
|
+
return arr2.every(e => ids1.has(e.id));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// State management
|
|
73
|
+
getState() {
|
|
74
|
+
return { ...this.state };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setState(updates, options = {}) {
|
|
78
|
+
const { silent = false } = options;
|
|
79
|
+
const oldState = { ...this.state };
|
|
80
|
+
this.state = { ...this.state, ...updates };
|
|
81
|
+
|
|
82
|
+
if (!silent) {
|
|
83
|
+
this.notifySubscribers(oldState, this.state);
|
|
84
|
+
this.emitStateChange(oldState, this.state);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return this.state;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
subscribe(callback, subscriberId = null) {
|
|
91
|
+
this.subscribers.add(callback);
|
|
92
|
+
|
|
93
|
+
// Track subscriber ID for debugging/cleanup
|
|
94
|
+
if (subscriberId) {
|
|
95
|
+
if (!this._subscriberIds) {
|
|
96
|
+
this._subscriberIds = new Map();
|
|
97
|
+
}
|
|
98
|
+
this._subscriberIds.set(subscriberId, callback);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return () => this.unsubscribe(callback, subscriberId);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
unsubscribe(callback, subscriberId = null) {
|
|
105
|
+
this.subscribers.delete(callback);
|
|
106
|
+
|
|
107
|
+
// Clean up ID tracking
|
|
108
|
+
if (subscriberId && this._subscriberIds) {
|
|
109
|
+
this._subscriberIds.delete(subscriberId);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Unsubscribe by subscriber ID
|
|
115
|
+
* @param {string} subscriberId - ID used when subscribing
|
|
116
|
+
*/
|
|
117
|
+
unsubscribeById(subscriberId) {
|
|
118
|
+
if (!this._subscriberIds) return false;
|
|
119
|
+
|
|
120
|
+
const callback = this._subscriberIds.get(subscriberId);
|
|
121
|
+
if (callback) {
|
|
122
|
+
this.subscribers.delete(callback);
|
|
123
|
+
this._subscriberIds.delete(subscriberId);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get subscriber count (for debugging/monitoring)
|
|
131
|
+
*/
|
|
132
|
+
getSubscriberCount() {
|
|
133
|
+
return this.subscribers.size;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
notifySubscribers(oldState, newState) {
|
|
137
|
+
this.subscribers.forEach(callback => {
|
|
138
|
+
try {
|
|
139
|
+
callback(newState, oldState);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('Error in state subscriber:', error);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
emitStateChange(oldState, newState) {
|
|
147
|
+
const changedKeys = Object.keys(newState).filter(key => oldState[key] !== newState[key]);
|
|
148
|
+
|
|
149
|
+
changedKeys.forEach(key => {
|
|
150
|
+
eventBus.emit(`state:${key}:changed`, {
|
|
151
|
+
oldValue: oldState[key],
|
|
152
|
+
newValue: newState[key],
|
|
153
|
+
state: newState
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (changedKeys.length > 0) {
|
|
158
|
+
eventBus.emit('state:changed', { oldState, newState, changedKeys });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Calendar operations
|
|
163
|
+
setView(view) {
|
|
164
|
+
this.calendar.setView(view);
|
|
165
|
+
this.setState({ view });
|
|
166
|
+
eventBus.emit('view:changed', { view });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getView() {
|
|
170
|
+
return this.state.view;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
setDate(date) {
|
|
174
|
+
this.calendar.goToDate(date);
|
|
175
|
+
this.setState({ currentDate: this.calendar.getCurrentDate() });
|
|
176
|
+
eventBus.emit('date:changed', { date: this.state.currentDate });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
getCurrentDate() {
|
|
180
|
+
return this.state.currentDate;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Navigation
|
|
184
|
+
next() {
|
|
185
|
+
this.calendar.next();
|
|
186
|
+
this.setState({ currentDate: this.calendar.getCurrentDate() });
|
|
187
|
+
eventBus.emit('navigation:next', { date: this.state.currentDate });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
previous() {
|
|
191
|
+
this.calendar.previous();
|
|
192
|
+
this.setState({ currentDate: this.calendar.getCurrentDate() });
|
|
193
|
+
eventBus.emit('navigation:previous', { date: this.state.currentDate });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
today() {
|
|
197
|
+
this.calendar.today();
|
|
198
|
+
this.setState({ currentDate: this.calendar.getCurrentDate() });
|
|
199
|
+
eventBus.emit('navigation:today', { date: this.state.currentDate });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
goToDate(date) {
|
|
203
|
+
this.calendar.goToDate(date);
|
|
204
|
+
this.setState({ currentDate: this.calendar.getCurrentDate() });
|
|
205
|
+
eventBus.emit('navigation:goto', { date: this.state.currentDate });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Event management
|
|
209
|
+
addEvent(event) {
|
|
210
|
+
const addedEvent = this.calendar.addEvent(event);
|
|
211
|
+
if (!addedEvent) {
|
|
212
|
+
console.error('Failed to add event to calendar');
|
|
213
|
+
eventBus.emit('event:error', { action: 'add', event, error: 'Failed to add event' });
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
// Sync from Core to ensure consistency (single source of truth)
|
|
217
|
+
this._syncEventsFromCore();
|
|
218
|
+
eventBus.emit('event:add', { event: addedEvent });
|
|
219
|
+
eventBus.emit('event:added', { event: addedEvent });
|
|
220
|
+
return addedEvent;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
updateEvent(eventId, updates) {
|
|
224
|
+
// First, ensure state is in sync with Core (recover from any prior desync)
|
|
225
|
+
this._syncEventsFromCore({ silent: true });
|
|
226
|
+
|
|
227
|
+
const event = this.calendar.updateEvent(eventId, updates);
|
|
228
|
+
if (!event) {
|
|
229
|
+
console.error(`Failed to update event: ${eventId}`);
|
|
230
|
+
eventBus.emit('event:error', {
|
|
231
|
+
action: 'update',
|
|
232
|
+
eventId,
|
|
233
|
+
updates,
|
|
234
|
+
error: 'Event not found in calendar'
|
|
235
|
+
});
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Sync from Core to ensure consistency (single source of truth)
|
|
240
|
+
this._syncEventsFromCore();
|
|
241
|
+
eventBus.emit('event:update', { event });
|
|
242
|
+
eventBus.emit('event:updated', { event });
|
|
243
|
+
return event;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
deleteEvent(eventId) {
|
|
247
|
+
// First, ensure state is in sync with Core (recover from any prior desync)
|
|
248
|
+
this._syncEventsFromCore({ silent: true });
|
|
249
|
+
|
|
250
|
+
const deleted = this.calendar.removeEvent(eventId);
|
|
251
|
+
if (!deleted) {
|
|
252
|
+
console.error(`Failed to delete event: ${eventId}`);
|
|
253
|
+
eventBus.emit('event:error', { action: 'delete', eventId, error: 'Event not found' });
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
// Sync from Core to ensure consistency (single source of truth)
|
|
257
|
+
this._syncEventsFromCore();
|
|
258
|
+
eventBus.emit('event:remove', { eventId });
|
|
259
|
+
eventBus.emit('event:deleted', { eventId });
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
getEvents() {
|
|
264
|
+
// Return from Core (source of truth)
|
|
265
|
+
return this.calendar.getEvents() || [];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Force sync state.events from Core calendar
|
|
270
|
+
* Use this if you've modified events directly on the Core calendar
|
|
271
|
+
*/
|
|
272
|
+
syncEvents() {
|
|
273
|
+
return this._syncEventsFromCore();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
getEventsForDate(date) {
|
|
277
|
+
return this.calendar.getEventsForDate(date);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
getEventsInRange(start, end) {
|
|
281
|
+
return this.calendar.getEventsInRange(start, end);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// View data
|
|
285
|
+
getViewData() {
|
|
286
|
+
const viewData = this.calendar.getViewData();
|
|
287
|
+
return this.enrichViewData(viewData);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
enrichViewData(viewData) {
|
|
291
|
+
const selectedDateString = this.state.selectedDate?.toDateString();
|
|
292
|
+
|
|
293
|
+
// Strategy 1: Multi-week structure (Month view)
|
|
294
|
+
if (viewData.weeks) {
|
|
295
|
+
viewData.weeks = viewData.weeks.map(week => ({
|
|
296
|
+
...week,
|
|
297
|
+
days: week.days.map(day => {
|
|
298
|
+
const dayDate = new Date(day.date);
|
|
299
|
+
return {
|
|
300
|
+
...day,
|
|
301
|
+
isSelected: dayDate.toDateString() === selectedDateString,
|
|
302
|
+
events: day.events || this.getEventsForDate(dayDate)
|
|
303
|
+
};
|
|
304
|
+
})
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Strategy 2: Flat days structure (Week view or list view)
|
|
309
|
+
if (viewData.days) {
|
|
310
|
+
viewData.days = viewData.days.map(day => {
|
|
311
|
+
const dayDate = new Date(day.date);
|
|
312
|
+
return {
|
|
313
|
+
...day,
|
|
314
|
+
isSelected: dayDate.toDateString() === selectedDateString,
|
|
315
|
+
events: day.events || this.getEventsForDate(dayDate)
|
|
33
316
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
this.subscribers.forEach(callback => {
|
|
136
|
-
try {
|
|
137
|
-
callback(newState, oldState);
|
|
138
|
-
} catch (error) {
|
|
139
|
-
console.error('Error in state subscriber:', error);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
emitStateChange(oldState, newState) {
|
|
145
|
-
const changedKeys = Object.keys(newState).filter(
|
|
146
|
-
key => oldState[key] !== newState[key]
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
changedKeys.forEach(key => {
|
|
150
|
-
eventBus.emit(`state:${key}:changed`, {
|
|
151
|
-
oldValue: oldState[key],
|
|
152
|
-
newValue: newState[key],
|
|
153
|
-
state: newState
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
if (changedKeys.length > 0) {
|
|
158
|
-
eventBus.emit('state:changed', { oldState, newState, changedKeys });
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Calendar operations
|
|
163
|
-
setView(view) {
|
|
164
|
-
this.calendar.setView(view);
|
|
165
|
-
this.setState({ view });
|
|
166
|
-
eventBus.emit('view:changed', { view });
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
getView() {
|
|
170
|
-
return this.state.view;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
setDate(date) {
|
|
174
|
-
this.calendar.goToDate(date);
|
|
175
|
-
this.setState({ currentDate: this.calendar.getCurrentDate() });
|
|
176
|
-
eventBus.emit('date:changed', { date: this.state.currentDate });
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
getCurrentDate() {
|
|
180
|
-
return this.state.currentDate;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Navigation
|
|
184
|
-
next() {
|
|
185
|
-
this.calendar.next();
|
|
186
|
-
this.setState({ currentDate: this.calendar.getCurrentDate() });
|
|
187
|
-
eventBus.emit('navigation:next', { date: this.state.currentDate });
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
previous() {
|
|
191
|
-
this.calendar.previous();
|
|
192
|
-
this.setState({ currentDate: this.calendar.getCurrentDate() });
|
|
193
|
-
eventBus.emit('navigation:previous', { date: this.state.currentDate });
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
today() {
|
|
197
|
-
this.calendar.today();
|
|
198
|
-
this.setState({ currentDate: this.calendar.getCurrentDate() });
|
|
199
|
-
eventBus.emit('navigation:today', { date: this.state.currentDate });
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
goToDate(date) {
|
|
203
|
-
this.calendar.goToDate(date);
|
|
204
|
-
this.setState({ currentDate: this.calendar.getCurrentDate() });
|
|
205
|
-
eventBus.emit('navigation:goto', { date: this.state.currentDate });
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Event management
|
|
209
|
-
addEvent(event) {
|
|
210
|
-
const addedEvent = this.calendar.addEvent(event);
|
|
211
|
-
if (!addedEvent) {
|
|
212
|
-
console.error('Failed to add event to calendar');
|
|
213
|
-
eventBus.emit('event:error', { action: 'add', event, error: 'Failed to add event' });
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
// Sync from Core to ensure consistency (single source of truth)
|
|
217
|
-
this._syncEventsFromCore();
|
|
218
|
-
eventBus.emit('event:add', { event: addedEvent });
|
|
219
|
-
eventBus.emit('event:added', { event: addedEvent });
|
|
220
|
-
return addedEvent;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
updateEvent(eventId, updates) {
|
|
224
|
-
// First, ensure state is in sync with Core (recover from any prior desync)
|
|
225
|
-
this._syncEventsFromCore({ silent: true });
|
|
226
|
-
|
|
227
|
-
const event = this.calendar.updateEvent(eventId, updates);
|
|
228
|
-
if (!event) {
|
|
229
|
-
console.error(`Failed to update event: ${eventId}`);
|
|
230
|
-
eventBus.emit('event:error', { action: 'update', eventId, updates, error: 'Event not found in calendar' });
|
|
231
|
-
return null;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Sync from Core to ensure consistency (single source of truth)
|
|
235
|
-
this._syncEventsFromCore();
|
|
236
|
-
eventBus.emit('event:update', { event });
|
|
237
|
-
eventBus.emit('event:updated', { event });
|
|
238
|
-
return event;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
deleteEvent(eventId) {
|
|
242
|
-
// First, ensure state is in sync with Core (recover from any prior desync)
|
|
243
|
-
this._syncEventsFromCore({ silent: true });
|
|
244
|
-
|
|
245
|
-
const deleted = this.calendar.removeEvent(eventId);
|
|
246
|
-
if (!deleted) {
|
|
247
|
-
console.error(`Failed to delete event: ${eventId}`);
|
|
248
|
-
eventBus.emit('event:error', { action: 'delete', eventId, error: 'Event not found' });
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
251
|
-
// Sync from Core to ensure consistency (single source of truth)
|
|
252
|
-
this._syncEventsFromCore();
|
|
253
|
-
eventBus.emit('event:remove', { eventId });
|
|
254
|
-
eventBus.emit('event:deleted', { eventId });
|
|
255
|
-
return true;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
getEvents() {
|
|
259
|
-
// Return from Core (source of truth)
|
|
260
|
-
return this.calendar.getEvents() || [];
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Force sync state.events from Core calendar
|
|
265
|
-
* Use this if you've modified events directly on the Core calendar
|
|
266
|
-
*/
|
|
267
|
-
syncEvents() {
|
|
268
|
-
return this._syncEventsFromCore();
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
getEventsForDate(date) {
|
|
272
|
-
return this.calendar.getEventsForDate(date);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
getEventsInRange(start, end) {
|
|
276
|
-
return this.calendar.getEventsInRange(start, end);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// View data
|
|
280
|
-
getViewData() {
|
|
281
|
-
const viewData = this.calendar.getViewData();
|
|
282
|
-
return this.enrichViewData(viewData);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
enrichViewData(viewData) {
|
|
286
|
-
const selectedDateString = this.state.selectedDate?.toDateString();
|
|
287
|
-
|
|
288
|
-
// Strategy 1: Multi-week structure (Month view)
|
|
289
|
-
if (viewData.weeks) {
|
|
290
|
-
viewData.weeks = viewData.weeks.map(week => ({
|
|
291
|
-
...week,
|
|
292
|
-
days: week.days.map(day => {
|
|
293
|
-
const dayDate = new Date(day.date);
|
|
294
|
-
return {
|
|
295
|
-
...day,
|
|
296
|
-
isSelected: dayDate.toDateString() === selectedDateString,
|
|
297
|
-
events: day.events || this.getEventsForDate(dayDate)
|
|
298
|
-
};
|
|
299
|
-
})
|
|
300
|
-
}));
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Strategy 2: Flat days structure (Week view or list view)
|
|
304
|
-
if (viewData.days) {
|
|
305
|
-
viewData.days = viewData.days.map(day => {
|
|
306
|
-
const dayDate = new Date(day.date);
|
|
307
|
-
return {
|
|
308
|
-
...day,
|
|
309
|
-
isSelected: dayDate.toDateString() === selectedDateString,
|
|
310
|
-
events: day.events || this.getEventsForDate(dayDate)
|
|
311
|
-
};
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Strategy 3: Single day structure (Day view)
|
|
316
|
-
if (viewData.date && !viewData.days && !viewData.weeks) {
|
|
317
|
-
const dayDate = new Date(viewData.date);
|
|
318
|
-
viewData.isSelected = dayDate.toDateString() === selectedDateString;
|
|
319
|
-
viewData.events = viewData.events || this.getEventsForDate(dayDate);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return viewData;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Selection management
|
|
326
|
-
selectEvent(event) {
|
|
327
|
-
this.setState({ selectedEvent: event });
|
|
328
|
-
eventBus.emit('event:selected', { event });
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
selectEventById(eventId) {
|
|
332
|
-
const event = this.state.events.find(e => e.id === eventId);
|
|
333
|
-
if (event) {
|
|
334
|
-
this.selectEvent(event);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
deselectEvent() {
|
|
339
|
-
this.setState({ selectedEvent: null });
|
|
340
|
-
eventBus.emit('event:deselected', {});
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
selectDate(date) {
|
|
344
|
-
this.setState({ selectedDate: date });
|
|
345
|
-
eventBus.emit('date:selected', { date });
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
deselectDate() {
|
|
349
|
-
this.setState({ selectedDate: null });
|
|
350
|
-
eventBus.emit('date:deselected', {});
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Utility methods
|
|
354
|
-
isToday(date) {
|
|
355
|
-
const today = new Date();
|
|
356
|
-
return date.toDateString() === today.toDateString();
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
isSelectedDate(date) {
|
|
360
|
-
return this.state.selectedDate &&
|
|
361
|
-
date.toDateString() === this.state.selectedDate.toDateString();
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
isWeekend(date) {
|
|
365
|
-
const day = date.getDay();
|
|
366
|
-
return day === 0 || day === 6;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Loading state
|
|
370
|
-
setLoading(loading) {
|
|
371
|
-
this.setState({ loading });
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Error handling
|
|
375
|
-
setError(error) {
|
|
376
|
-
this.setState({ error });
|
|
377
|
-
if (error) {
|
|
378
|
-
eventBus.emit('error', { error });
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
clearError() {
|
|
383
|
-
this.setState({ error: null });
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Configuration
|
|
387
|
-
updateConfig(config) {
|
|
388
|
-
this.setState({ config: { ...this.state.config, ...config } });
|
|
389
|
-
|
|
390
|
-
// Update calendar configuration if needed
|
|
391
|
-
if (config.weekStartsOn !== undefined) {
|
|
392
|
-
this.calendar.setWeekStartsOn(config.weekStartsOn);
|
|
393
|
-
}
|
|
394
|
-
if (config.locale !== undefined) {
|
|
395
|
-
this.calendar.setLocale(config.locale);
|
|
396
|
-
}
|
|
397
|
-
if (config.timeZone !== undefined) {
|
|
398
|
-
this.calendar.setTimezone(config.timeZone);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Destroy
|
|
403
|
-
destroy() {
|
|
404
|
-
this.subscribers.clear();
|
|
405
|
-
if (this._subscriberIds) {
|
|
406
|
-
this._subscriberIds.clear();
|
|
407
|
-
this._subscriberIds = null;
|
|
408
|
-
}
|
|
409
|
-
this.state = null;
|
|
410
|
-
this.calendar = null;
|
|
411
|
-
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Strategy 3: Single day structure (Day view)
|
|
321
|
+
if (viewData.date && !viewData.days && !viewData.weeks) {
|
|
322
|
+
const dayDate = new Date(viewData.date);
|
|
323
|
+
viewData.isSelected = dayDate.toDateString() === selectedDateString;
|
|
324
|
+
viewData.events = viewData.events || this.getEventsForDate(dayDate);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return viewData;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Selection management
|
|
331
|
+
selectEvent(event) {
|
|
332
|
+
this.setState({ selectedEvent: event });
|
|
333
|
+
eventBus.emit('event:selected', { event });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
selectEventById(eventId) {
|
|
337
|
+
const event = this.state.events.find(e => e.id === eventId);
|
|
338
|
+
if (event) {
|
|
339
|
+
this.selectEvent(event);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
deselectEvent() {
|
|
344
|
+
this.setState({ selectedEvent: null });
|
|
345
|
+
eventBus.emit('event:deselected', {});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
selectDate(date) {
|
|
349
|
+
this.setState({ selectedDate: date });
|
|
350
|
+
eventBus.emit('date:selected', { date });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
deselectDate() {
|
|
354
|
+
this.setState({ selectedDate: null });
|
|
355
|
+
eventBus.emit('date:deselected', {});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Utility methods
|
|
359
|
+
isToday(date) {
|
|
360
|
+
const today = new Date();
|
|
361
|
+
return date.toDateString() === today.toDateString();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
isSelectedDate(date) {
|
|
365
|
+
return (
|
|
366
|
+
this.state.selectedDate && date.toDateString() === this.state.selectedDate.toDateString()
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
isWeekend(date) {
|
|
371
|
+
const day = date.getDay();
|
|
372
|
+
return day === 0 || day === 6;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Loading state
|
|
376
|
+
setLoading(loading) {
|
|
377
|
+
this.setState({ loading });
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Error handling
|
|
381
|
+
setError(error) {
|
|
382
|
+
this.setState({ error });
|
|
383
|
+
if (error) {
|
|
384
|
+
eventBus.emit('error', { error });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
clearError() {
|
|
389
|
+
this.setState({ error: null });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Configuration
|
|
393
|
+
updateConfig(config) {
|
|
394
|
+
this.setState({ config: { ...this.state.config, ...config } });
|
|
395
|
+
|
|
396
|
+
// Update calendar configuration if needed
|
|
397
|
+
if (config.weekStartsOn !== undefined) {
|
|
398
|
+
this.calendar.setWeekStartsOn(config.weekStartsOn);
|
|
399
|
+
}
|
|
400
|
+
if (config.locale !== undefined) {
|
|
401
|
+
this.calendar.setLocale(config.locale);
|
|
402
|
+
}
|
|
403
|
+
if (config.timeZone !== undefined) {
|
|
404
|
+
this.calendar.setTimezone(config.timeZone);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Destroy
|
|
409
|
+
destroy() {
|
|
410
|
+
this.subscribers.clear();
|
|
411
|
+
if (this._subscriberIds) {
|
|
412
|
+
this._subscriberIds.clear();
|
|
413
|
+
this._subscriberIds = null;
|
|
414
|
+
}
|
|
415
|
+
this.state = null;
|
|
416
|
+
this.calendar = null;
|
|
417
|
+
}
|
|
412
418
|
}
|
|
413
419
|
|
|
414
420
|
// Export StateManager
|