@forcecalendar/core 2.1.12 → 2.1.14

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.
@@ -154,12 +154,20 @@ export class Event {
154
154
  // Validate timezone if provided
155
155
  if (data.timeZone) {
156
156
  try {
157
- // Test if timezone is valid by trying to use it
158
157
  new Intl.DateTimeFormat('en-US', { timeZone: data.timeZone });
159
158
  } catch (e) {
160
159
  throw new Error(`Invalid timezone: ${data.timeZone}`);
161
160
  }
162
161
  }
162
+
163
+ // Validate end timezone if provided
164
+ if (data.endTimeZone) {
165
+ try {
166
+ new Intl.DateTimeFormat('en-US', { timeZone: data.endTimeZone });
167
+ } catch (e) {
168
+ throw new Error(`Invalid end timezone: ${data.endTimeZone}`);
169
+ }
170
+ }
163
171
  }
164
172
 
165
173
  /**
@@ -415,33 +415,32 @@ export class EventStore {
415
415
  events = events.filter(e => !e.allDay);
416
416
  }
417
417
 
418
- const groups = [];
419
- const processed = new Set();
420
-
421
- events.forEach(event => {
422
- if (processed.has(event.id)) return;
423
-
424
- // Start a new group with this event
425
- const group = [event];
426
- processed.add(event.id);
418
+ if (events.length === 0) return [];
427
419
 
428
- // Find all events that overlap with any event in this group
429
- let i = 0;
430
- while (i < group.length) {
431
- const currentEvent = group[i];
432
-
433
- events.forEach(otherEvent => {
434
- if (!processed.has(otherEvent.id) && currentEvent.overlaps(otherEvent)) {
435
- group.push(otherEvent);
436
- processed.add(otherEvent.id);
437
- }
438
- });
420
+ // Sweep-line approach: sort by start, then merge overlapping intervals
421
+ // O(n log n) instead of O(n²)
422
+ events.sort((a, b) => a.start - b.start || b.end - a.end);
439
423
 
440
- i++;
424
+ const groups = [];
425
+ let currentGroup = [events[0]];
426
+ let groupEnd = events[0].end;
427
+
428
+ for (let i = 1; i < events.length; i++) {
429
+ const event = events[i];
430
+ if (event.start < groupEnd) {
431
+ // Overlaps with current group
432
+ currentGroup.push(event);
433
+ if (event.end > groupEnd) {
434
+ groupEnd = event.end;
435
+ }
436
+ } else {
437
+ // No overlap — start new group
438
+ groups.push(currentGroup);
439
+ currentGroup = [event];
440
+ groupEnd = event.end;
441
441
  }
442
-
443
- groups.push(group);
444
- });
442
+ }
443
+ groups.push(currentGroup);
445
444
 
446
445
  return groups;
447
446
  }
@@ -504,15 +503,24 @@ export class EventStore {
504
503
  * Get events for a date range
505
504
  * @param {Date} start - Start date
506
505
  * @param {Date} end - End date
507
- * @param {boolean|string} expandRecurring - Whether to expand recurring events, or timezone string
508
- * @param {string} [timezone] - Timezone for the query (if expandRecurring is boolean)
506
+ * @param {boolean|Object} [expandRecurringOrOptions=true] - Boolean to expand recurring events,
507
+ * or options object: { expandRecurring?: boolean, timezone?: string }
508
+ * @param {string} [timezone] - Timezone for the query
509
509
  * @returns {Event[]}
510
510
  */
511
- getEventsInRange(start, end, expandRecurring = true, timezone = null) {
512
- // Handle overloaded parameters
513
- if (typeof expandRecurring === 'string') {
514
- timezone = expandRecurring;
511
+ getEventsInRange(start, end, expandRecurringOrOptions = true, timezone = null) {
512
+ let expandRecurring = true;
513
+
514
+ if (typeof expandRecurringOrOptions === 'object' && expandRecurringOrOptions !== null) {
515
+ // Options object form: getEventsInRange(start, end, { expandRecurring, timezone })
516
+ expandRecurring = expandRecurringOrOptions.expandRecurring !== false;
517
+ timezone = expandRecurringOrOptions.timezone || timezone;
518
+ } else if (typeof expandRecurringOrOptions === 'string') {
519
+ // Legacy overloaded form: string was treated as timezone (deprecated)
520
+ timezone = expandRecurringOrOptions;
515
521
  expandRecurring = true;
522
+ } else {
523
+ expandRecurring = expandRecurringOrOptions;
516
524
  }
517
525
 
518
526
  timezone = timezone || this.defaultTimezone;
@@ -165,6 +165,18 @@ export class RecurrenceEngine {
165
165
  const next = new Date(currentDate);
166
166
 
167
167
  switch (rule.freq) {
168
+ case 'SECONDLY':
169
+ next.setSeconds(next.getSeconds() + rule.interval);
170
+ break;
171
+
172
+ case 'MINUTELY':
173
+ next.setMinutes(next.getMinutes() + rule.interval);
174
+ break;
175
+
176
+ case 'HOURLY':
177
+ next.setHours(next.getHours() + rule.interval);
178
+ break;
179
+
168
180
  case 'DAILY':
169
181
  next.setDate(next.getDate() + rule.interval);
170
182
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forcecalendar/core",
3
- "version": "2.1.12",
3
+ "version": "2.1.14",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "A modern, lightweight, framework-agnostic calendar engine optimized for Salesforce",