@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.
@@ -9,406 +9,412 @@ import { Calendar } from '@forcecalendar/core';
9
9
  import eventBus from './EventBus.js';
10
10
 
11
11
  class StateManager {
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 }
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
- // 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 (this.state.events.length !== coreEvents.length ||
55
- !this._eventsMatch(this.state.events, coreEvents)) {
56
- this.setState({ events: [...coreEvents] }, options);
57
- }
58
- return coreEvents;
59
- }
60
-
61
- /**
62
- * Check if two event arrays have the same events (by id)
63
- */
64
- _eventsMatch(arr1, arr2) {
65
- if (arr1.length !== arr2.length) return false;
66
- const ids1 = new Set(arr1.map(e => e.id));
67
- return arr2.every(e => ids1.has(e.id));
68
- }
69
-
70
- // State management
71
- getState() {
72
- return { ...this.state };
73
- }
74
-
75
- setState(updates, options = {}) {
76
- const { silent = false } = options;
77
- const oldState = { ...this.state };
78
- this.state = { ...this.state, ...updates };
79
-
80
- if (!silent) {
81
- this.notifySubscribers(oldState, this.state);
82
- this.emitStateChange(oldState, this.state);
83
- }
84
-
85
- return this.state;
86
- }
87
-
88
- subscribe(callback, subscriberId = null) {
89
- this.subscribers.add(callback);
90
-
91
- // Track subscriber ID for debugging/cleanup
92
- if (subscriberId) {
93
- if (!this._subscriberIds) {
94
- this._subscriberIds = new Map();
95
- }
96
- this._subscriberIds.set(subscriberId, callback);
97
- }
98
-
99
- return () => this.unsubscribe(callback, subscriberId);
100
- }
101
-
102
- unsubscribe(callback, subscriberId = null) {
103
- this.subscribers.delete(callback);
104
-
105
- // Clean up ID tracking
106
- if (subscriberId && this._subscriberIds) {
107
- this._subscriberIds.delete(subscriberId);
108
- }
109
- }
110
-
111
- /**
112
- * Unsubscribe by subscriber ID
113
- * @param {string} subscriberId - ID used when subscribing
114
- */
115
- unsubscribeById(subscriberId) {
116
- if (!this._subscriberIds) return false;
117
-
118
- const callback = this._subscriberIds.get(subscriberId);
119
- if (callback) {
120
- this.subscribers.delete(callback);
121
- this._subscriberIds.delete(subscriberId);
122
- return true;
123
- }
124
- return false;
125
- }
126
-
127
- /**
128
- * Get subscriber count (for debugging/monitoring)
129
- */
130
- getSubscriberCount() {
131
- return this.subscribers.size;
132
- }
133
-
134
- notifySubscribers(oldState, newState) {
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