@forcecalendar/core 2.1.5 → 2.1.7

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.
@@ -59,6 +59,15 @@ export class Event {
59
59
 
60
60
  normalized.attachments = Array.isArray(normalized.attachments) ? normalized.attachments : [];
61
61
 
62
+ // Backward compatibility: support legacy "recurrence" alias.
63
+ // Canonical fields are "recurring" + "recurrenceRule".
64
+ if (normalized.recurrence && !normalized.recurrenceRule) {
65
+ normalized.recurrenceRule = normalized.recurrence;
66
+ }
67
+ if (normalized.recurrenceRule) {
68
+ normalized.recurring = true;
69
+ }
70
+
62
71
  // Normalize status and visibility
63
72
  const validStatuses = ['confirmed', 'tentative', 'cancelled'];
64
73
  if (!validStatuses.includes(normalized.status)) {
@@ -172,6 +181,7 @@ export class Event {
172
181
  textColor = null,
173
182
  recurring = false,
174
183
  recurrenceRule = null,
184
+ recurrence = null, // Backward-compatible alias for recurrenceRule
175
185
  timeZone = null,
176
186
  endTimeZone = null,
177
187
  status = 'confirmed',
@@ -201,6 +211,7 @@ export class Event {
201
211
  textColor,
202
212
  recurring,
203
213
  recurrenceRule,
214
+ recurrence,
204
215
  timeZone,
205
216
  endTimeZone,
206
217
  status,
@@ -390,6 +401,14 @@ export class Event {
390
401
  return this.recurring && this.recurrenceRule !== null;
391
402
  }
392
403
 
404
+ /**
405
+ * Backward-compatible alias for recurrenceRule
406
+ * @returns {import('../../types.js').RecurrenceRule|string|null}
407
+ */
408
+ get recurrence() {
409
+ return this.recurrenceRule;
410
+ }
411
+
393
412
  /**
394
413
  * Check if event occurs on a specific date
395
414
  * @param {Date|string} date - The date to check
@@ -471,6 +490,7 @@ export class Event {
471
490
  textColor: this.textColor,
472
491
  recurring: this.recurring,
473
492
  recurrenceRule: this.recurrenceRule,
493
+ recurrence: this.recurrenceRule,
474
494
  timeZone: this.timeZone,
475
495
  status: this.status,
476
496
  visibility: this.visibility,
@@ -133,7 +133,7 @@ export class ICSHandler {
133
133
  if (expandRecurring) {
134
134
  events = this.expandRecurringEvents(events, dateRange);
135
135
  } else if (!includeRecurring) {
136
- events = events.filter(event => !event.recurrence);
136
+ events = events.filter(event => !(event.recurring || event.recurrenceRule || event.recurrence));
137
137
  }
138
138
 
139
139
  // Generate ICS
@@ -300,7 +300,7 @@ export class ICSHandler {
300
300
  const rangeEnd = dateRange?.end || new Date(Date.now() + 365 * 24 * 60 * 60 * 1000);
301
301
 
302
302
  for (const event of events) {
303
- if (!event.recurrence) {
303
+ if (!(event.recurring || event.recurrenceRule || event.recurrence)) {
304
304
  expanded.push(event);
305
305
  continue;
306
306
  }
@@ -321,6 +321,8 @@ export class ICSHandler {
321
321
  id: `${event.id}-${instance.start.getTime()}`,
322
322
  start: instance.start,
323
323
  end: instance.end,
324
+ recurring: false,
325
+ recurrenceRule: null,
324
326
  recurrence: null, // Remove recurrence from instances
325
327
  parentId: event.id // Reference to original
326
328
  });
@@ -22,7 +22,7 @@ export class ICSParser {
22
22
  TRANSP: 'showAs',
23
23
  ORGANIZER: 'organizer',
24
24
  ATTENDEE: 'attendees',
25
- RRULE: 'recurrence',
25
+ RRULE: 'recurrenceRule',
26
26
  EXDATE: 'excludeDates'
27
27
  };
28
28
  }
@@ -184,17 +184,18 @@ export class ICSParser {
184
184
  }
185
185
 
186
186
  // Recurrence
187
- if (event.recurrence) {
188
- if (typeof event.recurrence === 'string') {
187
+ const recurrenceRule = event.recurrenceRule || event.recurrence;
188
+ if (recurrenceRule) {
189
+ if (typeof recurrenceRule === 'string') {
189
190
  // Already in RRULE format
190
- if (event.recurrence.startsWith('RRULE:')) {
191
- lines.push(event.recurrence);
191
+ if (recurrenceRule.startsWith('RRULE:')) {
192
+ lines.push(recurrenceRule);
192
193
  } else {
193
- lines.push(`RRULE:${event.recurrence}`);
194
+ lines.push(`RRULE:${recurrenceRule}`);
194
195
  }
195
- } else if (typeof event.recurrence === 'object') {
196
+ } else if (typeof recurrenceRule === 'object') {
196
197
  // Convert object to RRULE
197
- lines.push(`RRULE:${this.objectToRRule(event.recurrence)}`);
198
+ lines.push(`RRULE:${this.objectToRRule(recurrenceRule)}`);
198
199
  }
199
200
  }
200
201
 
@@ -277,7 +278,8 @@ export class ICSParser {
277
278
  }
278
279
 
279
280
  case 'RRULE':
280
- event.recurrence = value;
281
+ event.recurrenceRule = value;
282
+ event.recurring = true;
281
283
  break;
282
284
  }
283
285
  }
@@ -423,6 +425,8 @@ export class ICSParser {
423
425
  allDay: false,
424
426
  location: '',
425
427
  category: '',
428
+ recurring: false,
429
+ recurrenceRule: null,
426
430
  status: 'confirmed',
427
431
  showAs: 'busy',
428
432
  attendees: [],
@@ -64,7 +64,7 @@ export class EnhancedCalendar extends Calendar {
64
64
  const recurringEvents = [];
65
65
 
66
66
  // Separate regular and recurring events
67
- const allEvents = this.eventStore.getEventsInDateRange(startDate, endDate);
67
+ const allEvents = this.eventStore.getEventsInRange(startDate, endDate, false);
68
68
 
69
69
  for (const event of allEvents) {
70
70
  if (event.recurring) {
@@ -107,14 +107,11 @@ export class EnhancedCalendar extends Calendar {
107
107
  this.recurrenceEngine.addModifiedInstance(eventId, occurrenceDate, modifications);
108
108
 
109
109
  // Emit change event
110
- this.emit('occurrence:modified', {
110
+ this._emit('occurrenceModified', {
111
111
  eventId,
112
112
  occurrenceDate,
113
113
  modifications
114
114
  });
115
-
116
- // Trigger re-render if in view
117
- this.refreshView();
118
115
  }
119
116
 
120
117
  /**
@@ -125,14 +122,11 @@ export class EnhancedCalendar extends Calendar {
125
122
  this.recurrenceEngine.addException(eventId, occurrenceDate, reason);
126
123
 
127
124
  // Emit change event
128
- this.emit('occurrence:cancelled', {
125
+ this._emit('occurrenceCancelled', {
129
126
  eventId,
130
127
  occurrenceDate,
131
128
  reason
132
129
  });
133
-
134
- // Trigger re-render
135
- this.refreshView();
136
130
  }
137
131
 
138
132
  /**
@@ -153,13 +147,11 @@ export class EnhancedCalendar extends Calendar {
153
147
  }
154
148
 
155
149
  // Emit bulk change event
156
- this.emit('occurrences:bulk-modified', {
150
+ this._emit('occurrencesBulkModified', {
157
151
  eventId,
158
152
  count: occurrences.length,
159
153
  modifications
160
154
  });
161
-
162
- this.refreshView();
163
155
  }
164
156
 
165
157
  /**
@@ -211,28 +203,33 @@ export class EnhancedCalendar extends Calendar {
211
203
  * Setup real-time indexing for search
212
204
  */
213
205
  setupRealtimeIndexing() {
214
- // Re-index when events are added
215
- this.on('event:added', event => {
216
- this.searchManager.indexEvents();
217
- });
218
-
219
- // Re-index when events are modified
220
- this.on('event:updated', event => {
221
- this.searchManager.indexEvents();
222
- });
223
-
224
- // Re-index when events are removed
225
- this.on('event:removed', eventId => {
226
- this.searchManager.indexEvents();
227
- });
228
-
229
- // Batch re-indexing for bulk operations
230
- let reindexTimeout;
231
- this.on('events:bulk-operation', () => {
232
- clearTimeout(reindexTimeout);
206
+ // Batch re-indexing to avoid rebuilding the index repeatedly for rapid event changes.
207
+ let reindexTimeout = null;
208
+ const scheduleReindex = () => {
209
+ if (reindexTimeout) {
210
+ clearTimeout(reindexTimeout);
211
+ }
233
212
  reindexTimeout = setTimeout(() => {
234
213
  this.searchManager.indexEvents();
235
214
  }, 100);
215
+ };
216
+
217
+ // Store cleanup handle so timers are cleared on destroy.
218
+ this._clearReindexTimeout = () => {
219
+ if (reindexTimeout) {
220
+ clearTimeout(reindexTimeout);
221
+ reindexTimeout = null;
222
+ }
223
+ };
224
+
225
+ this.on('eventAdd', scheduleReindex);
226
+ this.on('eventUpdate', scheduleReindex);
227
+ this.on('eventRemove', scheduleReindex);
228
+ this.on('eventsSet', scheduleReindex);
229
+ this.on('eventStoreChange', change => {
230
+ if (change?.type === 'batch') {
231
+ scheduleReindex();
232
+ }
236
233
  });
237
234
  }
238
235
 
@@ -365,6 +362,11 @@ export class EnhancedCalendar extends Calendar {
365
362
  * Clean up resources
366
363
  */
367
364
  destroy() {
365
+ if (typeof this._clearReindexTimeout === 'function') {
366
+ this._clearReindexTimeout();
367
+ this._clearReindexTimeout = null;
368
+ }
369
+
368
370
  // Clean up worker
369
371
  if (this.searchManager) {
370
372
  this.searchManager.destroy();
@@ -152,7 +152,7 @@ export class EventSearch {
152
152
  // Recurring filter
153
153
  if (recurring !== null) {
154
154
  events = events.filter(event => {
155
- const hasRecurrence = !!event.recurrence;
155
+ const hasRecurrence = !!(event.recurring || event.recurrenceRule || event.recurrence);
156
156
  return recurring ? hasRecurrence : !hasRecurrence;
157
157
  });
158
158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forcecalendar/core",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "A modern, lightweight, framework-agnostic calendar engine optimized for Salesforce",