@chaaanito/event-resource-calendar 1.2.0 → 1.3.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +120 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaaanito/event-resource-calendar",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/chaaanito/event-resource-calendar.git"
package/src/index.js CHANGED
@@ -7,7 +7,69 @@
7
7
  * @typedef {import('./event-resource.types.js').CustomButton} CustomButton
8
8
  */
9
9
 
10
+ /**
11
+ * EventResource
12
+ * A lightweight, high-performance vanilla JavaScript resource calendar library.
13
+ * Features O(1) internal event mapping, extensible rich HTML layout renderers, holiday detection, and scroll freezing.
14
+ */
10
15
  export default class EventResource {
16
+ /**
17
+ * Initializes a new EventResource calendar instance, mounts it to the DOM, and fires initial render passes.
18
+ * @param {EventResourceOptions} options - Configuration parameters required to bootstrap the calendar matrix.
19
+ * @throws {Error} Throws if a valid `container` element, Node, or string selector cannot be resolved in the DOM.
20
+ * @example
21
+ * const calendar = new EventResource({
22
+ * // 1. Core Mount & State
23
+ * container: '#calendar-root',
24
+ * defaultView: 'daily',
25
+ * defaultDate: '2026-06-24', // Accepts string, number (epoch), or Date object
26
+ * * // 2. Structural UI Toggles
27
+ * showControls: true,
28
+ * stickyHeaders: true,
29
+ * * // 3. Grid Definitions (Static Fallbacks)
30
+ * rooms: [{ id: 'r1', name: 'Studio A', capacity: 10 }],
31
+ * timeSlots: [{ id: 't1', label: '09:00 AM' }],
32
+ * holidays: [{ date: '2026-12-25', name: 'Christmas Day' }],
33
+ * * // 4. Initial In-Memory Data
34
+ * initialEvents: [{
35
+ * id: 'evt-1',
36
+ * roomId: 'r1',
37
+ * timeId: 't1',
38
+ * title: 'Morning Sync',
39
+ * color: '#10b981'
40
+ * }],
41
+ * * // 5. Toolbar Extensions
42
+ * customButtons: [{
43
+ * label: 'Export PDF',
44
+ * className: 'bg-red-500 text-white',
45
+ * onClick: (e) => console.log('Exporting...', e)
46
+ * }],
47
+ * * // 6. Interaction Event Hooks
48
+ * onCellClick: (payload) => console.log('Empty slot clicked!'),
49
+ * onEventClick: (payload) => alert(`Clicked: ${payload.event.title}`),
50
+ * * // 7. Rich HTML Generation Intercepts
51
+ * renderRoomHeader: (room) => `<div>${room.name}</div>`,
52
+ * renderTimeSlotHeader: (slot) => `<div>${slot.label}</div>`,
53
+ * renderEvent: (event) => `<div>${event.title}</div>`,
54
+ * * // 8. Async Lifecycle Management (Draws skeleton first, then populates data)
55
+ * fetchRooms: async (date, view) => {
56
+ * const res = await fetch(`/api/rooms?date=${date.toISOString()}&view=${view}`);
57
+ * return await res.json();
58
+ * },
59
+ * fetchTimeSlots: async (date, view) => {
60
+ * const res = await fetch(`/api/timeslots?view=${view}`);
61
+ * return await res.json();
62
+ * },
63
+ * fetchHolidays: async (date, view) => {
64
+ * const res = await fetch(`/api/holidays?year=${date.getFullYear()}`);
65
+ * return await res.json();
66
+ * },
67
+ * fetchEvents: async (date, view) => {
68
+ * const res = await fetch(`/api/events?date=${date.toISOString()}&view=${view}`);
69
+ * return await res.json();
70
+ * }
71
+ * });
72
+ */
11
73
  constructor(options) {
12
74
  let resolvedContainer = null;
13
75
 
@@ -92,7 +154,6 @@ export default class EventResource {
92
154
  _buildEventsMap = () => {
93
155
  this.eventsMap.clear();
94
156
  for (const ev of this.events) {
95
- // PERFORMANCE FIX: Use '::' as a delimiter in case user IDs contain hyphens
96
157
  const key = `${ev.roomId}::${ev.timeId}`;
97
158
  if (!this.eventsMap.has(key)) {
98
159
  this.eventsMap.set(key, []);
@@ -101,17 +162,38 @@ export default class EventResource {
101
162
  }
102
163
  };
103
164
 
165
+ /**
166
+ * Forces a chronological state shift, rewiring internal date parameters and firing synchronous/asynchronous re-render loops.
167
+ * @param {Date|string|number} newDate - REQUIRED: The new calendar baseline target date parameter.
168
+ * @returns {Promise<void>} Resolves automatically when the async data refetch and complete re-render lifecycle are complete.
169
+ * @example
170
+ * await calendar.setDate('2026-10-31');
171
+ */
104
172
  setDate = async (newDate) => {
105
173
  this.currentDate = new Date(newDate);
106
- await this.forceRender(); // Requires full rebuild to evaluate new holidays
174
+ await this.forceRender();
107
175
  };
108
176
 
177
+ /**
178
+ * Mutates the application structural layout framework dynamically between daily and weekly granularities.
179
+ * @param {'daily'|'weekly'} newView - REQUIRED: The strict target structural mode selection string.
180
+ * @returns {Promise<void>} Resolves when the refetch, DOM teardown, and structural framework update complete.
181
+ * @example
182
+ * await calendar.setView('weekly');
183
+ */
109
184
  setView = async (newView) => {
110
185
  if (this.currentView === newView) return;
111
186
  this.currentView = newView;
112
187
  await this.forceRender();
113
188
  };
114
189
 
190
+ /**
191
+ * Calculates timeline vector offsets based on the active view mode, steps the internal date parameter, and triggers reconciliations.
192
+ * @param {'prev'|'next'} direction - REQUIRED: The chronological vector direction keyword.
193
+ * @returns {Promise<void>} Resolves upon successful navigation and canvas redraw.
194
+ * @example
195
+ * await calendar.navigate('next');
196
+ */
115
197
  navigate = async (direction) => {
116
198
  const daysToMove = this.currentView === "weekly" ? 7 : 1;
117
199
  const multiplier = direction === "next" ? 1 : -1;
@@ -124,31 +206,53 @@ export default class EventResource {
124
206
 
125
207
  // --- Public API ---
126
208
 
209
+ /**
210
+ * Injects a raw configuration event into the application data state. Recompiles the collision map and forces a high-speed DOM update.
211
+ * @param {CalendarEvent} newEvent - REQUIRED: A valid object data map matching configuration structural specs exactly.
212
+ * @returns {void}
213
+ * @example
214
+ * calendar.addEvent({ id: 'evt-999', roomId: 'r1', timeId: 't1', title: 'Emergency Sync' });
215
+ */
127
216
  addEvent = (newEvent) => {
128
217
  this.events.push(newEvent);
129
218
  this._buildEventsMap();
130
- // PERFORMANCE FIX: Only redraw the events, leave the layout untouched
131
219
  this._renderEvents();
132
220
  };
133
221
 
222
+ /**
223
+ * Executes a hard delete across the internal event arrays based strictly on a uniquely matched id reference string.
224
+ * @param {string|number} eventId - REQUIRED: The exact, unique reference key index matching the target CalendarEvent.id.
225
+ * @returns {void}
226
+ * @example
227
+ * calendar.removeEvent('evt-999');
228
+ */
134
229
  removeEvent = (eventId) => {
135
230
  this.events = this.events.filter((e) => e.id !== eventId);
136
231
  this._buildEventsMap();
137
- // PERFORMANCE FIX: Only redraw the events, leave the layout untouched
138
232
  this._renderEvents();
139
233
  };
140
234
 
235
+ /**
236
+ * Wipes out all transient operational event records cached in client memory structures while preserving column rules and grid row configurations.
237
+ * @returns {void}
238
+ * @example
239
+ * calendar.clearAllEvents();
240
+ */
141
241
  clearAllEvents = () => {
142
242
  this.events = [];
143
243
  this.eventsMap.clear();
144
- // PERFORMANCE FIX: Fast clear of events without layout thrashing
145
244
  this._renderEvents();
146
245
  };
147
246
 
247
+ /**
248
+ * Triggers a comprehensive data replenishment cycle. Evaluates external fetch connectors, updates map caches, and rebuilds the visual DOM.
249
+ * @returns {Promise<void>} Resolves once all external data resolves and the DOM reconciliation concludes successfully.
250
+ * @example
251
+ * await calendar.forceRender();
252
+ */
148
253
  forceRender = async () => {
149
254
  if (this.isFetching) return;
150
255
 
151
- // 1. Draw empty skeleton instantly based on current memory
152
256
  this.render();
153
257
 
154
258
  const hasAsyncSources =
@@ -188,10 +292,17 @@ export default class EventResource {
188
292
  }
189
293
 
190
294
  this._buildEventsMap();
191
- // 2. Re-render entirely to apply fetched layout rules and fetched events
192
295
  this.render();
193
296
  };
194
297
 
298
+ /**
299
+ * Executes irreversible teardown routines protecting application host memory state cycles.
300
+ * Wipes parameters, drops listener closures, clears map caches, and securely unmounts UI sub-trees.
301
+ * @returns {void}
302
+ * @example
303
+ * calendar.destroy();
304
+ * calendar = null;
305
+ */
195
306
  destroy = () => {
196
307
  this.events = [];
197
308
  this.rooms = [];
@@ -299,17 +410,14 @@ export default class EventResource {
299
410
  /**
300
411
  * Manual render hook. Triggers a complete synchronization of the Skeleton UI and the Data Layer.
301
412
  * @returns {void}
413
+ * @example
414
+ * calendar.render();
302
415
  */
303
416
  render = () => {
304
417
  this._renderSkeleton();
305
418
  this._renderEvents();
306
419
  };
307
420
 
308
- /**
309
- * PERFORMANCE FIX: Renders ONLY the static layout (rows, columns, headers, empty cells).
310
- * Never touches actual event data or cards.
311
- * @private
312
- */
313
421
  _renderSkeleton = () => {
314
422
  this.container.innerHTML = "";
315
423
 
@@ -362,14 +470,11 @@ export default class EventResource {
362
470
  const cell = document.createElement("div");
363
471
  cell.className = `er-grid-cell ${activeHoliday ? "er-holiday-cell" : ""}`;
364
472
 
365
- // Inject queryable coordinates for high-speed DOM updates
366
473
  cell.dataset.roomId = room.id;
367
474
  cell.dataset.timeId = time.id;
368
475
 
369
476
  cell.addEventListener("click", () => {
370
477
  if (this.onCellClick) {
371
- // PERFORMANCE FIX: Dynamically fetch current events at click time.
372
- // Avoids needing to detach/reattach listeners when data changes.
373
478
  const currentEvents =
374
479
  this.eventsMap.get(`${room.id}::${time.id}`) || [];
375
480
 
@@ -406,25 +511,17 @@ export default class EventResource {
406
511
  this.container.appendChild(wrapper);
407
512
  };
408
513
 
409
- /**
410
- * PERFORMANCE FIX: Renders ONLY the event cards. Leaves the skeleton UI completely intact.
411
- * Uses O(1) DOM targeting via data attributes.
412
- * @private
413
- */
414
514
  _renderEvents = () => {
415
- // 1. Wipe only the existing event DOM nodes, leaving empty cells perfectly intact
416
515
  const existingEvents = this.container.querySelectorAll(".er-event");
417
516
  existingEvents.forEach((el) => el.remove());
418
517
 
419
518
  const activeHoliday = this._getHolidayForDate(this.currentDate);
420
519
 
421
- // 2. Map and inject fresh events into their specific coordinate cells
422
520
  this.events.forEach((ev) => {
423
521
  const cell = this.container.querySelector(
424
522
  `[data-room-id="${ev.roomId}"][data-time-id="${ev.timeId}"]`,
425
523
  );
426
524
 
427
- // If cell doesn't exist (e.g., event is scheduled for a room not currently in view), skip rendering
428
525
  if (!cell) return;
429
526
 
430
527
  const eventDiv = document.createElement("div");
@@ -440,11 +537,9 @@ export default class EventResource {
440
537
  eventDiv.addEventListener("click", (e) => {
441
538
  e.stopPropagation();
442
539
  if (this.onEventClick) {
443
- // Dynamically fetch sibling events sharing this exact coordinate
444
540
  const sharedEvents =
445
541
  this.eventsMap.get(`${ev.roomId}::${ev.timeId}`) || [];
446
542
 
447
- // Look up current row/col index dynamically based on UI position
448
543
  const roomIndex = this.rooms.findIndex(
449
544
  (r) => String(r.id) === String(ev.roomId),
450
545
  );