@forcecalendar/interface 0.10.0 → 1.0.0

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": "0.10.0",
3
+ "version": "1.0.0",
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",
@@ -108,11 +108,30 @@ export class MonthView extends BaseComponent {
108
108
  }
109
109
 
110
110
  getContrastingTextColor(bgColor) {
111
- if (!bgColor) return 'white';
112
- const color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
113
- const r = parseInt(color.substring(0, 2), 16);
114
- const g = parseInt(color.substring(2, 4), 16);
115
- const b = parseInt(color.substring(4, 6), 16);
111
+ if (!bgColor || typeof bgColor !== 'string') return 'white';
112
+
113
+ // Extract hex color, removing # if present
114
+ const color = (bgColor.charAt(0) === '#') ? bgColor.substring(1) : bgColor;
115
+
116
+ // Validate hex format (3 or 6 characters)
117
+ if (!/^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(color)) {
118
+ return 'white'; // Fallback for invalid format
119
+ }
120
+
121
+ // Expand 3-char hex to 6-char
122
+ const fullColor = color.length === 3
123
+ ? color[0] + color[0] + color[1] + color[1] + color[2] + color[2]
124
+ : color;
125
+
126
+ const r = parseInt(fullColor.substring(0, 2), 16);
127
+ const g = parseInt(fullColor.substring(2, 4), 16);
128
+ const b = parseInt(fullColor.substring(4, 6), 16);
129
+
130
+ // Check for NaN (shouldn't happen with validation, but just in case)
131
+ if (isNaN(r) || isNaN(g) || isNaN(b)) {
132
+ return 'white';
133
+ }
134
+
116
135
  const uicolors = [r / 255, g / 255, b / 255];
117
136
  const c = uicolors.map((col) => {
118
137
  if (col <= 0.03928) {
@@ -102,13 +102,14 @@ class EventBus {
102
102
  }
103
103
  }
104
104
 
105
- // Handle wildcard subscriptions
106
- for (const subscription of this.wildcardHandlers) {
105
+ // Handle wildcard subscriptions (copy Set to avoid mutation during iteration)
106
+ const toRemove = [];
107
+ for (const subscription of [...this.wildcardHandlers]) {
107
108
  if (this.matchesPattern(eventName, subscription.pattern)) {
108
109
  const { handler, once } = subscription;
109
110
 
110
111
  if (once) {
111
- this.wildcardHandlers.delete(subscription);
112
+ toRemove.push(subscription);
112
113
  }
113
114
 
114
115
  try {
@@ -121,6 +122,8 @@ class EventBus {
121
122
  }
122
123
  }
123
124
  }
125
+ // Remove one-time handlers after iteration
126
+ toRemove.forEach(sub => this.wildcardHandlers.delete(sub));
124
127
 
125
128
  return Promise.all(promises);
126
129
  }
@@ -145,33 +145,53 @@ class StateManager {
145
145
  // Event management
146
146
  addEvent(event) {
147
147
  const addedEvent = this.calendar.addEvent(event);
148
- this.state.events.push(addedEvent);
149
- this.setState({ events: [...this.state.events] });
148
+ if (!addedEvent) {
149
+ console.error('Failed to add event to calendar');
150
+ eventBus.emit('event:error', { action: 'add', event, error: 'Failed to add event' });
151
+ return null;
152
+ }
153
+ // Create new array to avoid mutation before setState
154
+ const newEvents = [...this.state.events, addedEvent];
155
+ this.setState({ events: newEvents });
150
156
  eventBus.emit('event:added', { event: addedEvent });
151
157
  return addedEvent;
152
158
  }
153
159
 
154
160
  updateEvent(eventId, updates) {
155
161
  const event = this.calendar.updateEvent(eventId, updates);
156
- if (event) {
157
- const index = this.state.events.findIndex(e => e.id === eventId);
158
- if (index > -1) {
159
- this.state.events[index] = event;
160
- this.setState({ events: [...this.state.events] });
161
- eventBus.emit('event:updated', { event });
162
- }
162
+ if (!event) {
163
+ console.error(`Failed to update event: ${eventId}`);
164
+ eventBus.emit('event:error', { action: 'update', eventId, updates, error: 'Event not found in calendar' });
165
+ return null;
163
166
  }
167
+
168
+ const index = this.state.events.findIndex(e => e.id === eventId);
169
+ if (index === -1) {
170
+ console.error(`Event ${eventId} not found in state`);
171
+ eventBus.emit('event:error', { action: 'update', eventId, error: 'Event not found in state' });
172
+ return null;
173
+ }
174
+
175
+ // Create new array to avoid mutation before setState
176
+ const newEvents = [...this.state.events];
177
+ newEvents[index] = event;
178
+ this.setState({ events: newEvents });
179
+ eventBus.emit('event:updated', { event });
164
180
  return event;
165
181
  }
166
182
 
167
183
  deleteEvent(eventId) {
168
184
  const deleted = this.calendar.removeEvent(eventId);
169
- if (deleted) {
170
- this.state.events = this.state.events.filter(e => e.id !== eventId);
171
- this.setState({ events: [...this.state.events] });
172
- eventBus.emit('event:deleted', { eventId });
185
+ if (!deleted) {
186
+ console.error(`Failed to delete event: ${eventId}`);
187
+ eventBus.emit('event:error', { action: 'delete', eventId, error: 'Event not found' });
188
+ return false;
173
189
  }
174
- return deleted;
190
+ // Create new array to avoid mutation before setState
191
+ const newEvents = this.state.events.filter(e => e.id !== eventId);
192
+ this.setState({ events: newEvents });
193
+ eventBus.emit('event:deleted', { eventId });
194
+ return true;
175
195
  }
176
196
 
177
197
  getEvents() {
@@ -255,6 +255,15 @@ export class DOMUtils {
255
255
  const focusableElements = container.querySelectorAll(
256
256
  'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
257
257
  );
258
+
259
+ // Handle case where there are no focusable elements
260
+ if (focusableElements.length === 0) {
261
+ // Make container focusable as fallback
262
+ container.setAttribute('tabindex', '-1');
263
+ container.focus();
264
+ return () => container.removeAttribute('tabindex');
265
+ }
266
+
258
267
  const firstFocusable = focusableElements[0];
259
268
  const lastFocusable = focusableElements[focusableElements.length - 1];
260
269
 
@@ -263,12 +272,12 @@ export class DOMUtils {
263
272
 
264
273
  if (e.shiftKey) {
265
274
  if (document.activeElement === firstFocusable) {
266
- lastFocusable.focus();
275
+ lastFocusable?.focus();
267
276
  e.preventDefault();
268
277
  }
269
278
  } else {
270
279
  if (document.activeElement === lastFocusable) {
271
- firstFocusable.focus();
280
+ firstFocusable?.focus();
272
281
  e.preventDefault();
273
282
  }
274
283
  }