@forcecalendar/interface 1.0.16 → 1.0.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forcecalendar/interface",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "type": "module",
5
5
  "description": "Official interface layer for forceCalendar Core - Enterprise calendar components",
6
6
  "main": "dist/force-calendar-interface.umd.js",
@@ -300,16 +300,16 @@ export class EventForm extends BaseComponent {
300
300
  if (e.target === this) this.close();
301
301
  });
302
302
 
303
- // Close on Escape key - remove old listener before adding new one
304
- if (this._handleKeyDown) {
305
- window.removeEventListener('keydown', this._handleKeyDown);
303
+ // Close on Escape key - only add once to prevent memory leaks
304
+ if (!this._keydownListenerAdded) {
305
+ this._handleKeyDown = (e) => {
306
+ if (e.key === 'Escape' && this.hasAttribute('open')) {
307
+ this.close();
308
+ }
309
+ };
310
+ window.addEventListener('keydown', this._handleKeyDown);
311
+ this._keydownListenerAdded = true;
306
312
  }
307
- this._handleKeyDown = (e) => {
308
- if (e.key === 'Escape' && this.hasAttribute('open')) {
309
- this.close();
310
- }
311
- };
312
- window.addEventListener('keydown', this._handleKeyDown);
313
313
  }
314
314
 
315
315
  updateColorSelection() {
@@ -410,7 +410,12 @@ export class EventForm extends BaseComponent {
410
410
  if (this._cleanupFocusTrap) {
411
411
  this._cleanupFocusTrap();
412
412
  }
413
- window.removeEventListener('keydown', this._handleKeyDown);
413
+ // Clean up window listener
414
+ if (this._handleKeyDown) {
415
+ window.removeEventListener('keydown', this._handleKeyDown);
416
+ this._handleKeyDown = null;
417
+ this._keydownListenerAdded = false;
418
+ }
414
419
  }
415
420
  }
416
421
 
@@ -58,6 +58,17 @@ class EventBus {
58
58
  * Unsubscribe from an event
59
59
  */
60
60
  off(eventName, handler) {
61
+ // Handle wildcard pattern removal
62
+ if (eventName.includes('*')) {
63
+ for (const sub of this.wildcardHandlers) {
64
+ if (sub.pattern === eventName && sub.handler === handler) {
65
+ this.wildcardHandlers.delete(sub);
66
+ return;
67
+ }
68
+ }
69
+ return;
70
+ }
71
+
61
72
  if (!this.events.has(eventName)) return;
62
73
 
63
74
  const handlers = this.events.get(eventName);
@@ -71,6 +82,43 @@ class EventBus {
71
82
  }
72
83
  }
73
84
 
85
+ /**
86
+ * Remove all wildcard handlers matching a pattern
87
+ * @param {string} pattern - Pattern to match (e.g., 'event:*')
88
+ */
89
+ offWildcard(pattern) {
90
+ for (const sub of [...this.wildcardHandlers]) {
91
+ if (sub.pattern === pattern) {
92
+ this.wildcardHandlers.delete(sub);
93
+ }
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Remove all handlers (regular and wildcard) for a specific handler function
99
+ * Useful for cleanup when a component is destroyed
100
+ * @param {Function} handler - Handler function to remove
101
+ */
102
+ offAll(handler) {
103
+ // Remove from regular events
104
+ for (const [eventName, handlers] of this.events) {
105
+ const index = handlers.findIndex(sub => sub.handler === handler);
106
+ if (index > -1) {
107
+ handlers.splice(index, 1);
108
+ }
109
+ if (handlers.length === 0) {
110
+ this.events.delete(eventName);
111
+ }
112
+ }
113
+
114
+ // Remove from wildcard handlers
115
+ for (const sub of [...this.wildcardHandlers]) {
116
+ if (sub.handler === handler) {
117
+ this.wildcardHandlers.delete(sub);
118
+ }
119
+ }
120
+ }
121
+
74
122
  /**
75
123
  * Emit an event
76
124
  * @param {string} eventName - Event name
@@ -157,6 +205,24 @@ class EventBus {
157
205
  getHandlerCount(eventName) {
158
206
  return this.events.has(eventName) ? this.events.get(eventName).length : 0;
159
207
  }
208
+
209
+ /**
210
+ * Get wildcard handler count
211
+ */
212
+ getWildcardHandlerCount() {
213
+ return this.wildcardHandlers.size;
214
+ }
215
+
216
+ /**
217
+ * Get total handler count (for debugging/monitoring)
218
+ */
219
+ getTotalHandlerCount() {
220
+ let count = this.wildcardHandlers.size;
221
+ for (const handlers of this.events.values()) {
222
+ count += handlers.length;
223
+ }
224
+ return count;
225
+ }
160
226
  }
161
227
 
162
228
  // Create singleton instance
@@ -85,13 +85,50 @@ class StateManager {
85
85
  return this.state;
86
86
  }
87
87
 
88
- subscribe(callback) {
88
+ subscribe(callback, subscriberId = null) {
89
89
  this.subscribers.add(callback);
90
- return () => this.unsubscribe(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);
91
100
  }
92
101
 
93
- unsubscribe(callback) {
102
+ unsubscribe(callback, subscriberId = null) {
94
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;
95
132
  }
96
133
 
97
134
  notifySubscribers(oldState, newState) {
@@ -362,6 +399,10 @@ class StateManager {
362
399
  // Destroy
363
400
  destroy() {
364
401
  this.subscribers.clear();
402
+ if (this._subscriberIds) {
403
+ this._subscriberIds.clear();
404
+ this._subscriberIds = null;
405
+ }
365
406
  this.state = null;
366
407
  this.calendar = null;
367
408
  }