@forcecalendar/interface 1.0.61 → 1.2.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.
@@ -1,901 +1,770 @@
1
- var H = Object.defineProperty;
2
- var L = (h, t, e) => t in h ? H(h, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : h[t] = e;
3
- var m = (h, t, e) => L(h, typeof t != "symbol" ? t + "" : t, e);
4
- import { Calendar as F, DateUtils as I } from "@forcecalendar/core";
5
- class $ extends HTMLElement {
6
- constructor() {
7
- super(), this.attachShadow({ mode: "open" }), this._listeners = [], this._state = null, this._props = /* @__PURE__ */ new Map(), this._initialized = !1;
8
- }
9
- // Lifecycle methods
10
- connectedCallback() {
11
- this._initialized || (this.initialize(), this._initialized = !0), this.mount();
12
- }
13
- disconnectedCallback() {
14
- this.unmount(), this.cleanup(), this._styleEl = null, this._contentWrapper = null;
15
- }
16
- // To be overridden by child classes
17
- initialize() {
18
- }
19
- mount() {
20
- this.render();
21
- }
22
- unmount() {
23
- }
24
- cleanup() {
25
- this._listeners.forEach(({ element: t, event: e, handler: r }) => {
26
- t.removeEventListener(e, r);
27
- }), this._listeners = [];
28
- }
29
- // State management
30
- setState(t) {
31
- const e = this._state;
32
- this._state = { ...this._state, ...t }, this.stateChanged(e, this._state), this.render();
33
- }
34
- getState() {
35
- return this._state;
36
- }
37
- stateChanged(t, e) {
38
- }
39
- // Props management
40
- setProp(t, e) {
41
- const r = this._props.get(t);
42
- this._props.set(t, e), this.propChanged(t, r, e);
43
- }
44
- getProp(t) {
45
- return this._props.get(t);
46
- }
47
- propChanged(t, e, r) {
48
- }
49
- // Event handling
50
- addListener(t, e, r) {
51
- if (!t || !e || !r) {
52
- console.warn("addListener called with invalid parameters", { element: t, event: e, handler: r });
53
- return;
54
- }
55
- const i = r.bind(this);
56
- t.addEventListener(e, i), this._listeners.push({ element: t, event: e, handler: i });
57
- }
58
- emit(t, e = {}) {
59
- this.dispatchEvent(
60
- new CustomEvent(t, {
61
- detail: e,
62
- bubbles: !0,
63
- composed: !0
64
- })
65
- );
66
- }
67
- // Style management
68
- getStyles() {
69
- return "";
70
- }
71
- getBaseStyles() {
72
- return `
73
- :host {
74
- display: block;
75
- box-sizing: border-box;
76
- }
77
-
78
- *, *::before, *::after {
79
- box-sizing: inherit;
80
- }
81
- `;
82
- }
83
- // Template rendering
84
- render() {
85
- this.cleanup();
86
- const t = this.template();
87
- this._styleEl || (this._styleEl = document.createElement("style"), this.shadowRoot.appendChild(this._styleEl), this._contentWrapper = document.createElement("div"), this._contentWrapper.setAttribute("id", "fc-root"), this._contentWrapper.style.display = "contents", this.shadowRoot.appendChild(this._contentWrapper)), this._styleEl.textContent = this.getBaseStyles() + `
88
- ` + this.getStyles();
89
- const e = this._saveScrollPositions(), r = this._getActiveElementSelector();
90
- this._contentWrapper.innerHTML = t, this._restoreScrollPositions(e), this._restoreFocus(r), this.afterRender();
91
- }
92
- /**
93
- * Save scroll positions of all scrollable containers within shadow DOM
94
- * @returns {Map<string, {top: number, left: number}>}
95
- */
96
- _saveScrollPositions() {
97
- const t = /* @__PURE__ */ new Map();
98
- return this._contentWrapper && this._contentWrapper.querySelectorAll("[id]").forEach((r) => {
99
- (r.scrollTop !== 0 || r.scrollLeft !== 0) && t.set(r.id, { top: r.scrollTop, left: r.scrollLeft });
100
- }), t;
101
- }
102
- /**
103
- * Restore previously saved scroll positions
104
- * @param {Map<string, {top: number, left: number}>} positions
105
- */
106
- _restoreScrollPositions(t) {
107
- if (!this._contentWrapper || t.size === 0) return;
108
- const e = typeof CSS < "u" && typeof CSS.escape == "function" ? CSS.escape : (r) => String(r).replace(/["\\]/g, "\\$&");
109
- t.forEach((r, i) => {
110
- const s = this._contentWrapper.querySelector(`[id="${e(i)}"]`);
111
- s && (s.scrollTop = r.top, s.scrollLeft = r.left);
112
- });
113
- }
114
- /**
115
- * Get a CSS selector for the currently focused element within shadow DOM
116
- * @returns {string|null}
117
- */
118
- _getActiveElementSelector() {
119
- const t = this.shadowRoot.activeElement;
120
- return !t || t === this._contentWrapper ? null : t.id ? `[id="${CSS.escape(t.id)}"]` : t.dataset && t.dataset.action ? `[data-action="${CSS.escape(t.dataset.action)}"]` : t.dataset && t.dataset.view ? `[data-view="${CSS.escape(t.dataset.view)}"]` : t.tagName ? t.tagName.toLowerCase() : null;
121
- }
122
- /**
123
- * Restore focus to a previously focused element
124
- * @param {string|null} selector
125
- */
126
- _restoreFocus(t) {
127
- if (!(!t || !this._contentWrapper))
128
- try {
129
- const e = this._contentWrapper.querySelector(t);
130
- e && typeof e.focus == "function" && e.focus();
131
- } catch {
132
- }
133
- }
134
- template() {
135
- return "";
136
- }
137
- afterRender() {
138
- }
139
- // Utility methods
140
- $(t) {
141
- return this.shadowRoot.querySelector(t);
142
- }
143
- $$(t) {
144
- return this.shadowRoot.querySelectorAll(t);
145
- }
146
- // Attribute observation
147
- static get observedAttributes() {
148
- return [];
149
- }
150
- attributeChangedCallback(t, e, r) {
151
- this.setProp(t, r), this._initialized && this.render();
152
- }
153
- }
154
- class S {
155
- constructor() {
156
- this.events = /* @__PURE__ */ new Map(), this.wildcardHandlers = /* @__PURE__ */ new Set();
157
- }
158
- /**
159
- * Subscribe to an event
160
- * @param {string} eventName - Event name or pattern (supports wildcards)
161
- * @param {Function} handler - Event handler function
162
- * @param {Object} options - Subscription options
163
- * @returns {Function} Unsubscribe function
164
- */
165
- on(t, e, r = {}) {
166
- const { once: i = !1, priority: s = 0 } = r;
167
- if (t.includes("*")) {
168
- const o = { pattern: t, handler: e, once: i, priority: s };
169
- return this.wildcardHandlers.add(o), () => this.wildcardHandlers.delete(o);
170
- }
171
- this.events.has(t) || this.events.set(t, []);
172
- const n = { handler: e, once: i, priority: s }, a = this.events.get(t);
173
- return a.push(n), a.sort((o, c) => c.priority - o.priority), () => {
174
- const o = a.indexOf(n);
175
- o > -1 && a.splice(o, 1);
176
- };
177
- }
178
- /**
179
- * Subscribe to an event that fires only once
180
- */
181
- once(t, e, r = {}) {
182
- return this.on(t, e, { ...r, once: !0 });
183
- }
184
- /**
185
- * Unsubscribe from an event
186
- */
187
- off(t, e) {
188
- if (t.includes("*")) {
189
- for (const s of this.wildcardHandlers)
190
- if (s.pattern === t && s.handler === e) {
191
- this.wildcardHandlers.delete(s);
192
- return;
193
- }
194
- return;
195
- }
196
- if (!this.events.has(t)) return;
197
- const r = this.events.get(t), i = r.findIndex((s) => s.handler === e);
198
- i > -1 && r.splice(i, 1), r.length === 0 && this.events.delete(t);
199
- }
200
- /**
201
- * Remove all wildcard handlers matching a pattern
202
- * @param {string} pattern - Pattern to match (e.g., 'event:*')
203
- */
204
- offWildcard(t) {
205
- for (const e of [...this.wildcardHandlers])
206
- e.pattern === t && this.wildcardHandlers.delete(e);
207
- }
208
- /**
209
- * Remove all handlers (regular and wildcard) for a specific handler function
210
- * Useful for cleanup when a component is destroyed
211
- * @param {Function} handler - Handler function to remove
212
- */
213
- offAll(t) {
214
- for (const [e, r] of this.events) {
215
- const i = r.findIndex((s) => s.handler === t);
216
- i > -1 && r.splice(i, 1), r.length === 0 && this.events.delete(e);
217
- }
218
- for (const e of [...this.wildcardHandlers])
219
- e.handler === t && this.wildcardHandlers.delete(e);
220
- }
221
- /**
222
- * Emit an event synchronously
223
- * @param {string} eventName - Event name
224
- * @param {*} data - Event data
225
- */
226
- emit(t, e) {
227
- if (this.events.has(t)) {
228
- const i = [...this.events.get(t)];
229
- for (const s of i) {
230
- const { handler: n, once: a } = s;
231
- a && this.off(t, n);
232
- try {
233
- n(e, t);
234
- } catch (o) {
235
- console.error(`Error in event handler for ${t}:`, o);
236
- }
237
- }
238
- }
239
- const r = [];
240
- for (const i of [...this.wildcardHandlers])
241
- if (this.matchesPattern(t, i.pattern)) {
242
- const { handler: s, once: n } = i;
243
- n && r.push(i);
244
- try {
245
- s(e, t);
246
- } catch (a) {
247
- console.error(`Error in wildcard handler for ${t}:`, a);
248
- }
249
- }
250
- r.forEach((i) => this.wildcardHandlers.delete(i));
251
- }
252
- /**
253
- * Check if event name matches a pattern
254
- * Only `*` acts as a wildcard; all other characters match literally
255
- */
256
- matchesPattern(t, e) {
257
- const r = e.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
258
- return new RegExp("^" + r.replace(/\*/g, ".*") + "$").test(t);
259
- }
260
- /**
261
- * Clear all event subscriptions
262
- */
263
- clear() {
264
- this.events.clear(), this.wildcardHandlers.clear();
265
- }
266
- /**
267
- * Get all registered event names
268
- */
269
- getEventNames() {
270
- return Array.from(this.events.keys());
271
- }
272
- /**
273
- * Get handler count for an event
274
- */
275
- getHandlerCount(t) {
276
- return this.events.has(t) ? this.events.get(t).length : 0;
277
- }
278
- /**
279
- * Get wildcard handler count
280
- */
281
- getWildcardHandlerCount() {
282
- return this.wildcardHandlers.size;
283
- }
284
- /**
285
- * Get total handler count (for debugging/monitoring)
286
- */
287
- getTotalHandlerCount() {
288
- let t = this.wildcardHandlers.size;
289
- for (const e of this.events.values())
290
- t += e.length;
291
- return t;
292
- }
293
- }
294
- const j = new S();
295
- class z {
296
- constructor(t = {}) {
297
- this.eventBus = new S(), this.calendar = new F({
298
- view: t.view || "month",
299
- date: t.date || /* @__PURE__ */ new Date(),
300
- weekStartsOn: t.weekStartsOn ?? 0,
301
- locale: t.locale || "en-US",
302
- timeZone: t.timeZone || Intl.DateTimeFormat().resolvedOptions().timeZone,
303
- ...t
304
- }), this.state = {
305
- view: this.calendar.getView(),
306
- currentDate: this.calendar.getCurrentDate(),
307
- events: [],
308
- selectedEvent: null,
309
- selectedDate: null,
310
- loading: !1,
311
- error: null,
312
- config: { ...t }
313
- }, this.subscribers = /* @__PURE__ */ new Set(), this.subscribe = this.subscribe.bind(this), this.unsubscribe = this.unsubscribe.bind(this), this.setState = this.setState.bind(this), this._syncEventsFromCore({ silent: !0 });
314
- }
315
- /**
316
- * Sync state.events from Core calendar (single source of truth)
317
- * This ensures state.events always matches Core's event store.
318
- *
319
- * @param {object} options
320
- * @param {boolean} options.silent - suppress subscriber notifications
321
- * @param {boolean} options.force - always update even when IDs match
322
- * (required after updateEvent where IDs
323
- * are unchanged but content has changed)
324
- */
325
- _syncEventsFromCore(t = {}) {
326
- const { force: e = !1 } = t, r = this.calendar.getEvents() || [];
327
- return (e || this.state.events.length !== r.length || !this._eventsMatch(this.state.events, r)) && this.setState({ events: [...r] }, t), r;
328
- }
329
- /**
330
- * Check if two event arrays have the same events by id.
331
- * Only used for add/delete guards — updateEvent must pass force:true
332
- * to bypass this check because IDs are unchanged after an update.
333
- */
334
- _eventsMatch(t, e) {
335
- if (t.length !== e.length) return !1;
336
- const r = new Set(t.map((i) => i.id));
337
- return e.every((i) => r.has(i.id));
338
- }
339
- // State management
340
- getState() {
341
- return {
342
- ...this.state,
343
- config: { ...this.state.config },
344
- events: [...this.state.events]
345
- };
346
- }
347
- setState(t, e = {}) {
348
- const { silent: r = !1 } = e, i = { ...this.state };
349
- return this.state = { ...this.state, ...t }, r || (this.notifySubscribers(i, this.state), this.emitStateChange(i, this.state)), this.state;
350
- }
351
- subscribe(t, e = null) {
352
- return this.subscribers.add(t), e && (this._subscriberIds || (this._subscriberIds = /* @__PURE__ */ new Map()), this._subscriberIds.set(e, t)), () => this.unsubscribe(t, e);
353
- }
354
- unsubscribe(t, e = null) {
355
- this.subscribers.delete(t), e && this._subscriberIds && this._subscriberIds.delete(e);
356
- }
357
- /**
358
- * Unsubscribe by subscriber ID
359
- * @param {string} subscriberId - ID used when subscribing
360
- */
361
- unsubscribeById(t) {
362
- if (!this._subscriberIds) return !1;
363
- const e = this._subscriberIds.get(t);
364
- return e ? (this.subscribers.delete(e), this._subscriberIds.delete(t), !0) : !1;
365
- }
366
- /**
367
- * Get subscriber count (for debugging/monitoring)
368
- */
369
- getSubscriberCount() {
370
- return this.subscribers.size;
371
- }
372
- notifySubscribers(t, e) {
373
- this.subscribers.forEach((r) => {
374
- try {
375
- r(e, t);
376
- } catch (i) {
377
- console.error("Error in state subscriber:", i);
378
- }
379
- });
380
- }
381
- emitStateChange(t, e) {
382
- const r = Object.keys(e).filter((i) => t[i] !== e[i]);
383
- r.forEach((i) => {
384
- this.eventBus.emit(`state:${i}:changed`, {
385
- oldValue: t[i],
386
- newValue: e[i],
387
- state: e
388
- });
389
- }), r.length > 0 && this.eventBus.emit("state:changed", { oldState: t, newState: e, changedKeys: r });
390
- }
391
- // Calendar operations
392
- setView(t) {
393
- this.calendar.setView(t), this.setState({ view: t }), this.eventBus.emit("view:changed", { view: t });
394
- }
395
- getView() {
396
- return this.state.view;
397
- }
398
- setDate(t) {
399
- this.calendar.goToDate(t), this.setState({ currentDate: this.calendar.getCurrentDate() }), this.eventBus.emit("date:changed", { date: this.state.currentDate });
400
- }
401
- getCurrentDate() {
402
- return this.state.currentDate;
403
- }
404
- // Navigation
405
- next() {
406
- this.calendar.next(), this.setState({ currentDate: this.calendar.getCurrentDate() }), this.eventBus.emit("navigation:next", { date: this.state.currentDate });
407
- }
408
- previous() {
409
- this.calendar.previous(), this.setState({ currentDate: this.calendar.getCurrentDate() }), this.eventBus.emit("navigation:previous", { date: this.state.currentDate });
410
- }
411
- today() {
412
- this.calendar.today(), this.setState({ currentDate: this.calendar.getCurrentDate() }), this.eventBus.emit("navigation:today", { date: this.state.currentDate });
413
- }
414
- goToDate(t) {
415
- this.calendar.goToDate(t), this.setState({ currentDate: this.calendar.getCurrentDate() }), this.eventBus.emit("navigation:goto", { date: this.state.currentDate });
416
- }
417
- // Event management
418
- addEvent(t) {
419
- const e = this.calendar.addEvent(t);
420
- return e ? (this._syncEventsFromCore(), this.eventBus.emit("event:add", { event: e }), this.eventBus.emit("event:added", { event: e }), e) : (console.error("Failed to add event to calendar"), this.eventBus.emit("event:error", { action: "add", event: t, error: "Failed to add event" }), null);
421
- }
422
- updateEvent(t, e) {
423
- this._syncEventsFromCore({ silent: !0 });
424
- const r = this.calendar.updateEvent(t, e);
425
- return r ? (this._syncEventsFromCore({ force: !0 }), this.eventBus.emit("event:update", { event: r }), this.eventBus.emit("event:updated", { event: r }), r) : (console.error(`Failed to update event: ${t}`), this.eventBus.emit("event:error", {
426
- action: "update",
427
- eventId: t,
428
- updates: e,
429
- error: "Event not found in calendar"
430
- }), null);
431
- }
432
- deleteEvent(t) {
433
- return this._syncEventsFromCore({ silent: !0 }), this.calendar.removeEvent(t) ? (this._syncEventsFromCore(), this.eventBus.emit("event:remove", { eventId: t }), this.eventBus.emit("event:deleted", { eventId: t }), !0) : (console.error(`Failed to delete event: ${t}`), this.eventBus.emit("event:error", { action: "delete", eventId: t, error: "Event not found" }), !1);
434
- }
435
- getEvents() {
436
- return this.calendar.getEvents() || [];
437
- }
438
- /**
439
- * Force sync state.events from Core calendar
440
- * Use this if you've modified events directly on the Core calendar
441
- */
442
- syncEvents() {
443
- return this._syncEventsFromCore();
444
- }
445
- getEventsForDate(t) {
446
- return this.calendar.getEventsForDate(t);
447
- }
448
- getEventsInRange(t, e) {
449
- return this.calendar.getEventsInRange(t, e);
450
- }
451
- // View data
452
- getViewData() {
453
- const t = this.calendar.getViewData();
454
- return this.enrichViewData(t);
455
- }
456
- enrichViewData(t) {
457
- var i;
458
- const e = { ...t }, r = (i = this.state.selectedDate) == null ? void 0 : i.toDateString();
459
- if (e.weeks && (e.weeks = e.weeks.map((s) => ({
460
- ...s,
461
- days: s.days.map((n) => {
462
- const a = new Date(n.date);
463
- return {
464
- ...n,
465
- isSelected: a.toDateString() === r,
466
- events: n.events || this.getEventsForDate(a)
467
- };
468
- })
469
- }))), e.days && (e.days = e.days.map((s) => {
470
- const n = new Date(s.date);
471
- return {
472
- ...s,
473
- isSelected: n.toDateString() === r,
474
- events: s.events || this.getEventsForDate(n)
475
- };
476
- })), e.date && !e.days && !e.weeks) {
477
- const s = new Date(e.date);
478
- e.isSelected = s.toDateString() === r, e.events = e.events || this.getEventsForDate(s);
479
- }
480
- return e;
481
- }
482
- // Selection management
483
- selectEvent(t) {
484
- this.setState({ selectedEvent: t }), this.eventBus.emit("event:selected", { event: t });
485
- }
486
- selectEventById(t) {
487
- const e = this.state.events.find((r) => r.id === t);
488
- e && this.selectEvent(e);
489
- }
490
- deselectEvent() {
491
- this.setState({ selectedEvent: null }), this.eventBus.emit("event:deselected", {});
492
- }
493
- selectDate(t) {
494
- this.setState({ selectedDate: t }), this.eventBus.emit("date:selected", { date: t });
495
- }
496
- deselectDate() {
497
- this.setState({ selectedDate: null }), this.eventBus.emit("date:deselected", {});
498
- }
499
- // Utility methods
500
- isToday(t) {
501
- const e = /* @__PURE__ */ new Date();
502
- return t.toDateString() === e.toDateString();
503
- }
504
- isSelectedDate(t) {
505
- return this.state.selectedDate && t.toDateString() === this.state.selectedDate.toDateString();
506
- }
507
- isWeekend(t) {
508
- const e = t.getDay();
509
- return e === 0 || e === 6;
510
- }
511
- // Loading state
512
- setLoading(t) {
513
- this.setState({ loading: t });
514
- }
515
- // Error handling
516
- setError(t) {
517
- this.setState({ error: t }), t && this.eventBus.emit("error", { error: t });
518
- }
519
- clearError() {
520
- this.setState({ error: null });
521
- }
522
- // Configuration
523
- updateConfig(t) {
524
- this.setState({ config: { ...this.state.config, ...t } }), t.weekStartsOn !== void 0 && this.calendar.setWeekStartsOn(t.weekStartsOn), t.locale !== void 0 && this.calendar.setLocale(t.locale), t.timeZone !== void 0 && this.calendar.setTimezone(t.timeZone);
525
- }
526
- // Destroy
527
- destroy() {
528
- this.subscribers.clear(), this._subscriberIds && (this._subscriberIds.clear(), this._subscriberIds = null), this.eventBus && (this.eventBus.clear(), this.eventBus = null), this.state = null, this.calendar = null;
529
- }
530
- }
531
- class u extends I {
532
- /**
533
- * Format date for display
534
- */
535
- static formatDate(t, e = "default", r = "en-US") {
536
- if (!t) return "";
537
- const i = {
538
- default: { year: "numeric", month: "long", day: "numeric" },
539
- short: { year: "numeric", month: "short", day: "numeric" },
540
- long: { weekday: "long", year: "numeric", month: "long", day: "numeric" },
541
- month: { year: "numeric", month: "long" },
542
- monthShort: { year: "numeric", month: "short" },
543
- day: { weekday: "long", day: "numeric" },
544
- dayShort: { weekday: "short", day: "numeric" },
545
- time: { hour: "numeric", minute: "2-digit" },
546
- timeShort: { hour: "numeric" },
547
- datetime: {
548
- year: "numeric",
549
- month: "short",
550
- day: "numeric",
551
- hour: "numeric",
552
- minute: "2-digit"
553
- }
554
- }, s = i[e] || i.default;
555
- return new Intl.DateTimeFormat(r, s).format(t);
556
- }
557
- /**
558
- * Format time for display
559
- */
560
- static formatTime(t, e = !0, r = !1, i = "en-US") {
561
- if (!t) return "";
562
- const s = {
563
- hour: "numeric",
564
- minute: e ? "2-digit" : void 0,
565
- hour12: !r
566
- };
567
- return new Intl.DateTimeFormat(i, s).format(t);
568
- }
569
- /**
570
- * Format date range for display
571
- */
572
- static formatDateRange(t, e, r = "en-US") {
573
- if (!t) return "";
574
- if (!e || this.isSameDay(t, e))
575
- return this.formatDate(t, "default", r);
576
- const i = this.isSameYear(t, e) ? "short" : "default";
577
- return `${this.formatDate(t, i, r)} - ${this.formatDate(e, "default", r)}`;
578
- }
579
- /**
580
- * Format time range for display
581
- */
582
- static formatTimeRange(t, e, r = "en-US") {
583
- if (!t) return "";
584
- const i = this.formatTime(t, !0, !1, r);
585
- if (!e) return i;
586
- const s = this.formatTime(e, !0, !1, r);
587
- return `${i} - ${s}`;
588
- }
589
- /**
590
- * Get relative time string (e.g., "2 hours ago", "in 3 days")
591
- */
592
- static getRelativeTime(t, e = /* @__PURE__ */ new Date(), r = "en-US") {
593
- const i = new Intl.RelativeTimeFormat(r, { numeric: "auto" }), s = t - e, n = Math.floor(s / 1e3), a = Math.floor(n / 60), o = Math.floor(a / 60), c = Math.floor(o / 24), l = Math.floor(c / 7), d = Math.floor(c / 30), p = Math.floor(c / 365);
594
- return Math.abs(n) < 60 ? i.format(n, "second") : Math.abs(a) < 60 ? i.format(a, "minute") : Math.abs(o) < 24 ? i.format(o, "hour") : Math.abs(c) < 7 ? i.format(c, "day") : Math.abs(l) < 4 ? i.format(l, "week") : Math.abs(d) < 12 ? i.format(d, "month") : i.format(p, "year");
595
- }
596
- /**
597
- * Check if date is today
598
- */
599
- static isToday(t) {
600
- const e = /* @__PURE__ */ new Date();
601
- return this.isSameDay(t, e);
602
- }
603
- /**
604
- * Check if date is in the past
605
- */
606
- static isPast(t) {
607
- return t < /* @__PURE__ */ new Date();
608
- }
609
- /**
610
- * Check if date is in the future
611
- */
612
- static isFuture(t) {
613
- return t > /* @__PURE__ */ new Date();
614
- }
615
- /**
616
- * Get calendar week number
617
- */
618
- static getWeekNumber(t) {
619
- const e = new Date(t.getFullYear(), 0, 1), r = (t - e) / 864e5;
620
- return Math.ceil((r + e.getDay() + 1) / 7);
621
- }
622
- /**
623
- * Get day abbreviation
624
- */
625
- static getDayAbbreviation(t, e = "en-US") {
626
- const r = new Date(2024, 0, 7 + t);
627
- return new Intl.DateTimeFormat(e, { weekday: "short" }).format(r);
628
- }
629
- /**
630
- * Get full day name
631
- */
632
- static getDayName(t, e = "en-US") {
633
- const r = new Date(2024, 0, 7 + t);
634
- return new Intl.DateTimeFormat(e, { weekday: "long" }).format(r);
635
- }
636
- /**
637
- * Get month name
638
- */
639
- static getMonthName(t, e = "long", r = "en-US") {
640
- const i = new Date(2024, t, 1);
641
- return new Intl.DateTimeFormat(r, { month: e }).format(i);
642
- }
643
- /**
644
- * Parse time string (e.g., "14:30" or "2:30 PM")
645
- */
646
- static parseTimeString(t, e = /* @__PURE__ */ new Date()) {
647
- const r = new Date(e), [i, s] = t.split(/\s+/), [n, a] = i.split(":").map(Number);
648
- let o = n;
649
- return s && (s.toLowerCase() === "pm" && n < 12 ? o = n + 12 : s.toLowerCase() === "am" && n === 12 && (o = 0)), r.setHours(o, a || 0, 0, 0), r;
650
- }
651
- }
652
- class g {
653
- /**
654
- * Create element with attributes and children
655
- */
656
- static createElement(t, e = {}, r = []) {
657
- const i = document.createElement(t);
658
- return Object.entries(e).forEach(([s, n]) => {
659
- if (s === "className")
660
- i.className = n;
661
- else if (s === "style" && typeof n == "object")
662
- Object.assign(i.style, n);
663
- else if (s.startsWith("data-"))
664
- i.setAttribute(s, n);
665
- else if (s.startsWith("on") && typeof n == "function") {
666
- const a = s.slice(2).toLowerCase();
667
- i.addEventListener(a, n);
668
- } else
669
- i[s] = n;
670
- }), r.forEach((s) => {
671
- typeof s == "string" ? i.appendChild(document.createTextNode(s)) : s instanceof Node && i.appendChild(s);
672
- }), i;
673
- }
674
- /**
675
- * Add multiple event listeners
676
- */
677
- static addEventListeners(t, e) {
678
- return Object.entries(e).forEach(([r, i]) => {
679
- t.addEventListener(r, i);
680
- }), () => {
681
- Object.entries(e).forEach(([r, i]) => {
682
- t.removeEventListener(r, i);
683
- });
684
- };
685
- }
686
- /**
687
- * Delegate event handling
688
- */
689
- static delegate(t, e, r, i) {
690
- const s = (n) => {
691
- const a = n.target.closest(e);
692
- a && t.contains(a) && i.call(a, n);
693
- };
694
- return t.addEventListener(r, s), () => t.removeEventListener(r, s);
695
- }
696
- /**
697
- * Get element position relative to viewport
698
- */
699
- static getPosition(t) {
700
- const e = t.getBoundingClientRect();
701
- return {
702
- top: e.top + window.scrollY,
703
- left: e.left + window.scrollX,
704
- bottom: e.bottom + window.scrollY,
705
- right: e.right + window.scrollX,
706
- width: e.width,
707
- height: e.height
708
- };
709
- }
710
- /**
711
- * Check if element is in viewport
712
- */
713
- static isInViewport(t, e = 0) {
714
- const r = t.getBoundingClientRect();
715
- return r.top >= -e && r.left >= -e && r.bottom <= (window.innerHeight || document.documentElement.clientHeight) + e && r.right <= (window.innerWidth || document.documentElement.clientWidth) + e;
716
- }
717
- /**
718
- * Smooth scroll to element
719
- */
720
- static scrollToElement(t, e = {}) {
721
- const { behavior: r = "smooth", block: i = "start", inline: s = "nearest" } = e;
722
- t.scrollIntoView({ behavior: r, block: i, inline: s });
723
- }
724
- /**
725
- * Get computed style value
726
- */
727
- static getStyle(t, e) {
728
- return window.getComputedStyle(t).getPropertyValue(e);
729
- }
730
- /**
731
- * Set multiple styles
732
- */
733
- static setStyles(t, e) {
734
- Object.assign(t.style, e);
735
- }
736
- /**
737
- * Add/remove classes with animation support
738
- */
739
- static async animateClass(t, e, r = 300) {
740
- t.classList.add(e), await this.wait(r), t.classList.remove(e);
741
- }
742
- /**
743
- * Wait for animation/transition to complete
744
- * Resolves after `timeout` ms even if the event never fires (animation
745
- * cancelled, element removed from DOM, etc.) so awaiting callers can't hang.
746
- * @param {Element} element - Element to listen on
747
- * @param {string} [eventType='animationend'] - Event to wait for
748
- * @param {number} [timeout=3000] - Max wait in ms; 0 disables the timeout
749
- */
750
- static waitForAnimation(t, e = "animationend", r = 3e3) {
751
- return new Promise((i) => {
752
- let s = null;
753
- const n = () => {
754
- s !== null && clearTimeout(s), t.removeEventListener(e, n), i();
755
- };
756
- t.addEventListener(e, n), r > 0 && (s = setTimeout(n, r));
757
- });
758
- }
759
- /**
760
- * Utility wait function
761
- */
762
- static wait(t) {
763
- return new Promise((e) => setTimeout(e, t));
764
- }
765
- /**
766
- * Parse HTML string safely
767
- * Sanitizes by default: strips script-capable elements, inline event
768
- * handlers and javascript: URLs so the returned node is inert even if the
769
- * input contains an XSS payload. Pass { sanitize: false } only for trusted,
770
- * non-user-controlled markup.
771
- */
772
- static parseHTML(t, { sanitize: e = !0 } = {}) {
773
- const r = document.createElement("template");
774
- return r.innerHTML = t.trim(), e && this._sanitizeNode(r.content), r.content.firstChild;
775
- }
776
- /**
777
- * Remove script-capable elements, on* handlers and javascript: URLs
778
- * from a parsed DOM fragment (in place)
779
- */
780
- static _sanitizeNode(t) {
781
- t.querySelectorAll("script, iframe, object, embed, link, meta, base").forEach((s) => s.remove());
782
- const r = document.createTreeWalker(t, NodeFilter.SHOW_ELEMENT);
783
- let i = r.nextNode();
784
- for (; i; ) {
785
- for (const s of Array.from(i.attributes)) {
786
- const n = s.name.toLowerCase(), a = s.value.replace(/[\u0000-\u0020]/g, "").toLowerCase();
787
- (n.startsWith("on") || a.startsWith("javascript:") || a.startsWith("data:text/html")) && i.removeAttribute(s.name);
788
- }
789
- i = r.nextNode();
790
- }
791
- }
792
- /**
793
- * Escape HTML to prevent XSS
794
- */
795
- static escapeHTML(t) {
796
- const e = document.createElement("div");
797
- return e.textContent = t, e.innerHTML;
798
- }
799
- /**
800
- * Debounce function calls
801
- */
802
- static debounce(t, e = 250) {
803
- let r;
804
- return function(...s) {
805
- clearTimeout(r), r = setTimeout(() => t.apply(this, s), e);
806
- };
807
- }
808
- /**
809
- * Throttle function calls
810
- */
811
- static throttle(t, e = 250) {
812
- let r;
813
- return function(...i) {
814
- r || (t.apply(this, i), r = !0, setTimeout(() => r = !1, e));
815
- };
816
- }
817
- /**
818
- * Get closest parent matching selector
819
- */
820
- static closest(t, e) {
821
- return t.closest(e);
822
- }
823
- /**
824
- * Get all parents matching selector
825
- */
826
- static parents(t, e) {
827
- const r = [];
828
- let i = t.parentElement;
829
- for (; i; )
830
- i.matches(e) && r.push(i), i = i.parentElement;
831
- return r;
832
- }
833
- /**
834
- * Measure element dimensions including margins
835
- */
836
- static getOuterDimensions(t) {
837
- const e = window.getComputedStyle(t), r = {
838
- top: parseInt(e.marginTop),
839
- right: parseInt(e.marginRight),
840
- bottom: parseInt(e.marginBottom),
841
- left: parseInt(e.marginLeft)
842
- };
843
- return {
844
- width: t.offsetWidth + r.left + r.right,
845
- height: t.offsetHeight + r.top + r.bottom,
846
- margin: r
847
- };
848
- }
849
- /**
850
- * Clone an element. Event listeners are NOT copied — the Web Platform
851
- * provides no way to enumerate listeners, so callers must re-attach their
852
- * own handlers to the clone.
853
- * @deprecated Use element.cloneNode(deep) directly and re-bind listeners.
854
- */
855
- static cloneWithEvents(t, e = !0) {
856
- return t.cloneNode(e);
857
- }
858
- /**
859
- * Focus trap for modals/dialogs
860
- */
861
- static trapFocus(t) {
862
- const e = t.querySelectorAll(
863
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
864
- );
865
- if (e.length === 0)
866
- return t.setAttribute("tabindex", "-1"), t.focus(), () => t.removeAttribute("tabindex");
867
- const r = e[0], i = e[e.length - 1], s = (n) => {
868
- if (n.key !== "Tab") return;
869
- const o = t.getRootNode().activeElement ?? document.activeElement;
870
- n.shiftKey ? o === r && (i == null || i.focus(), n.preventDefault()) : o === i && (r == null || r.focus(), n.preventDefault());
871
- };
872
- return t.addEventListener("keydown", s), r == null || r.focus(), () => t.removeEventListener("keydown", s);
873
- }
874
- }
875
- class f {
876
- /**
877
- * Get CSS variable value
878
- */
879
- static getCSSVariable(t, e = document.documentElement) {
880
- return getComputedStyle(e).getPropertyValue(t).trim();
881
- }
882
- /**
883
- * Set CSS variables
884
- */
885
- static setCSSVariables(t, e = document.documentElement) {
886
- Object.entries(t).forEach(([r, i]) => {
887
- e.style.setProperty(r, i);
888
- });
889
- }
890
- /**
891
- * Generate base styles
892
- */
893
- static getBaseStyles() {
894
- return `
1
+ import { Calendar as e, DateUtils as t } from "@forcecalendar/core";
2
+ //#region src/core/BaseComponent.js
3
+ var n = class extends HTMLElement {
4
+ constructor() {
5
+ super(), this.attachShadow({ mode: "open" }), this._listeners = [], this._state = null, this._props = /* @__PURE__ */ new Map(), this._initialized = !1;
6
+ }
7
+ connectedCallback() {
8
+ this._initialized ||= (this.initialize(), !0), this.mount();
9
+ }
10
+ disconnectedCallback() {
11
+ this.unmount(), this.cleanup(), this._styleEl = null, this._contentWrapper = null;
12
+ }
13
+ initialize() {}
14
+ mount() {
15
+ this.render();
16
+ }
17
+ unmount() {}
18
+ cleanup() {
19
+ this._listeners.forEach(({ element: e, event: t, handler: n }) => {
20
+ e.removeEventListener(t, n);
21
+ }), this._listeners = [];
22
+ }
23
+ setState(e) {
24
+ let t = this._state;
25
+ this._state = {
26
+ ...this._state,
27
+ ...e
28
+ }, this.stateChanged(t, this._state), this.render();
29
+ }
30
+ getState() {
31
+ return this._state;
32
+ }
33
+ stateChanged(e, t) {}
34
+ setProp(e, t) {
35
+ let n = this._props.get(e);
36
+ this._props.set(e, t), this.propChanged(e, n, t);
37
+ }
38
+ getProp(e) {
39
+ return this._props.get(e);
40
+ }
41
+ propChanged(e, t, n) {}
42
+ addListener(e, t, n) {
43
+ if (!e || !t || !n) {
44
+ console.warn("addListener called with invalid parameters", {
45
+ element: e,
46
+ event: t,
47
+ handler: n
48
+ });
49
+ return;
50
+ }
51
+ let r = n.bind(this);
52
+ e.addEventListener(t, r), this._listeners.push({
53
+ element: e,
54
+ event: t,
55
+ handler: r
56
+ });
57
+ }
58
+ emit(e, t = {}) {
59
+ this.dispatchEvent(new CustomEvent(e, {
60
+ detail: t,
61
+ bubbles: !0,
62
+ composed: !0
63
+ }));
64
+ }
65
+ getStyles() {
66
+ return "";
67
+ }
68
+ getBaseStyles() {
69
+ return "\n :host {\n display: block;\n box-sizing: border-box;\n }\n\n *, *::before, *::after {\n box-sizing: inherit;\n }\n ";
70
+ }
71
+ render() {
72
+ this.cleanup();
73
+ let e = this.template();
74
+ this._styleEl || (this._styleEl = document.createElement("style"), this.shadowRoot.appendChild(this._styleEl), this._contentWrapper = document.createElement("div"), this._contentWrapper.setAttribute("id", "fc-root"), this._contentWrapper.style.display = "contents", this.shadowRoot.appendChild(this._contentWrapper)), this._styleEl.textContent = this.getBaseStyles() + "\n" + this.getStyles();
75
+ let t = this._saveScrollPositions(), n = this._getActiveElementSelector();
76
+ this._contentWrapper.innerHTML = e, this._restoreScrollPositions(t), this._restoreFocus(n), this.afterRender();
77
+ }
78
+ _saveScrollPositions() {
79
+ let e = /* @__PURE__ */ new Map();
80
+ return this._contentWrapper && this._contentWrapper.querySelectorAll("[id]").forEach((t) => {
81
+ (t.scrollTop !== 0 || t.scrollLeft !== 0) && e.set(t.id, {
82
+ top: t.scrollTop,
83
+ left: t.scrollLeft
84
+ });
85
+ }), e;
86
+ }
87
+ _restoreScrollPositions(e) {
88
+ if (!this._contentWrapper || e.size === 0) return;
89
+ let t = typeof CSS < "u" && typeof CSS.escape == "function" ? CSS.escape : (e) => String(e).replace(/["\\]/g, "\\$&");
90
+ e.forEach((e, n) => {
91
+ let r = this._contentWrapper.querySelector(`[id="${t(n)}"]`);
92
+ r && (r.scrollTop = e.top, r.scrollLeft = e.left);
93
+ });
94
+ }
95
+ _getActiveElementSelector() {
96
+ let e = this.shadowRoot.activeElement;
97
+ return !e || e === this._contentWrapper ? null : e.id ? `[id="${CSS.escape(e.id)}"]` : e.dataset && e.dataset.action ? `[data-action="${CSS.escape(e.dataset.action)}"]` : e.dataset && e.dataset.view ? `[data-view="${CSS.escape(e.dataset.view)}"]` : e.tagName ? e.tagName.toLowerCase() : null;
98
+ }
99
+ _restoreFocus(e) {
100
+ if (!(!e || !this._contentWrapper)) try {
101
+ let t = this._contentWrapper.querySelector(e);
102
+ t && typeof t.focus == "function" && t.focus();
103
+ } catch {}
104
+ }
105
+ template() {
106
+ return "";
107
+ }
108
+ afterRender() {}
109
+ $(e) {
110
+ return this.shadowRoot.querySelector(e);
111
+ }
112
+ $$(e) {
113
+ return this.shadowRoot.querySelectorAll(e);
114
+ }
115
+ static get observedAttributes() {
116
+ return [];
117
+ }
118
+ attributeChangedCallback(e, t, n) {
119
+ this.setProp(e, n), this._initialized && this.render();
120
+ }
121
+ }, r = class {
122
+ constructor() {
123
+ this.events = /* @__PURE__ */ new Map(), this.wildcardHandlers = /* @__PURE__ */ new Set();
124
+ }
125
+ on(e, t, n = {}) {
126
+ let { once: r = !1, priority: i = 0 } = n;
127
+ if (e.includes("*")) {
128
+ let n = {
129
+ pattern: e,
130
+ handler: t,
131
+ once: r,
132
+ priority: i
133
+ };
134
+ return this.wildcardHandlers.add(n), () => this.wildcardHandlers.delete(n);
135
+ }
136
+ this.events.has(e) || this.events.set(e, []);
137
+ let a = {
138
+ handler: t,
139
+ once: r,
140
+ priority: i
141
+ }, o = this.events.get(e);
142
+ return o.push(a), o.sort((e, t) => t.priority - e.priority), () => {
143
+ let e = o.indexOf(a);
144
+ e > -1 && o.splice(e, 1);
145
+ };
146
+ }
147
+ once(e, t, n = {}) {
148
+ return this.on(e, t, {
149
+ ...n,
150
+ once: !0
151
+ });
152
+ }
153
+ off(e, t) {
154
+ if (e.includes("*")) {
155
+ for (let n of this.wildcardHandlers) if (n.pattern === e && n.handler === t) {
156
+ this.wildcardHandlers.delete(n);
157
+ return;
158
+ }
159
+ return;
160
+ }
161
+ if (!this.events.has(e)) return;
162
+ let n = this.events.get(e), r = n.findIndex((e) => e.handler === t);
163
+ r > -1 && n.splice(r, 1), n.length === 0 && this.events.delete(e);
164
+ }
165
+ offWildcard(e) {
166
+ for (let t of [...this.wildcardHandlers]) t.pattern === e && this.wildcardHandlers.delete(t);
167
+ }
168
+ offAll(e) {
169
+ for (let [t, n] of this.events) {
170
+ let r = n.findIndex((t) => t.handler === e);
171
+ r > -1 && n.splice(r, 1), n.length === 0 && this.events.delete(t);
172
+ }
173
+ for (let t of [...this.wildcardHandlers]) t.handler === e && this.wildcardHandlers.delete(t);
174
+ }
175
+ emit(e, t) {
176
+ if (this.events.has(e)) {
177
+ let n = [...this.events.get(e)];
178
+ for (let r of n) {
179
+ let { handler: n, once: i } = r;
180
+ i && this.off(e, n);
181
+ try {
182
+ n(t, e);
183
+ } catch (t) {
184
+ console.error(`Error in event handler for ${e}:`, t);
185
+ }
186
+ }
187
+ }
188
+ let n = [];
189
+ for (let r of [...this.wildcardHandlers]) if (this.matchesPattern(e, r.pattern)) {
190
+ let { handler: i, once: a } = r;
191
+ a && n.push(r);
192
+ try {
193
+ i(t, e);
194
+ } catch (t) {
195
+ console.error(`Error in wildcard handler for ${e}:`, t);
196
+ }
197
+ }
198
+ n.forEach((e) => this.wildcardHandlers.delete(e));
199
+ }
200
+ matchesPattern(e, t) {
201
+ let n = t.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
202
+ return RegExp("^" + n.replace(/\*/g, ".*") + "$").test(e);
203
+ }
204
+ clear() {
205
+ this.events.clear(), this.wildcardHandlers.clear();
206
+ }
207
+ getEventNames() {
208
+ return Array.from(this.events.keys());
209
+ }
210
+ getHandlerCount(e) {
211
+ return this.events.has(e) ? this.events.get(e).length : 0;
212
+ }
213
+ getWildcardHandlerCount() {
214
+ return this.wildcardHandlers.size;
215
+ }
216
+ getTotalHandlerCount() {
217
+ let e = this.wildcardHandlers.size;
218
+ for (let t of this.events.values()) e += t.length;
219
+ return e;
220
+ }
221
+ }, i = new r(), a = class {
222
+ constructor(t = {}) {
223
+ this.eventBus = new r(), this.calendar = new e({
224
+ view: t.view || "month",
225
+ date: t.date || /* @__PURE__ */ new Date(),
226
+ weekStartsOn: t.weekStartsOn ?? 0,
227
+ locale: t.locale || "en-US",
228
+ timeZone: t.timeZone || Intl.DateTimeFormat().resolvedOptions().timeZone,
229
+ ...t
230
+ }), this.state = {
231
+ view: this.calendar.getView(),
232
+ currentDate: this.calendar.getCurrentDate(),
233
+ events: [],
234
+ selectedEvent: null,
235
+ selectedDate: null,
236
+ loading: !1,
237
+ error: null,
238
+ config: { ...t }
239
+ }, this.subscribers = /* @__PURE__ */ new Set(), this.subscribe = this.subscribe.bind(this), this.unsubscribe = this.unsubscribe.bind(this), this.setState = this.setState.bind(this), this._syncEventsFromCore({ silent: !0 });
240
+ }
241
+ _syncEventsFromCore(e = {}) {
242
+ let { force: t = !1 } = e, n = this.calendar.getEvents() || [];
243
+ return (t || this.state.events.length !== n.length || !this._eventsMatch(this.state.events, n)) && this.setState({ events: [...n] }, e), n;
244
+ }
245
+ _eventsMatch(e, t) {
246
+ if (e.length !== t.length) return !1;
247
+ let n = new Set(e.map((e) => e.id));
248
+ return t.every((e) => n.has(e.id));
249
+ }
250
+ getState() {
251
+ return {
252
+ ...this.state,
253
+ config: { ...this.state.config },
254
+ events: [...this.state.events]
255
+ };
256
+ }
257
+ setState(e, t = {}) {
258
+ let { silent: n = !1 } = t, r = { ...this.state };
259
+ return this.state = {
260
+ ...this.state,
261
+ ...e
262
+ }, n || (this.notifySubscribers(r, this.state), this.emitStateChange(r, this.state)), this.state;
263
+ }
264
+ subscribe(e, t = null) {
265
+ return this.subscribers.add(e), t && (this._subscriberIds ||= /* @__PURE__ */ new Map(), this._subscriberIds.set(t, e)), () => this.unsubscribe(e, t);
266
+ }
267
+ unsubscribe(e, t = null) {
268
+ this.subscribers.delete(e), t && this._subscriberIds && this._subscriberIds.delete(t);
269
+ }
270
+ unsubscribeById(e) {
271
+ if (!this._subscriberIds) return !1;
272
+ let t = this._subscriberIds.get(e);
273
+ return t ? (this.subscribers.delete(t), this._subscriberIds.delete(e), !0) : !1;
274
+ }
275
+ getSubscriberCount() {
276
+ return this.subscribers.size;
277
+ }
278
+ notifySubscribers(e, t) {
279
+ this.subscribers.forEach((n) => {
280
+ try {
281
+ n(t, e);
282
+ } catch (e) {
283
+ console.error("Error in state subscriber:", e);
284
+ }
285
+ });
286
+ }
287
+ emitStateChange(e, t) {
288
+ let n = Object.keys(t).filter((n) => e[n] !== t[n]);
289
+ n.forEach((n) => {
290
+ this.eventBus.emit(`state:${n}:changed`, {
291
+ oldValue: e[n],
292
+ newValue: t[n],
293
+ state: t
294
+ });
295
+ }), n.length > 0 && this.eventBus.emit("state:changed", {
296
+ oldState: e,
297
+ newState: t,
298
+ changedKeys: n
299
+ });
300
+ }
301
+ setView(e) {
302
+ this.calendar.setView(e), this.setState({ view: e }), this.eventBus.emit("view:changed", { view: e });
303
+ }
304
+ getView() {
305
+ return this.state.view;
306
+ }
307
+ setDate(e) {
308
+ this.calendar.goToDate(e), this.setState({ currentDate: this.calendar.getCurrentDate() }), this.eventBus.emit("date:changed", { date: this.state.currentDate });
309
+ }
310
+ getCurrentDate() {
311
+ return this.state.currentDate;
312
+ }
313
+ next() {
314
+ this.calendar.next(), this.setState({ currentDate: this.calendar.getCurrentDate() }), this.eventBus.emit("navigation:next", { date: this.state.currentDate });
315
+ }
316
+ previous() {
317
+ this.calendar.previous(), this.setState({ currentDate: this.calendar.getCurrentDate() }), this.eventBus.emit("navigation:previous", { date: this.state.currentDate });
318
+ }
319
+ today() {
320
+ this.calendar.today(), this.setState({ currentDate: this.calendar.getCurrentDate() }), this.eventBus.emit("navigation:today", { date: this.state.currentDate });
321
+ }
322
+ goToDate(e) {
323
+ this.calendar.goToDate(e), this.setState({ currentDate: this.calendar.getCurrentDate() }), this.eventBus.emit("navigation:goto", { date: this.state.currentDate });
324
+ }
325
+ addEvent(e) {
326
+ let t = this.calendar.addEvent(e);
327
+ return t ? (this._syncEventsFromCore(), this.eventBus.emit("event:add", { event: t }), this.eventBus.emit("event:added", { event: t }), t) : (console.error("Failed to add event to calendar"), this.eventBus.emit("event:error", {
328
+ action: "add",
329
+ event: e,
330
+ error: "Failed to add event"
331
+ }), null);
332
+ }
333
+ updateEvent(e, t) {
334
+ this._syncEventsFromCore({ silent: !0 });
335
+ let n = this.calendar.updateEvent(e, t);
336
+ return n ? (this._syncEventsFromCore({ force: !0 }), this.eventBus.emit("event:update", { event: n }), this.eventBus.emit("event:updated", { event: n }), n) : (console.error(`Failed to update event: ${e}`), this.eventBus.emit("event:error", {
337
+ action: "update",
338
+ eventId: e,
339
+ updates: t,
340
+ error: "Event not found in calendar"
341
+ }), null);
342
+ }
343
+ deleteEvent(e) {
344
+ return this._syncEventsFromCore({ silent: !0 }), this.calendar.removeEvent(e) ? (this._syncEventsFromCore(), this.eventBus.emit("event:remove", { eventId: e }), this.eventBus.emit("event:deleted", { eventId: e }), !0) : (console.error(`Failed to delete event: ${e}`), this.eventBus.emit("event:error", {
345
+ action: "delete",
346
+ eventId: e,
347
+ error: "Event not found"
348
+ }), !1);
349
+ }
350
+ getEvents() {
351
+ return this.calendar.getEvents() || [];
352
+ }
353
+ syncEvents() {
354
+ return this._syncEventsFromCore();
355
+ }
356
+ getEventsForDate(e) {
357
+ return this.calendar.getEventsForDate(e);
358
+ }
359
+ getEventsInRange(e, t) {
360
+ return this.calendar.getEventsInRange(e, t);
361
+ }
362
+ getViewData() {
363
+ let e = this.calendar.getViewData();
364
+ return this.enrichViewData(e);
365
+ }
366
+ enrichViewData(e) {
367
+ let t = { ...e }, n = this.state.selectedDate?.toDateString();
368
+ if (t.weeks &&= t.weeks.map((e) => ({
369
+ ...e,
370
+ days: e.days.map((e) => {
371
+ let t = new Date(e.date);
372
+ return {
373
+ ...e,
374
+ isSelected: t.toDateString() === n,
375
+ events: e.events || this.getEventsForDate(t)
376
+ };
377
+ })
378
+ })), t.days &&= t.days.map((e) => {
379
+ let t = new Date(e.date);
380
+ return {
381
+ ...e,
382
+ isSelected: t.toDateString() === n,
383
+ events: e.events || this.getEventsForDate(t)
384
+ };
385
+ }), t.date && !t.days && !t.weeks) {
386
+ let e = new Date(t.date);
387
+ t.isSelected = e.toDateString() === n, t.events = t.events || this.getEventsForDate(e);
388
+ }
389
+ return t;
390
+ }
391
+ selectEvent(e) {
392
+ this.setState({ selectedEvent: e }), this.eventBus.emit("event:selected", { event: e });
393
+ }
394
+ selectEventById(e) {
395
+ let t = this.state.events.find((t) => t.id === e);
396
+ t && this.selectEvent(t);
397
+ }
398
+ deselectEvent() {
399
+ this.setState({ selectedEvent: null }), this.eventBus.emit("event:deselected", {});
400
+ }
401
+ selectDate(e) {
402
+ this.setState({ selectedDate: e }), this.eventBus.emit("date:selected", { date: e });
403
+ }
404
+ deselectDate() {
405
+ this.setState({ selectedDate: null }), this.eventBus.emit("date:deselected", {});
406
+ }
407
+ isToday(e) {
408
+ let t = /* @__PURE__ */ new Date();
409
+ return e.toDateString() === t.toDateString();
410
+ }
411
+ isSelectedDate(e) {
412
+ return this.state.selectedDate && e.toDateString() === this.state.selectedDate.toDateString();
413
+ }
414
+ isWeekend(e) {
415
+ let t = e.getDay();
416
+ return t === 0 || t === 6;
417
+ }
418
+ setLoading(e) {
419
+ this.setState({ loading: e });
420
+ }
421
+ setError(e) {
422
+ this.setState({ error: e }), e && this.eventBus.emit("error", { error: e });
423
+ }
424
+ clearError() {
425
+ this.setState({ error: null });
426
+ }
427
+ updateConfig(e) {
428
+ this.setState({ config: {
429
+ ...this.state.config,
430
+ ...e
431
+ } }), e.weekStartsOn !== void 0 && this.calendar.setWeekStartsOn(e.weekStartsOn), e.locale !== void 0 && this.calendar.setLocale(e.locale), e.timeZone !== void 0 && this.calendar.setTimezone(e.timeZone);
432
+ }
433
+ destroy() {
434
+ this.subscribers.clear(), this._subscriberIds &&= (this._subscriberIds.clear(), null), this.eventBus &&= (this.eventBus.clear(), null), this.state = null, this.calendar = null;
435
+ }
436
+ }, o = class extends t {
437
+ static formatDate(e, t = "default", n = "en-US") {
438
+ if (!e) return "";
439
+ let r = {
440
+ default: {
441
+ year: "numeric",
442
+ month: "long",
443
+ day: "numeric"
444
+ },
445
+ short: {
446
+ year: "numeric",
447
+ month: "short",
448
+ day: "numeric"
449
+ },
450
+ long: {
451
+ weekday: "long",
452
+ year: "numeric",
453
+ month: "long",
454
+ day: "numeric"
455
+ },
456
+ month: {
457
+ year: "numeric",
458
+ month: "long"
459
+ },
460
+ monthShort: {
461
+ year: "numeric",
462
+ month: "short"
463
+ },
464
+ day: {
465
+ weekday: "long",
466
+ day: "numeric"
467
+ },
468
+ dayShort: {
469
+ weekday: "short",
470
+ day: "numeric"
471
+ },
472
+ time: {
473
+ hour: "numeric",
474
+ minute: "2-digit"
475
+ },
476
+ timeShort: { hour: "numeric" },
477
+ datetime: {
478
+ year: "numeric",
479
+ month: "short",
480
+ day: "numeric",
481
+ hour: "numeric",
482
+ minute: "2-digit"
483
+ }
484
+ }, i = r[t] || r.default;
485
+ return new Intl.DateTimeFormat(n, i).format(e);
486
+ }
487
+ static formatTime(e, t = !0, n = !1, r = "en-US") {
488
+ if (!e) return "";
489
+ let i = {
490
+ hour: "numeric",
491
+ minute: t ? "2-digit" : void 0,
492
+ hour12: !n
493
+ };
494
+ return new Intl.DateTimeFormat(r, i).format(e);
495
+ }
496
+ static formatDateRange(e, t, n = "en-US") {
497
+ if (!e) return "";
498
+ if (!t || this.isSameDay(e, t)) return this.formatDate(e, "default", n);
499
+ let r = this.isSameYear(e, t) ? "short" : "default";
500
+ return `${this.formatDate(e, r, n)} - ${this.formatDate(t, "default", n)}`;
501
+ }
502
+ static formatTimeRange(e, t, n = "en-US") {
503
+ if (!e) return "";
504
+ let r = this.formatTime(e, !0, !1, n);
505
+ return t ? `${r} - ${this.formatTime(t, !0, !1, n)}` : r;
506
+ }
507
+ static getRelativeTime(e, t = /* @__PURE__ */ new Date(), n = "en-US") {
508
+ let r = new Intl.RelativeTimeFormat(n, { numeric: "auto" }), i = e - t, a = Math.floor(i / 1e3), o = Math.floor(a / 60), s = Math.floor(o / 60), c = Math.floor(s / 24), l = Math.floor(c / 7), u = Math.floor(c / 30), d = Math.floor(c / 365);
509
+ return Math.abs(a) < 60 ? r.format(a, "second") : Math.abs(o) < 60 ? r.format(o, "minute") : Math.abs(s) < 24 ? r.format(s, "hour") : Math.abs(c) < 7 ? r.format(c, "day") : Math.abs(l) < 4 ? r.format(l, "week") : Math.abs(u) < 12 ? r.format(u, "month") : r.format(d, "year");
510
+ }
511
+ static isToday(e) {
512
+ let t = /* @__PURE__ */ new Date();
513
+ return this.isSameDay(e, t);
514
+ }
515
+ static isPast(e) {
516
+ return e < /* @__PURE__ */ new Date();
517
+ }
518
+ static isFuture(e) {
519
+ return e > /* @__PURE__ */ new Date();
520
+ }
521
+ static getWeekNumber(e) {
522
+ let t = new Date(e.getFullYear(), 0, 1), n = (e - t) / 864e5;
523
+ return Math.ceil((n + t.getDay() + 1) / 7);
524
+ }
525
+ static getDayAbbreviation(e, t = "en-US") {
526
+ let n = new Date(2024, 0, 7 + e);
527
+ return new Intl.DateTimeFormat(t, { weekday: "short" }).format(n);
528
+ }
529
+ static getDayName(e, t = "en-US") {
530
+ let n = new Date(2024, 0, 7 + e);
531
+ return new Intl.DateTimeFormat(t, { weekday: "long" }).format(n);
532
+ }
533
+ static getMonthName(e, t = "long", n = "en-US") {
534
+ let r = new Date(2024, e, 1);
535
+ return new Intl.DateTimeFormat(n, { month: t }).format(r);
536
+ }
537
+ static parseTimeString(e, t = /* @__PURE__ */ new Date()) {
538
+ let n = new Date(t), [r, i] = e.split(/\s+/), [a, o] = r.split(":").map(Number), s = a;
539
+ return i && (i.toLowerCase() === "pm" && a < 12 ? s = a + 12 : i.toLowerCase() === "am" && a === 12 && (s = 0)), n.setHours(s, o || 0, 0, 0), n;
540
+ }
541
+ }, s = class {
542
+ static createElement(e, t = {}, n = []) {
543
+ let r = document.createElement(e);
544
+ return Object.entries(t).forEach(([e, t]) => {
545
+ if (e === "className") r.className = t;
546
+ else if (e === "style" && typeof t == "object") Object.assign(r.style, t);
547
+ else if (e.startsWith("data-")) r.setAttribute(e, t);
548
+ else if (e.startsWith("on") && typeof t == "function") {
549
+ let n = e.slice(2).toLowerCase();
550
+ r.addEventListener(n, t);
551
+ } else r[e] = t;
552
+ }), n.forEach((e) => {
553
+ typeof e == "string" ? r.appendChild(document.createTextNode(e)) : e instanceof Node && r.appendChild(e);
554
+ }), r;
555
+ }
556
+ static addEventListeners(e, t) {
557
+ return Object.entries(t).forEach(([t, n]) => {
558
+ e.addEventListener(t, n);
559
+ }), () => {
560
+ Object.entries(t).forEach(([t, n]) => {
561
+ e.removeEventListener(t, n);
562
+ });
563
+ };
564
+ }
565
+ static delegate(e, t, n, r) {
566
+ let i = (n) => {
567
+ let i = n.target.closest(t);
568
+ i && e.contains(i) && r.call(i, n);
569
+ };
570
+ return e.addEventListener(n, i), () => e.removeEventListener(n, i);
571
+ }
572
+ static getPosition(e) {
573
+ let t = e.getBoundingClientRect();
574
+ return {
575
+ top: t.top + window.scrollY,
576
+ left: t.left + window.scrollX,
577
+ bottom: t.bottom + window.scrollY,
578
+ right: t.right + window.scrollX,
579
+ width: t.width,
580
+ height: t.height
581
+ };
582
+ }
583
+ static isInViewport(e, t = 0) {
584
+ let n = e.getBoundingClientRect();
585
+ return n.top >= -t && n.left >= -t && n.bottom <= (window.innerHeight || document.documentElement.clientHeight) + t && n.right <= (window.innerWidth || document.documentElement.clientWidth) + t;
586
+ }
587
+ static scrollToElement(e, t = {}) {
588
+ let { behavior: n = "smooth", block: r = "start", inline: i = "nearest" } = t;
589
+ e.scrollIntoView({
590
+ behavior: n,
591
+ block: r,
592
+ inline: i
593
+ });
594
+ }
595
+ static getStyle(e, t) {
596
+ return window.getComputedStyle(e).getPropertyValue(t);
597
+ }
598
+ static setStyles(e, t) {
599
+ Object.assign(e.style, t);
600
+ }
601
+ static async animateClass(e, t, n = 300) {
602
+ e.classList.add(t), await this.wait(n), e.classList.remove(t);
603
+ }
604
+ static waitForAnimation(e, t = "animationend", n = 3e3) {
605
+ return new Promise((r) => {
606
+ let i = null, a = () => {
607
+ i !== null && clearTimeout(i), e.removeEventListener(t, a), r();
608
+ };
609
+ e.addEventListener(t, a), n > 0 && (i = setTimeout(a, n));
610
+ });
611
+ }
612
+ static wait(e) {
613
+ return new Promise((t) => setTimeout(t, e));
614
+ }
615
+ static parseHTML(e, { sanitize: t = !0 } = {}) {
616
+ let n = document.createElement("template");
617
+ return n.innerHTML = e.trim(), t && this._sanitizeNode(n.content), n.content.firstChild;
618
+ }
619
+ static _sanitizeNode(e) {
620
+ e.querySelectorAll("script, iframe, object, embed, link, meta, base").forEach((e) => e.remove());
621
+ let t = document.createTreeWalker(e, NodeFilter.SHOW_ELEMENT), n = t.nextNode();
622
+ for (; n;) {
623
+ for (let e of Array.from(n.attributes)) {
624
+ let t = e.name.toLowerCase(), r = e.value.replace(/[\u0000-\u0020]/g, "").toLowerCase();
625
+ (t.startsWith("on") || r.startsWith("javascript:") || r.startsWith("data:text/html")) && n.removeAttribute(e.name);
626
+ }
627
+ n = t.nextNode();
628
+ }
629
+ }
630
+ static escapeHTML(e) {
631
+ let t = document.createElement("div");
632
+ return t.textContent = e, t.innerHTML;
633
+ }
634
+ static debounce(e, t = 250) {
635
+ let n;
636
+ return function(...r) {
637
+ clearTimeout(n), n = setTimeout(() => e.apply(this, r), t);
638
+ };
639
+ }
640
+ static throttle(e, t = 250) {
641
+ let n;
642
+ return function(...r) {
643
+ n || (e.apply(this, r), n = !0, setTimeout(() => n = !1, t));
644
+ };
645
+ }
646
+ static closest(e, t) {
647
+ return e.closest(t);
648
+ }
649
+ static parents(e, t) {
650
+ let n = [], r = e.parentElement;
651
+ for (; r;) r.matches(t) && n.push(r), r = r.parentElement;
652
+ return n;
653
+ }
654
+ static getOuterDimensions(e) {
655
+ let t = window.getComputedStyle(e), n = {
656
+ top: parseInt(t.marginTop),
657
+ right: parseInt(t.marginRight),
658
+ bottom: parseInt(t.marginBottom),
659
+ left: parseInt(t.marginLeft)
660
+ };
661
+ return {
662
+ width: e.offsetWidth + n.left + n.right,
663
+ height: e.offsetHeight + n.top + n.bottom,
664
+ margin: n
665
+ };
666
+ }
667
+ static cloneWithEvents(e, t = !0) {
668
+ return e.cloneNode(t);
669
+ }
670
+ static trapFocus(e) {
671
+ let t = e.querySelectorAll("button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])");
672
+ if (t.length === 0) return e.setAttribute("tabindex", "-1"), e.focus(), () => e.removeAttribute("tabindex");
673
+ let n = t[0], r = t[t.length - 1], i = (t) => {
674
+ if (t.key !== "Tab") return;
675
+ let i = e.getRootNode().activeElement ?? document.activeElement;
676
+ t.shiftKey ? i === n && (r?.focus(), t.preventDefault()) : i === r && (n?.focus(), t.preventDefault());
677
+ };
678
+ return e.addEventListener("keydown", i), n?.focus(), () => e.removeEventListener("keydown", i);
679
+ }
680
+ }, c = class {
681
+ static colors = {
682
+ primary: "#3B82F6",
683
+ secondary: "#64748B",
684
+ accent: "#F59E0B",
685
+ danger: "#EF4444",
686
+ warning: "#F97316",
687
+ info: "#06B6D4",
688
+ success: "#22C55E",
689
+ light: "#F8FAFC",
690
+ dark: "#0F172A",
691
+ white: "#FFFFFF",
692
+ gray: {
693
+ 50: "#F8FAFC",
694
+ 100: "#F1F5F9",
695
+ 200: "#E2E8F0",
696
+ 300: "#CBD5E1",
697
+ 400: "#94A3B8",
698
+ 500: "#64748B",
699
+ 600: "#475569",
700
+ 700: "#334155",
701
+ 800: "#1E293B",
702
+ 900: "#0F172A"
703
+ }
704
+ };
705
+ static cssVariables = {
706
+ "--fc-primary-color": "#2563EB",
707
+ "--fc-primary-hover": "#1D4ED8",
708
+ "--fc-primary-light": "#EFF6FF",
709
+ "--fc-text-color": "#111827",
710
+ "--fc-text-secondary": "#6B7280",
711
+ "--fc-text-light": "#9CA3AF",
712
+ "--fc-border-color": "#E5E7EB",
713
+ "--fc-border-color-hover": "#D1D5DB",
714
+ "--fc-background": "#FFFFFF",
715
+ "--fc-background-alt": "#FAFAFA",
716
+ "--fc-background-hover": "#F3F4F6",
717
+ "--fc-background-active": "#E5E7EB",
718
+ "--fc-accent-color": "#F59E0B",
719
+ "--fc-danger-color": "#EF4444",
720
+ "--fc-success-color": "#10B981",
721
+ "--fc-font-family": "Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif",
722
+ "--fc-font-size-xs": "11px",
723
+ "--fc-font-size-sm": "12px",
724
+ "--fc-font-size-base": "13px",
725
+ "--fc-font-size-lg": "15px",
726
+ "--fc-font-size-xl": "18px",
727
+ "--fc-font-size-2xl": "24px",
728
+ "--fc-line-height": "1.4",
729
+ "--fc-font-weight-normal": "400",
730
+ "--fc-font-weight-medium": "500",
731
+ "--fc-font-weight-semibold": "600",
732
+ "--fc-font-weight-bold": "700",
733
+ "--fc-spacing-xs": "2px",
734
+ "--fc-spacing-sm": "6px",
735
+ "--fc-spacing-md": "10px",
736
+ "--fc-spacing-lg": "14px",
737
+ "--fc-spacing-xl": "20px",
738
+ "--fc-spacing-2xl": "28px",
739
+ "--fc-border-width": "1px",
740
+ "--fc-border-radius-sm": "3px",
741
+ "--fc-border-radius": "5px",
742
+ "--fc-border-radius-lg": "8px",
743
+ "--fc-border-radius-full": "9999px",
744
+ "--fc-shadow-sm": "0 1px 1px rgba(0,0,0,0.05)",
745
+ "--fc-shadow": "0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06)",
746
+ "--fc-shadow-md": "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
747
+ "--fc-shadow-lg": "0 10px 15px -3px rgba(0, 0, 0, 0.1)",
748
+ "--fc-transition-fast": "100ms ease-out",
749
+ "--fc-transition": "150ms ease-out",
750
+ "--fc-transition-slow": "250ms ease-out",
751
+ "--fc-z-dropdown": "1000",
752
+ "--fc-z-modal": "2000",
753
+ "--fc-z-tooltip": "3000"
754
+ };
755
+ static getCSSVariable(e, t = document.documentElement) {
756
+ return getComputedStyle(t).getPropertyValue(e).trim();
757
+ }
758
+ static setCSSVariables(e, t = document.documentElement) {
759
+ Object.entries(e).forEach(([e, n]) => {
760
+ t.style.setProperty(e, n);
761
+ });
762
+ }
763
+ static getBaseStyles() {
764
+ return `
895
765
  :host {
896
766
  /* Apply CSS variables */
897
- ${Object.entries(this.cssVariables).map(([t, e]) => `${t}: ${e};`).join(`
898
- `)}
767
+ ${Object.entries(this.cssVariables).map(([e, t]) => `${e}: ${t};`).join("\n ")}
899
768
 
900
769
  /* Base styles */
901
770
  display: block;
@@ -944,697 +813,395 @@ class f {
944
813
  outline-offset: 2px;
945
814
  }
946
815
  `;
947
- }
948
- /**
949
- * Generate button styles
950
- */
951
- static getButtonStyles() {
952
- return `
953
- .fc-btn {
954
- display: inline-flex;
955
- align-items: center;
956
- justify-content: center;
957
- padding: 8px 16px;
958
- font-size: var(--fc-font-size-sm);
959
- font-weight: var(--fc-font-weight-medium);
960
- line-height: 1.25rem;
961
- border-radius: var(--fc-border-radius);
962
- border: 1px solid transparent;
963
- cursor: pointer;
964
- transition: all var(--fc-transition-fast);
965
- outline: none;
966
- user-select: none;
967
- gap: var(--fc-spacing-sm);
968
- white-space: nowrap;
969
- }
970
-
971
- .fc-btn:disabled {
972
- opacity: 0.5;
973
- cursor: not-allowed;
974
- }
975
-
976
- .fc-btn-primary {
977
- background: var(--fc-primary-color);
978
- color: white;
979
- box-shadow: var(--fc-shadow-sm);
980
- }
981
-
982
- .fc-btn-primary:hover:not(:disabled) {
983
- background: var(--fc-primary-hover);
984
- box-shadow: var(--fc-shadow);
985
- }
986
-
987
- .fc-btn-secondary {
988
- background: white;
989
- border-color: var(--fc-border-color);
990
- color: var(--fc-text-color);
991
- box-shadow: var(--fc-shadow-sm);
992
- }
993
-
994
- .fc-btn-secondary:hover:not(:disabled) {
995
- background: var(--fc-background-hover);
996
- border-color: var(--fc-border-color-hover);
997
- }
998
-
999
- .fc-btn-outline {
1000
- background: transparent;
1001
- border-color: var(--fc-border-color);
1002
- color: var(--fc-text-secondary);
1003
- }
1004
-
1005
- .fc-btn-outline:hover:not(:disabled) {
1006
- background: var(--fc-background-hover);
1007
- color: var(--fc-text-color);
1008
- border-color: var(--fc-border-color-hover);
1009
- }
1010
-
1011
- .fc-btn-ghost {
1012
- background: transparent;
1013
- color: var(--fc-text-secondary);
1014
- }
1015
-
1016
- .fc-btn-ghost:hover:not(:disabled) {
1017
- background: var(--fc-background-hover);
1018
- color: var(--fc-text-color);
1019
- }
1020
-
1021
- .fc-btn-sm {
1022
- padding: 6px 12px;
1023
- font-size: var(--fc-font-size-xs);
1024
- }
1025
-
1026
- .fc-btn-lg {
1027
- padding: 10px 20px;
1028
- font-size: var(--fc-font-size-base);
1029
- }
1030
-
1031
- .fc-btn-icon {
1032
- width: 32px;
1033
- height: 32px;
1034
- padding: 0;
1035
- border-radius: var(--fc-border-radius-full);
1036
- }
1037
- `;
1038
- }
1039
- /**
1040
- * Darken color by percentage
1041
- */
1042
- static darken(t, e) {
1043
- const r = parseInt(t.replace("#", ""), 16), i = Math.round(2.55 * e), s = (r >> 16) - i, n = (r >> 8 & 255) - i, a = (r & 255) - i;
1044
- return "#" + (16777216 + (s < 255 ? s < 1 ? 0 : s : 255) * 65536 + (n < 255 ? n < 1 ? 0 : n : 255) * 256 + (a < 255 ? a < 1 ? 0 : a : 255)).toString(16).slice(1);
1045
- }
1046
- /**
1047
- * Lighten color by percentage
1048
- */
1049
- static lighten(t, e) {
1050
- const r = parseInt(t.replace("#", ""), 16), i = Math.round(2.55 * e), s = (r >> 16) + i, n = (r >> 8 & 255) + i, a = (r & 255) + i;
1051
- return "#" + (16777216 + (s < 255 ? s < 1 ? 0 : s : 255) * 65536 + (n < 255 ? n < 1 ? 0 : n : 255) * 256 + (a < 255 ? a < 1 ? 0 : a : 255)).toString(16).slice(1);
1052
- }
1053
- /**
1054
- * Get contrast color (black or white) for background
1055
- */
1056
- static getContrastColor(t) {
1057
- const e = t.replace("#", ""), r = parseInt(e.substring(0, 2), 16), i = parseInt(e.substring(2, 4), 16), s = parseInt(e.substring(4, 6), 16);
1058
- return (r * 299 + i * 587 + s * 114) / 1e3 >= 128 ? "#000000" : "#FFFFFF";
1059
- }
1060
- /**
1061
- * Sanitize color value to prevent CSS injection
1062
- * Returns the color if valid, or a fallback color if invalid
1063
- */
1064
- static sanitizeColor(t, e = "var(--fc-primary-color)") {
1065
- if (!t || typeof t != "string")
1066
- return e;
1067
- const r = t.trim();
1068
- return /[;{}()<>\"\'\\]/.test(r) ? e : /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(r) || /^var\(--[a-zA-Z0-9-]+\)$/.test(r) || /^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(,\s*(0|1|0?\.\d+))?\s*\)$/.test(r) || [
1069
- "transparent",
1070
- "currentColor",
1071
- "inherit",
1072
- "black",
1073
- "white",
1074
- "red",
1075
- "green",
1076
- "blue",
1077
- "yellow",
1078
- "orange",
1079
- "purple",
1080
- "pink",
1081
- "brown",
1082
- "gray",
1083
- "grey",
1084
- "cyan",
1085
- "magenta",
1086
- "lime",
1087
- "navy",
1088
- "teal",
1089
- "aqua",
1090
- "maroon",
1091
- "olive",
1092
- "silver",
1093
- "fuchsia"
1094
- ].includes(r.toLowerCase()) ? r : e;
1095
- }
1096
- /**
1097
- * Convert hex to rgba
1098
- */
1099
- static hexToRgba(t, e = 1) {
1100
- const r = t.replace("#", ""), i = parseInt(r.substring(0, 2), 16), s = parseInt(r.substring(2, 4), 16), n = parseInt(r.substring(4, 6), 16);
1101
- return `rgba(${i}, ${s}, ${n}, ${e})`;
1102
- }
1103
- /**
1104
- * Generate grid styles
1105
- */
1106
- static getGridStyles() {
1107
- return `
1108
- .fc-grid {
1109
- display: grid;
1110
- gap: 1px;
1111
- background: var(--fc-border-color);
1112
- border: 1px solid var(--fc-border-color);
1113
- border-radius: var(--fc-border-radius);
1114
- overflow: hidden;
1115
- }
1116
-
1117
- .fc-grid-cell {
1118
- background: var(--fc-background);
1119
- padding: var(--fc-spacing-sm);
1120
- min-height: 100px;
1121
- position: relative;
1122
- }
1123
-
1124
- .fc-grid-cell:hover {
1125
- background: var(--fc-background-hover);
1126
- }
1127
-
1128
- .fc-grid-header {
1129
- background: var(--fc-background-alt);
1130
- padding: var(--fc-spacing-sm);
1131
- font-weight: var(--fc-font-weight-semibold);
1132
- text-align: center;
1133
- color: var(--fc-text-secondary);
1134
- font-size: var(--fc-font-size-xs);
1135
- text-transform: uppercase;
1136
- letter-spacing: 0.5px;
1137
- }
1138
- `;
1139
- }
1140
- /**
1141
- * Generate media query
1142
- */
1143
- static mediaQuery(t, e) {
1144
- const r = this.breakpoints[t];
1145
- return r ? `@media (min-width: ${r}) { ${e} }` : "";
1146
- }
1147
- /**
1148
- * Animation keyframes
1149
- */
1150
- static getAnimations() {
1151
- return `
1152
- @keyframes fc-fade-in {
1153
- from { opacity: 0; }
1154
- to { opacity: 1; }
1155
- }
1156
-
1157
- @keyframes fc-slide-in-up {
1158
- from {
1159
- opacity: 0;
1160
- transform: translateY(10px);
1161
- }
1162
- to {
1163
- opacity: 1;
1164
- transform: translateY(0);
1165
- }
1166
- }
1167
-
1168
- @keyframes fc-slide-in-down {
1169
- from {
1170
- opacity: 0;
1171
- transform: translateY(-10px);
1172
- }
1173
- to {
1174
- opacity: 1;
1175
- transform: translateY(0);
1176
- }
1177
- }
1178
-
1179
- @keyframes fc-scale-in {
1180
- from {
1181
- opacity: 0;
1182
- transform: scale(0.95);
1183
- }
1184
- to {
1185
- opacity: 1;
1186
- transform: scale(1);
1187
- }
1188
- }
1189
-
1190
- @keyframes fc-spin {
1191
- from { transform: rotate(0deg); }
1192
- to { transform: rotate(360deg); }
1193
- }
1194
-
1195
- .fc-fade-in {
1196
- animation: fc-fade-in var(--fc-transition);
1197
- }
1198
-
1199
- .fc-slide-in-up {
1200
- animation: fc-slide-in-up var(--fc-transition);
1201
- }
1202
-
1203
- .fc-scale-in {
1204
- animation: fc-scale-in var(--fc-transition);
1205
- }
1206
- `;
1207
- }
1208
- }
1209
- /**
1210
- * Default theme colors
1211
- */
1212
- m(f, "colors", {
1213
- primary: "#3B82F6",
1214
- // Modern Blue
1215
- secondary: "#64748B",
1216
- // Slate
1217
- accent: "#F59E0B",
1218
- // Amber
1219
- danger: "#EF4444",
1220
- // Red
1221
- warning: "#F97316",
1222
- // Orange
1223
- info: "#06B6D4",
1224
- // Cyan
1225
- success: "#22C55E",
1226
- // Green
1227
- light: "#F8FAFC",
1228
- dark: "#0F172A",
1229
- white: "#FFFFFF",
1230
- gray: {
1231
- 50: "#F8FAFC",
1232
- 100: "#F1F5F9",
1233
- 200: "#E2E8F0",
1234
- 300: "#CBD5E1",
1235
- 400: "#94A3B8",
1236
- 500: "#64748B",
1237
- 600: "#475569",
1238
- 700: "#334155",
1239
- 800: "#1E293B",
1240
- 900: "#0F172A"
1241
- }
1242
- }), /**
1243
- * Common CSS variables
1244
- */
1245
- m(f, "cssVariables", {
1246
- // "Pro" Palette - Functional & Sharp
1247
- "--fc-primary-color": "#2563EB",
1248
- // International Blue (Focus)
1249
- "--fc-primary-hover": "#1D4ED8",
1250
- "--fc-primary-light": "#EFF6FF",
1251
- // Neutral Scale (Slate/Gray for structure)
1252
- "--fc-text-color": "#111827",
1253
- // Almost Black
1254
- "--fc-text-secondary": "#6B7280",
1255
- // Cool Gray
1256
- "--fc-text-light": "#9CA3AF",
1257
- "--fc-border-color": "#E5E7EB",
1258
- // Crisp Light Gray
1259
- "--fc-border-color-hover": "#D1D5DB",
1260
- "--fc-background": "#FFFFFF",
1261
- "--fc-background-alt": "#FAFAFA",
1262
- // Very subtle off-white
1263
- "--fc-background-hover": "#F3F4F6",
1264
- "--fc-background-active": "#E5E7EB",
1265
- // Semantic Colors
1266
- "--fc-accent-color": "#F59E0B",
1267
- "--fc-danger-color": "#EF4444",
1268
- "--fc-success-color": "#10B981",
1269
- // Typography - optimized for UI density
1270
- "--fc-font-family": 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
1271
- "--fc-font-size-xs": "11px",
1272
- "--fc-font-size-sm": "12px",
1273
- "--fc-font-size-base": "13px",
1274
- // Slightly smaller for density
1275
- "--fc-font-size-lg": "15px",
1276
- "--fc-font-size-xl": "18px",
1277
- "--fc-font-size-2xl": "24px",
1278
- "--fc-line-height": "1.4",
1279
- "--fc-font-weight-normal": "400",
1280
- "--fc-font-weight-medium": "500",
1281
- "--fc-font-weight-semibold": "600",
1282
- "--fc-font-weight-bold": "700",
1283
- // Spacing - Tighter
1284
- "--fc-spacing-xs": "2px",
1285
- "--fc-spacing-sm": "6px",
1286
- "--fc-spacing-md": "10px",
1287
- "--fc-spacing-lg": "14px",
1288
- "--fc-spacing-xl": "20px",
1289
- "--fc-spacing-2xl": "28px",
1290
- // Border
1291
- "--fc-border-width": "1px",
1292
- "--fc-border-radius-sm": "3px",
1293
- // Micro rounding
1294
- "--fc-border-radius": "5px",
1295
- "--fc-border-radius-lg": "8px",
1296
- "--fc-border-radius-full": "9999px",
1297
- // Shadows - Minimal/Functional
1298
- "--fc-shadow-sm": "0 1px 1px rgba(0,0,0,0.05)",
1299
- "--fc-shadow": "0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06)",
1300
- "--fc-shadow-md": "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
1301
- "--fc-shadow-lg": "0 10px 15px -3px rgba(0, 0, 0, 0.1)",
1302
- // Transitions - Snappy
1303
- "--fc-transition-fast": "100ms ease-out",
1304
- "--fc-transition": "150ms ease-out",
1305
- "--fc-transition-slow": "250ms ease-out",
1306
- // Z-index
1307
- "--fc-z-dropdown": "1000",
1308
- "--fc-z-modal": "2000",
1309
- "--fc-z-tooltip": "3000"
1310
- }), /**
1311
- * Get responsive breakpoints
1312
- */
1313
- m(f, "breakpoints", {
1314
- xs: "320px",
1315
- sm: "576px",
1316
- md: "768px",
1317
- lg: "992px",
1318
- xl: "1200px",
1319
- "2xl": "1400px"
1320
- });
1321
- class D {
1322
- /**
1323
- * @param {HTMLElement} container - The DOM element to render into
1324
- * @param {StateManager} stateManager - The state manager instance
1325
- */
1326
- constructor(t, e) {
1327
- this.container = t, this.stateManager = e, this._listeners = [], this._scrolled = !1, this._nowIndicatorTimer = null;
1328
- }
1329
- /**
1330
- * Render the view into the container
1331
- * Must be implemented by subclasses
1332
- */
1333
- render() {
1334
- throw new Error("render() must be implemented by subclass");
1335
- }
1336
- /**
1337
- * Clean up event listeners
1338
- */
1339
- cleanup() {
1340
- this._listeners.forEach(({ element: t, event: e, handler: r }) => {
1341
- t.removeEventListener(e, r);
1342
- }), this._listeners = [], this._nowIndicatorTimer && (clearInterval(this._nowIndicatorTimer), this._nowIndicatorTimer = null);
1343
- }
1344
- /**
1345
- * Add an event listener with automatic cleanup tracking
1346
- * @param {HTMLElement} element
1347
- * @param {string} event
1348
- * @param {Function} handler
1349
- */
1350
- addListener(t, e, r) {
1351
- const i = r.bind(this);
1352
- t.addEventListener(e, i), this._listeners.push({ element: t, event: e, handler: i });
1353
- }
1354
- /**
1355
- * Escape HTML to prevent XSS
1356
- * @param {string} str
1357
- * @returns {string}
1358
- */
1359
- escapeHTML(t) {
1360
- return t == null ? "" : g.escapeHTML(String(t));
1361
- }
1362
- /**
1363
- * Check if a date is today
1364
- * @param {Date} date
1365
- * @returns {boolean}
1366
- */
1367
- isToday(t) {
1368
- const e = /* @__PURE__ */ new Date();
1369
- return t.getDate() === e.getDate() && t.getMonth() === e.getMonth() && t.getFullYear() === e.getFullYear();
1370
- }
1371
- /**
1372
- * Check if two dates are the same day
1373
- * @param {Date} date1
1374
- * @param {Date} date2
1375
- * @returns {boolean}
1376
- */
1377
- isSameDay(t, e) {
1378
- return t.getDate() === e.getDate() && t.getMonth() === e.getMonth() && t.getFullYear() === e.getFullYear();
1379
- }
1380
- /**
1381
- * Format hour for display (e.g., "9 AM", "2 PM")
1382
- * @param {number} hour - Hour in 24-hour format (0-23)
1383
- * @returns {string}
1384
- */
1385
- formatHour(t) {
1386
- const e = t >= 12 ? "PM" : "AM";
1387
- return `${t % 12 || 12} ${e}`;
1388
- }
1389
- /**
1390
- * Format time for display (e.g., "9 AM", "2:30 PM")
1391
- * @param {Date} date
1392
- * @returns {string}
1393
- */
1394
- formatTime(t) {
1395
- const e = t.getHours(), r = t.getMinutes(), i = e >= 12 ? "PM" : "AM", s = e % 12 || 12;
1396
- return r === 0 ? `${s} ${i}` : `${s}:${r.toString().padStart(2, "0")} ${i}`;
1397
- }
1398
- /**
1399
- * Get contrasting text color for a background color.
1400
- * Delegates to StyleUtils.getContrastColor() as the single implementation.
1401
- * @param {string} bgColor - Hex color string
1402
- * @returns {string} '#000000' or '#FFFFFF'
1403
- */
1404
- getContrastingTextColor(t) {
1405
- return !t || typeof t != "string" || t.charAt(0) !== "#" ? "white" : f.getContrastColor(t);
1406
- }
1407
- /**
1408
- * Render the "now" indicator line for time-based views
1409
- * @returns {string} HTML string
1410
- */
1411
- renderNowIndicator() {
1412
- const t = /* @__PURE__ */ new Date();
1413
- return `<div class="fc-now-indicator" style="position: absolute; left: 0; right: 0; top: ${t.getHours() * 60 + t.getMinutes()}px; height: 2px; background: var(--fc-danger-color); z-index: 15; pointer-events: none;"></div>`;
1414
- }
1415
- /**
1416
- * Start a timer that updates the now indicator position every 60 seconds.
1417
- * Call this after render() in day/week views that show a now indicator.
1418
- */
1419
- startNowIndicatorTimer() {
1420
- this._nowIndicatorTimer && clearInterval(this._nowIndicatorTimer), this._nowIndicatorTimer = setInterval(() => {
1421
- const t = this.container.querySelector(".fc-now-indicator");
1422
- if (t) {
1423
- const e = /* @__PURE__ */ new Date();
1424
- t.style.top = `${e.getHours() * 60 + e.getMinutes()}px`;
1425
- }
1426
- }, 6e4);
1427
- }
1428
- /**
1429
- * Compute overlap layout columns for a list of timed events.
1430
- * Returns a Map of event.id -> { column, totalColumns }.
1431
- * Uses a greedy left-to-right column packing algorithm.
1432
- * @param {Array} events - Array of event objects with start/end dates
1433
- * @returns {Map<string, {column: number, totalColumns: number}>}
1434
- */
1435
- computeOverlapLayout(t) {
1436
- if (!t || t.length === 0) return /* @__PURE__ */ new Map();
1437
- const e = t.map((o) => {
1438
- const c = new Date(o.start), l = new Date(o.end), d = c.getHours() * 60 + c.getMinutes(), p = Math.max(d + 1, l.getHours() * 60 + l.getMinutes());
1439
- return { id: o.id, startMin: d, endMin: p };
1440
- });
1441
- e.sort(
1442
- (o, c) => o.startMin - c.startMin || c.endMin - c.startMin - (o.endMin - o.startMin)
1443
- );
1444
- const r = [], i = /* @__PURE__ */ new Map();
1445
- for (const o of e) {
1446
- let c = !1;
1447
- for (let l = 0; l < r.length; l++)
1448
- if (r[l] <= o.startMin) {
1449
- r[l] = o.endMin, i.set(o.id, { column: l, totalColumns: 0 }), c = !0;
1450
- break;
1451
- }
1452
- c || (i.set(o.id, { column: r.length, totalColumns: 0 }), r.push(o.endMin));
1453
- }
1454
- const s = [];
1455
- let n = [], a = 0;
1456
- for (const o of e)
1457
- n.length === 0 || o.startMin < a ? (n.push(o), a = Math.max(a, o.endMin)) : (s.push(n), n = [o], a = o.endMin);
1458
- n.length > 0 && s.push(n);
1459
- for (const o of s) {
1460
- const c = Math.max(...o.map((l) => i.get(l.id).column)) + 1;
1461
- for (const l of o)
1462
- i.get(l.id).totalColumns = c;
1463
- }
1464
- return i;
1465
- }
1466
- /**
1467
- * Render a timed event block
1468
- * @param {Object} event - Event object
1469
- * @param {Object} options - Rendering options
1470
- * @param {Object} options.compact - Use compact layout
1471
- * @param {Object} options.overlapLayout - Map from computeOverlapLayout()
1472
- * @returns {string} HTML string
1473
- */
1474
- renderTimedEvent(t, e = {}) {
1475
- const { compact: r = !0, overlapLayout: i = null } = e, s = new Date(t.start), n = new Date(t.end), a = s.getHours() * 60 + s.getMinutes(), o = Math.max((n - s) / (1e3 * 60), r ? 20 : 30), c = this.getEventColor(t), l = this.getContrastingTextColor(c), d = r ? "4px 8px" : "8px 12px", p = r ? "11px" : "13px", v = r ? 2 : 12, y = r ? 2 : 24, T = r ? "4px" : "6px";
1476
- let x, w;
1477
- if (i && i.has(t.id)) {
1478
- const { column: C, totalColumns: E } = i.get(t.id), M = `(100% - ${v + y}px)`;
1479
- x = `calc(${v}px + ${C} * ${M} / ${E})`, w = `calc(${M} / ${E})`;
1480
- } else
1481
- x = `${v}px`, w = `calc(100% - ${v + y}px)`;
1482
- return `
1483
- <div class="fc-event fc-timed-event" data-event-id="${this.escapeHTML(t.id)}"
1484
- style="position: absolute; top: ${a}px; height: ${o}px;
1485
- left: ${x}; width: ${w};
1486
- background-color: ${c}; border-radius: ${T};
1487
- padding: ${d}; font-size: ${p};
816
+ }
817
+ static getButtonStyles() {
818
+ return "\n .fc-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 8px 16px;\n font-size: var(--fc-font-size-sm);\n font-weight: var(--fc-font-weight-medium);\n line-height: 1.25rem;\n border-radius: var(--fc-border-radius);\n border: 1px solid transparent;\n cursor: pointer;\n transition: all var(--fc-transition-fast);\n outline: none;\n user-select: none;\n gap: var(--fc-spacing-sm);\n white-space: nowrap;\n }\n\n .fc-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .fc-btn-primary {\n background: var(--fc-primary-color);\n color: white;\n box-shadow: var(--fc-shadow-sm);\n }\n\n .fc-btn-primary:hover:not(:disabled) {\n background: var(--fc-primary-hover);\n box-shadow: var(--fc-shadow);\n }\n\n .fc-btn-secondary {\n background: white;\n border-color: var(--fc-border-color);\n color: var(--fc-text-color);\n box-shadow: var(--fc-shadow-sm);\n }\n\n .fc-btn-secondary:hover:not(:disabled) {\n background: var(--fc-background-hover);\n border-color: var(--fc-border-color-hover);\n }\n\n .fc-btn-outline {\n background: transparent;\n border-color: var(--fc-border-color);\n color: var(--fc-text-secondary);\n }\n\n .fc-btn-outline:hover:not(:disabled) {\n background: var(--fc-background-hover);\n color: var(--fc-text-color);\n border-color: var(--fc-border-color-hover);\n }\n\n .fc-btn-ghost {\n background: transparent;\n color: var(--fc-text-secondary);\n }\n\n .fc-btn-ghost:hover:not(:disabled) {\n background: var(--fc-background-hover);\n color: var(--fc-text-color);\n }\n\n .fc-btn-sm {\n padding: 6px 12px;\n font-size: var(--fc-font-size-xs);\n }\n\n .fc-btn-lg {\n padding: 10px 20px;\n font-size: var(--fc-font-size-base);\n }\n\n .fc-btn-icon {\n width: 32px;\n height: 32px;\n padding: 0;\n border-radius: var(--fc-border-radius-full);\n }\n ";
819
+ }
820
+ static darken(e, t) {
821
+ let n = parseInt(e.replace("#", ""), 16), r = Math.round(2.55 * t), i = (n >> 16) - r, a = (n >> 8 & 255) - r, o = (n & 255) - r;
822
+ return "#" + (16777216 + (i < 255 ? i < 1 ? 0 : i : 255) * 65536 + (a < 255 ? a < 1 ? 0 : a : 255) * 256 + (o < 255 ? o < 1 ? 0 : o : 255)).toString(16).slice(1);
823
+ }
824
+ static lighten(e, t) {
825
+ let n = parseInt(e.replace("#", ""), 16), r = Math.round(2.55 * t), i = (n >> 16) + r, a = (n >> 8 & 255) + r, o = (n & 255) + r;
826
+ return "#" + (16777216 + (i < 255 ? i < 1 ? 0 : i : 255) * 65536 + (a < 255 ? a < 1 ? 0 : a : 255) * 256 + (o < 255 ? o < 1 ? 0 : o : 255)).toString(16).slice(1);
827
+ }
828
+ static getContrastColor(e) {
829
+ let t = e.replace("#", ""), n = parseInt(t.substring(0, 2), 16), r = parseInt(t.substring(2, 4), 16), i = parseInt(t.substring(4, 6), 16);
830
+ return (n * 299 + r * 587 + i * 114) / 1e3 >= 128 ? "#000000" : "#FFFFFF";
831
+ }
832
+ static sanitizeColor(e, t = "var(--fc-primary-color)") {
833
+ if (!e || typeof e != "string") return t;
834
+ let n = e.trim();
835
+ return /[;{}()<>\"\'\\]/.test(n) ? t : /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(n) || /^var\(--[a-zA-Z0-9-]+\)$/.test(n) || /^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(,\s*(0|1|0?\.\d+))?\s*\)$/.test(n) || [
836
+ "transparent",
837
+ "currentColor",
838
+ "inherit",
839
+ "black",
840
+ "white",
841
+ "red",
842
+ "green",
843
+ "blue",
844
+ "yellow",
845
+ "orange",
846
+ "purple",
847
+ "pink",
848
+ "brown",
849
+ "gray",
850
+ "grey",
851
+ "cyan",
852
+ "magenta",
853
+ "lime",
854
+ "navy",
855
+ "teal",
856
+ "aqua",
857
+ "maroon",
858
+ "olive",
859
+ "silver",
860
+ "fuchsia"
861
+ ].includes(n.toLowerCase()) ? n : t;
862
+ }
863
+ static hexToRgba(e, t = 1) {
864
+ let n = e.replace("#", "");
865
+ return `rgba(${parseInt(n.substring(0, 2), 16)}, ${parseInt(n.substring(2, 4), 16)}, ${parseInt(n.substring(4, 6), 16)}, ${t})`;
866
+ }
867
+ static getGridStyles() {
868
+ return "\n .fc-grid {\n display: grid;\n gap: 1px;\n background: var(--fc-border-color);\n border: 1px solid var(--fc-border-color);\n border-radius: var(--fc-border-radius);\n overflow: hidden;\n }\n\n .fc-grid-cell {\n background: var(--fc-background);\n padding: var(--fc-spacing-sm);\n min-height: 100px;\n position: relative;\n }\n\n .fc-grid-cell:hover {\n background: var(--fc-background-hover);\n }\n\n .fc-grid-header {\n background: var(--fc-background-alt);\n padding: var(--fc-spacing-sm);\n font-weight: var(--fc-font-weight-semibold);\n text-align: center;\n color: var(--fc-text-secondary);\n font-size: var(--fc-font-size-xs);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n ";
869
+ }
870
+ static breakpoints = {
871
+ xs: "320px",
872
+ sm: "576px",
873
+ md: "768px",
874
+ lg: "992px",
875
+ xl: "1200px",
876
+ "2xl": "1400px"
877
+ };
878
+ static mediaQuery(e, t) {
879
+ let n = this.breakpoints[e];
880
+ return n ? `@media (min-width: ${n}) { ${t} }` : "";
881
+ }
882
+ static getAnimations() {
883
+ return "\n @keyframes fc-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes fc-slide-in-up {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n @keyframes fc-slide-in-down {\n from {\n opacity: 0;\n transform: translateY(-10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n @keyframes fc-scale-in {\n from {\n opacity: 0;\n transform: scale(0.95);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n }\n\n @keyframes fc-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n\n .fc-fade-in {\n animation: fc-fade-in var(--fc-transition);\n }\n\n .fc-slide-in-up {\n animation: fc-slide-in-up var(--fc-transition);\n }\n\n .fc-scale-in {\n animation: fc-scale-in var(--fc-transition);\n }\n ";
884
+ }
885
+ }, l = class {
886
+ constructor(e, t) {
887
+ this.container = e, this.stateManager = t, this._listeners = [], this._scrolled = !1, this._nowIndicatorTimer = null;
888
+ }
889
+ render() {
890
+ throw Error("render() must be implemented by subclass");
891
+ }
892
+ cleanup() {
893
+ this._listeners.forEach(({ element: e, event: t, handler: n }) => {
894
+ e.removeEventListener(t, n);
895
+ }), this._listeners = [], this._nowIndicatorTimer &&= (clearInterval(this._nowIndicatorTimer), null);
896
+ }
897
+ addListener(e, t, n) {
898
+ let r = n.bind(this);
899
+ e.addEventListener(t, r), this._listeners.push({
900
+ element: e,
901
+ event: t,
902
+ handler: r
903
+ });
904
+ }
905
+ escapeHTML(e) {
906
+ return e == null ? "" : s.escapeHTML(String(e));
907
+ }
908
+ isToday(e) {
909
+ let t = /* @__PURE__ */ new Date();
910
+ return e.getDate() === t.getDate() && e.getMonth() === t.getMonth() && e.getFullYear() === t.getFullYear();
911
+ }
912
+ isSameDay(e, t) {
913
+ return e.getDate() === t.getDate() && e.getMonth() === t.getMonth() && e.getFullYear() === t.getFullYear();
914
+ }
915
+ formatHour(e) {
916
+ let t = e >= 12 ? "PM" : "AM";
917
+ return `${e % 12 || 12} ${t}`;
918
+ }
919
+ formatTime(e) {
920
+ let t = e.getHours(), n = e.getMinutes(), r = t >= 12 ? "PM" : "AM", i = t % 12 || 12;
921
+ return n === 0 ? `${i} ${r}` : `${i}:${n.toString().padStart(2, "0")} ${r}`;
922
+ }
923
+ getContrastingTextColor(e) {
924
+ return !e || typeof e != "string" || e.charAt(0) !== "#" ? "white" : c.getContrastColor(e);
925
+ }
926
+ renderNowIndicator() {
927
+ let e = /* @__PURE__ */ new Date();
928
+ return `<div class="fc-now-indicator" style="position: absolute; left: 0; right: 0; top: ${e.getHours() * 60 + e.getMinutes()}px; height: 2px; background: var(--fc-danger-color); z-index: 15; pointer-events: none;"></div>`;
929
+ }
930
+ startNowIndicatorTimer() {
931
+ this._nowIndicatorTimer && clearInterval(this._nowIndicatorTimer), this._nowIndicatorTimer = setInterval(() => {
932
+ let e = this.container.querySelector(".fc-now-indicator");
933
+ if (e) {
934
+ let t = /* @__PURE__ */ new Date();
935
+ e.style.top = `${t.getHours() * 60 + t.getMinutes()}px`;
936
+ }
937
+ }, 6e4);
938
+ }
939
+ computeOverlapLayout(e) {
940
+ if (!e || e.length === 0) return /* @__PURE__ */ new Map();
941
+ let t = e.map((e) => {
942
+ let t = new Date(e.start), n = new Date(e.end), r = t.getHours() * 60 + t.getMinutes(), i = Math.max(r + 1, n.getHours() * 60 + n.getMinutes());
943
+ return {
944
+ id: e.id,
945
+ startMin: r,
946
+ endMin: i
947
+ };
948
+ });
949
+ t.sort((e, t) => e.startMin - t.startMin || t.endMin - t.startMin - (e.endMin - e.startMin));
950
+ let n = [], r = /* @__PURE__ */ new Map();
951
+ for (let e of t) {
952
+ let t = !1;
953
+ for (let i = 0; i < n.length; i++) if (n[i] <= e.startMin) {
954
+ n[i] = e.endMin, r.set(e.id, {
955
+ column: i,
956
+ totalColumns: 0
957
+ }), t = !0;
958
+ break;
959
+ }
960
+ t || (r.set(e.id, {
961
+ column: n.length,
962
+ totalColumns: 0
963
+ }), n.push(e.endMin));
964
+ }
965
+ let i = [], a = [], o = 0;
966
+ for (let e of t) a.length === 0 || e.startMin < o ? (a.push(e), o = Math.max(o, e.endMin)) : (i.push(a), a = [e], o = e.endMin);
967
+ a.length > 0 && i.push(a);
968
+ for (let e of i) {
969
+ let t = Math.max(...e.map((e) => r.get(e.id).column)) + 1;
970
+ for (let n of e) r.get(n.id).totalColumns = t;
971
+ }
972
+ return r;
973
+ }
974
+ renderTimedEvent(e, t = {}) {
975
+ let { compact: n = !0, overlapLayout: r = null } = t, i = new Date(e.start), a = new Date(e.end), o = i.getHours() * 60 + i.getMinutes(), s = Math.max((a - i) / (1e3 * 60), n ? 20 : 30), c = this.getEventColor(e), l = this.getContrastingTextColor(c), u = n ? "4px 8px" : "8px 12px", d = n ? "11px" : "13px", f = n ? 2 : 12, p = n ? 2 : 24, m = n ? "4px" : "6px", h, g;
976
+ if (r && r.has(e.id)) {
977
+ let { column: t, totalColumns: n } = r.get(e.id), i = `(100% - ${f + p}px)`;
978
+ h = `calc(${f}px + ${t} * ${i} / ${n})`, g = `calc(${i} / ${n})`;
979
+ } else h = `${f}px`, g = `calc(100% - ${f + p}px)`;
980
+ return `
981
+ <div class="fc-event fc-timed-event" data-event-id="${this.escapeHTML(e.id)}"
982
+ style="position: absolute; top: ${o}px; height: ${s}px;
983
+ left: ${h}; width: ${g};
984
+ background-color: ${c}; border-radius: ${m};
985
+ padding: ${u}; font-size: ${d};
1488
986
  font-weight: 500; color: ${l}; overflow: hidden;
1489
987
  box-shadow: 0 1px 2px rgba(0,0,0,0.1);
1490
988
  cursor: pointer; z-index: 5;">
1491
989
  <div style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
1492
- ${this.escapeHTML(t.title)}
990
+ ${this.escapeHTML(e.title)}
1493
991
  </div>
1494
- <div style="font-size: ${r ? "10px" : "11px"}; opacity: 0.9;">
1495
- ${this.formatTime(s)}${r ? "" : " - " + this.formatTime(n)}
992
+ <div style="font-size: ${n ? "10px" : "11px"}; opacity: 0.9;">
993
+ ${this.formatTime(i)}${n ? "" : " - " + this.formatTime(a)}
1496
994
  </div>
1497
995
  </div>
1498
996
  `;
1499
- }
1500
- /**
1501
- * Get a safe, sanitized event color value.
1502
- * @param {Object} event
1503
- * @returns {string}
1504
- */
1505
- getEventColor(t) {
1506
- return f.sanitizeColor(t == null ? void 0 : t.backgroundColor, "#2563eb");
1507
- }
1508
- /**
1509
- * Attach common event handlers for day/event clicks
1510
- */
1511
- attachCommonEventHandlers() {
1512
- this.addListener(this.container, "click", (t) => {
1513
- const e = t.target.closest(".fc-event");
1514
- if (!e || !this.container.contains(e)) return;
1515
- t.stopPropagation();
1516
- const r = e.dataset.eventId, i = this.stateManager.getEvents().find((s) => s.id === r);
1517
- i && this.stateManager.selectEvent(i);
1518
- });
1519
- }
1520
- }
1521
- class _ extends D {
1522
- constructor(t, e) {
1523
- super(t, e), this.maxEventsToShow = 3;
1524
- }
1525
- render() {
1526
- if (!this.container || !this.stateManager) return;
1527
- const t = this.stateManager.getViewData();
1528
- if (!t || !t.weeks) {
1529
- this.container.innerHTML = '<div style="padding: 20px; text-align: center; color: var(--fc-text-secondary);">No data available for month view.</div>';
1530
- return;
1531
- }
1532
- this.cleanup();
1533
- const e = this.stateManager.getState().config, r = this._renderMonthView(t, e);
1534
- this.container.innerHTML = r, this._attachEventHandlers();
1535
- }
1536
- _renderMonthView(t, e) {
1537
- const r = e.weekStartsOn || 0;
1538
- let s = `
1539
- <div class="fc-month-view" style="display: flex; flex-direction: column; height: 100%; min-height: 400px; background: var(--fc-background); border: 1px solid var(--fc-border-color);">
1540
- <div class="fc-month-header" style="display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid var(--fc-border-color); background: var(--fc-background-alt);">
1541
- ${this._getDayNames(r).map((n) => `<div class="fc-month-header-cell" style="padding: 12px 8px; text-align: center; font-size: 11px; font-weight: 600; color: var(--fc-text-light); text-transform: uppercase;">${this.escapeHTML(n)}</div>`).join("")}
997
+ }
998
+ getEventColor(e) {
999
+ return c.sanitizeColor(e?.backgroundColor, "#2563eb");
1000
+ }
1001
+ attachCommonEventHandlers() {
1002
+ this.addListener(this.container, "click", (e) => {
1003
+ let t = e.target.closest(".fc-event");
1004
+ !t || !this.container.contains(t) || (e.stopPropagation(), this._selectEventFromElement(t));
1005
+ }), this.addListener(this.container, "keydown", (e) => {
1006
+ if (e.key !== "Enter" && e.key !== " ") return;
1007
+ let t = e.target.closest(".fc-event");
1008
+ !t || !this.container.contains(t) || (e.preventDefault(), e.stopPropagation(), this._selectEventFromElement(t));
1009
+ }), this._enhanceEventAccessibility();
1010
+ }
1011
+ _selectEventFromElement(e) {
1012
+ let t = e.dataset.eventId, n = this.stateManager.getEvents().find((e) => e.id === t);
1013
+ n && this.stateManager.selectEvent(n);
1014
+ }
1015
+ _enhanceEventAccessibility() {
1016
+ for (let e of this.container.querySelectorAll(".fc-event")) if (e.setAttribute("role", "button"), e.setAttribute("tabindex", "0"), !e.hasAttribute("aria-label")) {
1017
+ let t = (e.getAttribute("title") || e.textContent || "").trim();
1018
+ t && e.setAttribute("aria-label", `Event: ${t}`);
1019
+ }
1020
+ }
1021
+ }, u = class extends l {
1022
+ constructor(e, t) {
1023
+ super(e, t), this.maxEventsToShow = 3;
1024
+ }
1025
+ render() {
1026
+ if (!this.container || !this.stateManager) return;
1027
+ let e = this.stateManager.getViewData();
1028
+ if (!e || !e.weeks) {
1029
+ this.container.innerHTML = "<div style=\"padding: 20px; text-align: center; color: var(--fc-text-secondary);\">No data available for month view.</div>";
1030
+ return;
1031
+ }
1032
+ this.cleanup();
1033
+ let t = this.stateManager.getState().config, n = this._renderMonthView(e, t);
1034
+ this.container.innerHTML = n, this._attachEventHandlers();
1035
+ let r = this._applyRovingTabindex(this._pendingFocusMs ?? null);
1036
+ this._pendingFocusMs != null && (this._pendingFocusMs = null, r && r.focus());
1037
+ }
1038
+ _renderMonthView(e, t) {
1039
+ let n = t.weekStartsOn || 0, r = this._getDayNames(n), i = t.locale || "en-US";
1040
+ this._dayLabelFormatter = new Intl.DateTimeFormat(i, {
1041
+ weekday: "long",
1042
+ year: "numeric",
1043
+ month: "long",
1044
+ day: "numeric"
1045
+ });
1046
+ let a = this.stateManager.getState().currentDate, o = new Intl.DateTimeFormat(i, {
1047
+ month: "long",
1048
+ year: "numeric"
1049
+ }).format(a || /* @__PURE__ */ new Date()), s = `
1050
+ <div class="fc-month-view" role="grid" aria-label="${this.escapeHTML(o)}" style="display: flex; flex-direction: column; height: 100%; min-height: 400px; background: var(--fc-background); border: 1px solid var(--fc-border-color);">
1051
+ <div class="fc-month-header" role="row" style="display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid var(--fc-border-color); background: var(--fc-background-alt);">
1052
+ ${r.map((e) => `<div class="fc-month-header-cell" role="columnheader" style="padding: 12px 8px; text-align: center; font-size: 11px; font-weight: 600; color: var(--fc-text-light); text-transform: uppercase;">${this.escapeHTML(e)}</div>`).join("")}
1542
1053
  </div>
1543
1054
  <div class="fc-month-body" style="display: flex; flex-direction: column; flex: 1;">
1544
1055
  `;
1545
- return t.weeks.forEach((n) => {
1546
- s += this._renderWeek(n);
1547
- }), s += "</div></div>", s;
1548
- }
1549
- _getDayNames(t) {
1550
- const e = this.stateManager.getState().config.locale || "en-US", r = [];
1551
- for (let i = 0; i < 7; i++) {
1552
- const s = (t + i) % 7;
1553
- r.push(u.getDayAbbreviation(s, e));
1554
- }
1555
- return r;
1556
- }
1557
- _renderWeek(t) {
1558
- let e = '<div class="fc-month-week" style="display: grid; grid-template-columns: repeat(7, 1fr); flex: 1; min-height: 80px;">';
1559
- return t.days.forEach((r) => {
1560
- e += this._renderDay(r);
1561
- }), e += "</div>", e;
1562
- }
1563
- _renderDay(t) {
1564
- const e = !t.isCurrentMonth, r = t.isToday, i = e ? "var(--fc-background-hover)" : "var(--fc-background)", s = e ? "var(--fc-text-light)" : "var(--fc-text-color)", n = r ? "background: var(--fc-primary-color); color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;" : "", a = t.events || [], o = a.slice(0, this.maxEventsToShow), c = a.length - this.maxEventsToShow;
1565
- return `
1566
- <div class="fc-month-day" data-date="${this.escapeHTML(t.date)}"
1567
- style="background: ${i}; border-right: 1px solid var(--fc-border-color); border-bottom: 1px solid var(--fc-border-color); padding: 4px; min-height: 80px; cursor: pointer; display: flex; flex-direction: column;">
1568
- <div class="fc-day-number" style="font-size: 13px; font-weight: 500; color: ${s}; padding: 2px 4px; margin-bottom: 4px; ${n}">
1569
- ${this.escapeHTML(String(t.dayOfMonth))}
1056
+ return e.weeks.forEach((e) => {
1057
+ s += this._renderWeek(e);
1058
+ }), s += "</div></div>", s;
1059
+ }
1060
+ _getDayNames(e) {
1061
+ let t = this.stateManager.getState().config.locale || "en-US", n = [];
1062
+ for (let r = 0; r < 7; r++) {
1063
+ let i = (e + r) % 7;
1064
+ n.push(o.getDayAbbreviation(i, t));
1065
+ }
1066
+ return n;
1067
+ }
1068
+ _renderWeek(e) {
1069
+ let t = "<div class=\"fc-month-week\" role=\"row\" style=\"display: grid; grid-template-columns: repeat(7, 1fr); flex: 1; min-height: 80px;\">";
1070
+ return e.days.forEach((e) => {
1071
+ t += this._renderDay(e);
1072
+ }), t += "</div>", t;
1073
+ }
1074
+ _renderDay(e) {
1075
+ let t = !e.isCurrentMonth, n = e.isToday, r = t ? "var(--fc-background-hover)" : "var(--fc-background)", i = t ? "var(--fc-text-light)" : "var(--fc-text-color)", a = n ? "background: var(--fc-primary-color); color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;" : "", o = e.events || [], s = o.slice(0, this.maxEventsToShow), c = o.length - this.maxEventsToShow, l = this._dayLabelFormatter ? this._dayLabelFormatter.format(new Date(e.date)) : e.date;
1076
+ return o.length > 0 && (l += `, ${o.length} ${o.length === 1 ? "event" : "events"}`), `
1077
+ <div class="fc-month-day" data-date="${this.escapeHTML(e.date)}"
1078
+ role="gridcell" tabindex="-1"
1079
+ aria-selected="${e.isSelected ? "true" : "false"}"
1080
+ ${n ? "aria-current=\"date\"" : ""}
1081
+ ${t ? "data-other-month=\"true\"" : ""}
1082
+ aria-label="${this.escapeHTML(l)}"
1083
+ style="background: ${r}; border-right: 1px solid var(--fc-border-color); border-bottom: 1px solid var(--fc-border-color); padding: 4px; min-height: 80px; cursor: pointer; display: flex; flex-direction: column;">
1084
+ <div class="fc-day-number" style="font-size: 13px; font-weight: 500; color: ${i}; padding: 2px 4px; margin-bottom: 4px; ${a}">
1085
+ ${this.escapeHTML(String(e.dayOfMonth))}
1570
1086
  </div>
1571
1087
  <div class="fc-day-events" style="display: flex; flex-direction: column; gap: 2px; flex: 1; overflow: hidden;">
1572
- ${o.map((l) => this._renderEvent(l)).join("")}
1088
+ ${s.map((e) => this._renderEvent(e)).join("")}
1573
1089
  ${c > 0 ? `<div class="fc-more-events" style="font-size: 10px; color: var(--fc-text-light); padding: 2px 4px; font-weight: 500;">+${c} more</div>` : ""}
1574
1090
  </div>
1575
1091
  </div>
1576
1092
  `;
1577
- }
1578
- _renderEvent(t) {
1579
- const e = this.getEventColor(t), r = this.getContrastingTextColor(e);
1580
- return `
1581
- <div class="fc-event" data-event-id="${this.escapeHTML(t.id)}"
1582
- style="background-color: ${e}; font-size: 11px; padding: 2px 6px; border-radius: 3px; color: ${r}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer;">
1583
- ${this.escapeHTML(t.title)}
1093
+ }
1094
+ _renderEvent(e) {
1095
+ let t = this.getEventColor(e), n = this.getContrastingTextColor(t);
1096
+ return `
1097
+ <div class="fc-event" data-event-id="${this.escapeHTML(e.id)}"
1098
+ style="background-color: ${t}; font-size: 11px; padding: 2px 6px; border-radius: 3px; color: ${n}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer;">
1099
+ ${this.escapeHTML(e.title)}
1584
1100
  </div>
1585
1101
  `;
1586
- }
1587
- _attachEventHandlers() {
1588
- this.addListener(this.container, "click", (t) => {
1589
- const e = t.target.closest(".fc-month-day");
1590
- if (!e || !this.container.contains(e) || t.target.closest(".fc-event")) return;
1591
- const r = new Date(e.dataset.date);
1592
- this.stateManager.selectDate(r);
1593
- }), this.attachCommonEventHandlers();
1594
- }
1595
- }
1596
- class V extends D {
1597
- constructor(t, e) {
1598
- super(t, e), this.hourHeight = 60, this.totalHeight = 24 * this.hourHeight;
1599
- }
1600
- render() {
1601
- if (!this.container || !this.stateManager) return;
1602
- const t = this.stateManager.getViewData();
1603
- if (!t || !t.days || t.days.length === 0) {
1604
- this.container.innerHTML = '<div style="padding: 20px; text-align: center; color: var(--fc-text-secondary);">No data available for week view.</div>';
1605
- return;
1606
- }
1607
- this.cleanup(), this._scrolled = !1;
1608
- const e = this.stateManager.getState().config, r = this._renderWeekView(t, e);
1609
- this.container.innerHTML = r, this._attachEventHandlers(), this._scrollToCurrentTime(), this.startNowIndicatorTimer();
1610
- }
1611
- _renderWeekView(t, e) {
1612
- const r = t.days, i = this.stateManager.getState().config.locale || "en-US", s = Array.from({ length: 24 }, (a, o) => o), n = r.map((a) => {
1613
- const o = new Date(a.date), c = a.events || [];
1614
- return {
1615
- ...a,
1616
- date: o,
1617
- dayName: u.getDayAbbreviation(o.getDay(), i),
1618
- dayOfMonth: o.getDate(),
1619
- isToday: this.isToday(o),
1620
- timedEvents: c.filter((l) => !l.allDay),
1621
- allDayEvents: c.filter((l) => l.allDay)
1622
- };
1623
- });
1624
- return `
1102
+ }
1103
+ _attachEventHandlers() {
1104
+ this.addListener(this.container, "click", (e) => {
1105
+ let t = e.target.closest(".fc-month-day");
1106
+ if (!t || !this.container.contains(t) || e.target.closest(".fc-event")) return;
1107
+ let n = new Date(t.dataset.date);
1108
+ this.stateManager.selectDate(n);
1109
+ }), this.addListener(this.container, "keydown", (e) => {
1110
+ if (e.target.closest(".fc-event")) return;
1111
+ let t = e.target.closest(".fc-month-day");
1112
+ if (!t || !this.container.contains(t)) return;
1113
+ let n = Array.from(this.container.querySelectorAll(".fc-month-day")), r = n.indexOf(t), i;
1114
+ switch (e.key) {
1115
+ case "ArrowRight":
1116
+ i = r + 1;
1117
+ break;
1118
+ case "ArrowLeft":
1119
+ i = r - 1;
1120
+ break;
1121
+ case "ArrowDown":
1122
+ i = r + 7;
1123
+ break;
1124
+ case "ArrowUp":
1125
+ i = r - 7;
1126
+ break;
1127
+ case "Home":
1128
+ i = r - r % 7;
1129
+ break;
1130
+ case "End":
1131
+ i = r - r % 7 + 6;
1132
+ break;
1133
+ case "PageUp":
1134
+ case "PageDown": {
1135
+ e.preventDefault();
1136
+ let n = new Date(t.dataset.date);
1137
+ n.setMonth(n.getMonth() + (e.key === "PageDown" ? 1 : -1)), this._pendingFocusMs = n.getTime(), e.key === "PageDown" ? this.stateManager.next() : this.stateManager.previous();
1138
+ return;
1139
+ }
1140
+ case "Enter":
1141
+ case " ": {
1142
+ e.preventDefault();
1143
+ let n = new Date(t.dataset.date);
1144
+ this._pendingFocusMs = n.getTime(), this.stateManager.selectDate(n);
1145
+ return;
1146
+ }
1147
+ default: return;
1148
+ }
1149
+ if (e.preventDefault(), i >= 0 && i < n.length) this._applyRovingTabindex(new Date(n[i].dataset.date).getTime()), n[i].focus();
1150
+ else {
1151
+ let e = new Date(t.dataset.date);
1152
+ e.setDate(e.getDate() + (i - r)), this._pendingFocusMs = e.getTime(), i < 0 ? this.stateManager.previous() : this.stateManager.next();
1153
+ }
1154
+ }), this.attachCommonEventHandlers();
1155
+ }
1156
+ _applyRovingTabindex(e = null) {
1157
+ let t = Array.from(this.container.querySelectorAll(".fc-month-day"));
1158
+ if (t.length === 0) return null;
1159
+ let n = null;
1160
+ e != null && (n = t.find((t) => new Date(t.dataset.date).getTime() === e)), n ||= t.find((e) => e.getAttribute("aria-selected") === "true"), n ||= t.find((e) => e.hasAttribute("aria-current")), n ||= t.find((e) => !e.dataset.otherMonth) || t[0];
1161
+ for (let e of t) e.setAttribute("tabindex", e === n ? "0" : "-1");
1162
+ return n;
1163
+ }
1164
+ }, d = class extends l {
1165
+ constructor(e, t) {
1166
+ super(e, t), this.hourHeight = 60, this.totalHeight = 24 * this.hourHeight;
1167
+ }
1168
+ render() {
1169
+ if (!this.container || !this.stateManager) return;
1170
+ let e = this.stateManager.getViewData();
1171
+ if (!e || !e.days || e.days.length === 0) {
1172
+ this.container.innerHTML = "<div style=\"padding: 20px; text-align: center; color: var(--fc-text-secondary);\">No data available for week view.</div>";
1173
+ return;
1174
+ }
1175
+ this.cleanup(), this._scrolled = !1;
1176
+ let t = this.stateManager.getState().config, n = this._renderWeekView(e, t);
1177
+ this.container.innerHTML = n, this._attachEventHandlers(), this._scrollToCurrentTime(), this.startNowIndicatorTimer();
1178
+ }
1179
+ _renderWeekView(e, t) {
1180
+ let n = e.days, r = this.stateManager.getState().config.locale || "en-US", i = Array.from({ length: 24 }, (e, t) => t), a = n.map((e) => {
1181
+ let t = new Date(e.date), n = e.events || [];
1182
+ return {
1183
+ ...e,
1184
+ date: t,
1185
+ dayName: o.getDayAbbreviation(t.getDay(), r),
1186
+ dayOfMonth: t.getDate(),
1187
+ isToday: this.isToday(t),
1188
+ timedEvents: n.filter((e) => !e.allDay),
1189
+ allDayEvents: n.filter((e) => e.allDay)
1190
+ };
1191
+ });
1192
+ return `
1625
1193
  <div class="fc-week-view" style="display: flex; flex-direction: column; height: 100%; background: var(--fc-background); overflow: hidden;">
1626
- ${this._renderHeader(n)}
1627
- ${this._renderAllDayRow(n)}
1628
- ${this._renderTimeGrid(n, s)}
1194
+ ${this._renderHeader(a)}
1195
+ ${this._renderAllDayRow(a)}
1196
+ ${this._renderTimeGrid(a, i)}
1629
1197
  </div>
1630
1198
  `;
1631
- }
1632
- _renderHeader(t) {
1633
- return `
1199
+ }
1200
+ _renderHeader(e) {
1201
+ return `
1634
1202
  <div class="fc-week-header" style="display: grid; grid-template-columns: 60px repeat(7, 1fr); border-bottom: 1px solid var(--fc-border-color); background: var(--fc-background-alt); flex-shrink: 0;">
1635
1203
  <div style="border-right: 1px solid var(--fc-border-color);"></div>
1636
- ${t.map(
1637
- (e) => `
1204
+ ${e.map((e) => `
1638
1205
  <div style="padding: 12px 8px; text-align: center; border-right: 1px solid var(--fc-border-color);">
1639
1206
  <div style="font-size: 10px; font-weight: 700; color: var(--fc-text-light); text-transform: uppercase; letter-spacing: 0.1em;">
1640
1207
  ${this.escapeHTML(e.dayName)}
@@ -1643,273 +1210,269 @@ class V extends D {
1643
1210
  ${this.escapeHTML(String(e.dayOfMonth))}
1644
1211
  </div>
1645
1212
  </div>
1646
- `
1647
- ).join("")}
1213
+ `).join("")}
1648
1214
  </div>
1649
1215
  `;
1650
- }
1651
- _renderAllDayRow(t) {
1652
- return `
1216
+ }
1217
+ _renderAllDayRow(e) {
1218
+ return `
1653
1219
  <div class="fc-all-day-row" style="display: grid; grid-template-columns: 60px repeat(7, 1fr); border-bottom: 1px solid var(--fc-border-color); background: var(--fc-background-alt); min-height: 32px; flex-shrink: 0;">
1654
1220
  <div style="font-size: 9px; color: var(--fc-text-light); display: flex; align-items: center; justify-content: center; border-right: 1px solid var(--fc-border-color); text-transform: uppercase; font-weight: 700;">
1655
1221
  All day
1656
1222
  </div>
1657
- ${t.map(
1658
- (e) => `
1223
+ ${e.map((e) => `
1659
1224
  <div class="fc-all-day-cell" data-date="${e.date.toISOString()}" style="border-right: 1px solid var(--fc-border-color); padding: 4px; display: flex; flex-direction: column; gap: 2px;">
1660
- ${e.allDayEvents.map(
1661
- (r) => `
1662
- <div class="fc-event fc-all-day-event" data-event-id="${this.escapeHTML(r.id)}"
1663
- style="background-color: ${this.getEventColor(r)}; font-size: 10px; padding: 2px 4px; border-radius: 2px; color: white; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
1664
- ${this.escapeHTML(r.title)}
1225
+ ${e.allDayEvents.map((e) => `
1226
+ <div class="fc-event fc-all-day-event" data-event-id="${this.escapeHTML(e.id)}"
1227
+ style="background-color: ${this.getEventColor(e)}; font-size: 10px; padding: 2px 4px; border-radius: 2px; color: white; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
1228
+ ${this.escapeHTML(e.title)}
1665
1229
  </div>
1666
- `
1667
- ).join("")}
1230
+ `).join("")}
1668
1231
  </div>
1669
- `
1670
- ).join("")}
1232
+ `).join("")}
1671
1233
  </div>
1672
1234
  `;
1673
- }
1674
- _renderTimeGrid(t, e) {
1675
- return `
1235
+ }
1236
+ _renderTimeGrid(e, t) {
1237
+ return `
1676
1238
  <div id="week-scroll-container" class="fc-time-grid-container" style="flex: 1; overflow-y: auto; overflow-x: hidden; position: relative;">
1677
1239
  <div class="fc-time-grid" style="display: grid; grid-template-columns: 60px repeat(7, 1fr); position: relative; height: ${this.totalHeight}px;">
1678
- ${this._renderTimeGutter(e)}
1679
- ${t.map((r) => this._renderDayColumn(r, e)).join("")}
1240
+ ${this._renderTimeGutter(t)}
1241
+ ${e.map((e) => this._renderDayColumn(e, t)).join("")}
1680
1242
  </div>
1681
1243
  </div>
1682
1244
  `;
1683
- }
1684
- _renderTimeGutter(t) {
1685
- return `
1245
+ }
1246
+ _renderTimeGutter(e) {
1247
+ return `
1686
1248
  <div class="fc-time-gutter" style="border-right: 1px solid var(--fc-border-color); background: var(--fc-background-alt);">
1687
- ${t.map(
1688
- (e) => `
1249
+ ${e.map((e) => `
1689
1250
  <div style="height: ${this.hourHeight}px; font-size: 10px; color: var(--fc-text-light); text-align: right; padding-right: 8px; font-weight: 500;">
1690
1251
  ${e === 0 ? "" : this.formatHour(e)}
1691
1252
  </div>
1692
- `
1693
- ).join("")}
1253
+ `).join("")}
1694
1254
  </div>
1695
1255
  `;
1696
- }
1697
- _renderDayColumn(t, e) {
1698
- return `
1699
- <div class="fc-week-day-column" data-date="${t.date.toISOString()}" style="border-right: 1px solid var(--fc-border-color); position: relative; cursor: pointer;">
1256
+ }
1257
+ _renderDayColumn(e, t) {
1258
+ return `
1259
+ <div class="fc-week-day-column" data-date="${e.date.toISOString()}" style="border-right: 1px solid var(--fc-border-color); position: relative; cursor: pointer;">
1700
1260
  <!-- Hour grid lines -->
1701
- ${e.map(() => `<div style="height: ${this.hourHeight}px; border-bottom: 1px solid var(--fc-background-hover);"></div>`).join("")}
1261
+ ${t.map(() => `<div style="height: ${this.hourHeight}px; border-bottom: 1px solid var(--fc-background-hover);"></div>`).join("")}
1702
1262
 
1703
1263
  <!-- Now indicator for today -->
1704
- ${t.isToday ? this.renderNowIndicator() : ""}
1264
+ ${e.isToday ? this.renderNowIndicator() : ""}
1705
1265
 
1706
1266
  <!-- Timed events -->
1707
1267
  ${(() => {
1708
- const r = this.computeOverlapLayout(t.timedEvents);
1709
- return t.timedEvents.map(
1710
- (i) => this.renderTimedEvent(i, { compact: !0, overlapLayout: r })
1711
- ).join("");
1712
- })()}
1268
+ let t = this.computeOverlapLayout(e.timedEvents);
1269
+ return e.timedEvents.map((e) => this.renderTimedEvent(e, {
1270
+ compact: !0,
1271
+ overlapLayout: t
1272
+ })).join("");
1273
+ })()}
1713
1274
  </div>
1714
1275
  `;
1715
- }
1716
- _attachEventHandlers() {
1717
- this.addListener(this.container, "click", (t) => {
1718
- const e = t.target.closest(".fc-week-day-column");
1719
- if (!e || !this.container.contains(e) || t.target.closest(".fc-event")) return;
1720
- const r = new Date(e.dataset.date), i = this.container.querySelector("#week-scroll-container"), s = e.offsetTop, n = t.clientY - e.getBoundingClientRect().top + (i ? i.scrollTop : 0) - s, a = Math.max(0, Math.min(n + s, this.totalHeight));
1721
- r.setHours(
1722
- Math.floor(a / this.hourHeight),
1723
- Math.floor(a % this.hourHeight / (this.hourHeight / 60)),
1724
- 0,
1725
- 0
1726
- ), this.stateManager.selectDate(r);
1727
- }), this.attachCommonEventHandlers();
1728
- }
1729
- _scrollToCurrentTime() {
1730
- if (this._scrolled) return;
1731
- const t = this.container.querySelector("#week-scroll-container");
1732
- t && (t.scrollTop = 8 * this.hourHeight - 50, this._scrolled = !0);
1733
- }
1734
- }
1735
- class B extends D {
1736
- constructor(t, e) {
1737
- super(t, e), this.hourHeight = 60, this.totalHeight = 24 * this.hourHeight;
1738
- }
1739
- render() {
1740
- if (!this.container || !this.stateManager) return;
1741
- const t = this.stateManager.getViewData();
1742
- if (!t) {
1743
- this.container.innerHTML = '<div style="padding: 20px; text-align: center; color: var(--fc-text-secondary);">No data available for day view.</div>';
1744
- return;
1745
- }
1746
- this.cleanup(), this._scrolled = !1;
1747
- const e = this.stateManager.getState().config, r = this._renderDayView(t, e);
1748
- this.container.innerHTML = r, this._attachEventHandlers(), this._scrollToCurrentTime(), this.startNowIndicatorTimer();
1749
- }
1750
- _renderDayView(t, e) {
1751
- var d, p;
1752
- const r = ((p = (d = this.stateManager) == null ? void 0 : d.getState()) == null ? void 0 : p.currentDate) || /* @__PURE__ */ new Date(), i = this._extractDayData(t, r);
1753
- if (!i)
1754
- return '<div style="padding: 20px; text-align: center; color: var(--fc-text-secondary);">No data available for day view.</div>';
1755
- const { dayDate: s, dayName: n, isToday: a, allDayEvents: o, timedEvents: c } = i, l = Array.from({ length: 24 }, (v, y) => y);
1756
- return `
1276
+ }
1277
+ _attachEventHandlers() {
1278
+ this.addListener(this.container, "click", (e) => {
1279
+ let t = e.target.closest(".fc-week-day-column");
1280
+ if (!t || !this.container.contains(t) || e.target.closest(".fc-event")) return;
1281
+ let n = new Date(t.dataset.date), r = this.container.querySelector("#week-scroll-container"), i = t.offsetTop, a = e.clientY - t.getBoundingClientRect().top + (r ? r.scrollTop : 0) - i, o = Math.max(0, Math.min(a + i, this.totalHeight));
1282
+ n.setHours(Math.floor(o / this.hourHeight), Math.floor(o % this.hourHeight / (this.hourHeight / 60)), 0, 0), this.stateManager.selectDate(n);
1283
+ }), this.attachCommonEventHandlers();
1284
+ }
1285
+ _scrollToCurrentTime() {
1286
+ if (this._scrolled) return;
1287
+ let e = this.container.querySelector("#week-scroll-container");
1288
+ e && (e.scrollTop = 8 * this.hourHeight - 50, this._scrolled = !0);
1289
+ }
1290
+ }, f = class extends l {
1291
+ constructor(e, t) {
1292
+ super(e, t), this.hourHeight = 60, this.totalHeight = 24 * this.hourHeight;
1293
+ }
1294
+ render() {
1295
+ if (!this.container || !this.stateManager) return;
1296
+ let e = this.stateManager.getViewData();
1297
+ if (!e) {
1298
+ this.container.innerHTML = "<div style=\"padding: 20px; text-align: center; color: var(--fc-text-secondary);\">No data available for day view.</div>";
1299
+ return;
1300
+ }
1301
+ this.cleanup(), this._scrolled = !1;
1302
+ let t = this.stateManager.getState().config, n = this._renderDayView(e, t);
1303
+ this.container.innerHTML = n, this._attachEventHandlers(), this._scrollToCurrentTime(), this.startNowIndicatorTimer();
1304
+ }
1305
+ _renderDayView(e, t) {
1306
+ let n = this.stateManager?.getState()?.currentDate || /* @__PURE__ */ new Date(), r = this._extractDayData(e, n);
1307
+ if (!r) return "<div style=\"padding: 20px; text-align: center; color: var(--fc-text-secondary);\">No data available for day view.</div>";
1308
+ let { dayDate: i, dayName: a, isToday: o, allDayEvents: s, timedEvents: c } = r, l = Array.from({ length: 24 }, (e, t) => t);
1309
+ return `
1757
1310
  <div class="fc-day-view" style="display: flex; flex-direction: column; height: 100%; background: var(--fc-background); overflow: hidden;">
1758
- ${this._renderHeader(s, n, a)}
1759
- ${this._renderAllDayRow(o, s)}
1760
- ${this._renderTimeGrid(c, a, s, l)}
1311
+ ${this._renderHeader(i, a, o)}
1312
+ ${this._renderAllDayRow(s, i)}
1313
+ ${this._renderTimeGrid(c, o, i, l)}
1761
1314
  </div>
1762
1315
  `;
1763
- }
1764
- _extractDayData(t, e) {
1765
- let r, i, s, n, a;
1766
- const o = this.stateManager.getState().config.locale || "en-US";
1767
- if (t.type === "day" && t.date)
1768
- if (r = new Date(t.date), i = t.dayName || u.getDayName(r.getDay(), o), s = t.isToday !== void 0 ? t.isToday : this.isToday(r), n = t.allDayEvents || [], t.hours && Array.isArray(t.hours)) {
1769
- const c = /* @__PURE__ */ new Map();
1770
- t.hours.forEach((l) => {
1771
- (l.events || []).forEach((d) => {
1772
- c.has(d.id) || c.set(d.id, d);
1773
- });
1774
- }), a = Array.from(c.values());
1775
- } else
1776
- a = [];
1777
- else if (t.days && t.days.length > 0) {
1778
- const c = t.days.find((d) => this.isSameDay(new Date(d.date), e)) || t.days[0];
1779
- r = new Date(c.date), i = u.getDayName(r.getDay(), o), s = this.isToday(r);
1780
- const l = c.events || [];
1781
- n = l.filter((d) => d.allDay), a = l.filter((d) => !d.allDay);
1782
- } else
1783
- return null;
1784
- return { dayDate: r, dayName: i, isToday: s, allDayEvents: n, timedEvents: a };
1785
- }
1786
- _renderHeader(t, e, r) {
1787
- return `
1316
+ }
1317
+ _extractDayData(e, t) {
1318
+ let n, r, i, a, s, c = this.stateManager.getState().config.locale || "en-US";
1319
+ if (e.type === "day" && e.date) if (n = new Date(e.date), r = e.dayName || o.getDayName(n.getDay(), c), i = e.isToday === void 0 ? this.isToday(n) : e.isToday, a = e.allDayEvents || [], e.hours && Array.isArray(e.hours)) {
1320
+ let t = /* @__PURE__ */ new Map();
1321
+ e.hours.forEach((e) => {
1322
+ (e.events || []).forEach((e) => {
1323
+ t.has(e.id) || t.set(e.id, e);
1324
+ });
1325
+ }), s = Array.from(t.values());
1326
+ } else s = [];
1327
+ else if (e.days && e.days.length > 0) {
1328
+ let l = e.days.find((e) => this.isSameDay(new Date(e.date), t)) || e.days[0];
1329
+ n = new Date(l.date), r = o.getDayName(n.getDay(), c), i = this.isToday(n);
1330
+ let u = l.events || [];
1331
+ a = u.filter((e) => e.allDay), s = u.filter((e) => !e.allDay);
1332
+ } else return null;
1333
+ return {
1334
+ dayDate: n,
1335
+ dayName: r,
1336
+ isToday: i,
1337
+ allDayEvents: a,
1338
+ timedEvents: s
1339
+ };
1340
+ }
1341
+ _renderHeader(e, t, n) {
1342
+ return `
1788
1343
  <div class="fc-day-header" style="display: grid; grid-template-columns: 60px 1fr; border-bottom: 1px solid var(--fc-border-color); background: var(--fc-background-alt); flex-shrink: 0;">
1789
1344
  <div style="border-right: 1px solid var(--fc-border-color);"></div>
1790
1345
  <div style="padding: 16px 24px;">
1791
1346
  <div style="font-size: 12px; font-weight: 700; color: var(--fc-text-light); text-transform: uppercase; letter-spacing: 0.1em;">
1792
- ${this.escapeHTML(e)}
1347
+ ${this.escapeHTML(t)}
1793
1348
  </div>
1794
- <div style="font-size: 24px; font-weight: 600; margin-top: 4px; ${r ? "color: var(--fc-danger-color);" : "color: var(--fc-text-color);"}">
1795
- ${this.escapeHTML(String(t.getDate()))}
1349
+ <div style="font-size: 24px; font-weight: 600; margin-top: 4px; ${n ? "color: var(--fc-danger-color);" : "color: var(--fc-text-color);"}">
1350
+ ${this.escapeHTML(String(e.getDate()))}
1796
1351
  </div>
1797
1352
  </div>
1798
1353
  </div>
1799
1354
  `;
1800
- }
1801
- _renderAllDayRow(t, e) {
1802
- return `
1355
+ }
1356
+ _renderAllDayRow(e, t) {
1357
+ return `
1803
1358
  <div class="fc-all-day-row" style="display: grid; grid-template-columns: 60px 1fr; border-bottom: 1px solid var(--fc-border-color); background: var(--fc-background-alt); min-height: 36px; flex-shrink: 0;">
1804
1359
  <div style="font-size: 9px; color: var(--fc-text-light); display: flex; align-items: center; justify-content: center; border-right: 1px solid var(--fc-border-color); text-transform: uppercase; font-weight: 700;">
1805
1360
  All day
1806
1361
  </div>
1807
- <div class="fc-all-day-cell" data-date="${e.toISOString()}" style="padding: 6px 12px; display: flex; flex-wrap: wrap; gap: 4px;">
1808
- ${t.map(
1809
- (r) => `
1810
- <div class="fc-event fc-all-day-event" data-event-id="${this.escapeHTML(r.id)}"
1811
- style="background-color: ${this.getEventColor(r)}; font-size: 12px; padding: 4px 8px; border-radius: 4px; color: white; cursor: pointer; font-weight: 500;">
1812
- ${this.escapeHTML(r.title)}
1362
+ <div class="fc-all-day-cell" data-date="${t.toISOString()}" style="padding: 6px 12px; display: flex; flex-wrap: wrap; gap: 4px;">
1363
+ ${e.map((e) => `
1364
+ <div class="fc-event fc-all-day-event" data-event-id="${this.escapeHTML(e.id)}"
1365
+ style="background-color: ${this.getEventColor(e)}; font-size: 12px; padding: 4px 8px; border-radius: 4px; color: white; cursor: pointer; font-weight: 500;">
1366
+ ${this.escapeHTML(e.title)}
1813
1367
  </div>
1814
- `
1815
- ).join("")}
1368
+ `).join("")}
1816
1369
  </div>
1817
1370
  </div>
1818
1371
  `;
1819
- }
1820
- _renderTimeGrid(t, e, r, i) {
1821
- return `
1372
+ }
1373
+ _renderTimeGrid(e, t, n, r) {
1374
+ return `
1822
1375
  <div id="day-scroll-container" class="fc-time-grid-container" style="flex: 1; overflow-y: auto; overflow-x: hidden; position: relative;">
1823
1376
  <div class="fc-time-grid" style="display: grid; grid-template-columns: 60px 1fr; position: relative; height: ${this.totalHeight}px;">
1824
- ${this._renderTimeGutter(i)}
1825
- ${this._renderDayColumn(t, e, r, i)}
1377
+ ${this._renderTimeGutter(r)}
1378
+ ${this._renderDayColumn(e, t, n, r)}
1826
1379
  </div>
1827
1380
  </div>
1828
1381
  `;
1829
- }
1830
- _renderTimeGutter(t) {
1831
- return `
1382
+ }
1383
+ _renderTimeGutter(e) {
1384
+ return `
1832
1385
  <div class="fc-time-gutter" style="border-right: 1px solid var(--fc-border-color); background: var(--fc-background-alt);">
1833
- ${t.map(
1834
- (e) => `
1386
+ ${e.map((e) => `
1835
1387
  <div style="height: ${this.hourHeight}px; font-size: 11px; color: var(--fc-text-light); text-align: right; padding-right: 12px; font-weight: 500;">
1836
1388
  ${e === 0 ? "" : this.formatHour(e)}
1837
1389
  </div>
1838
- `
1839
- ).join("")}
1390
+ `).join("")}
1840
1391
  </div>
1841
1392
  `;
1842
- }
1843
- _renderDayColumn(t, e, r, i) {
1844
- return `
1845
- <div class="fc-day-column" data-date="${r.toISOString()}" style="position: relative; cursor: pointer;">
1393
+ }
1394
+ _renderDayColumn(e, t, n, r) {
1395
+ return `
1396
+ <div class="fc-day-column" data-date="${n.toISOString()}" style="position: relative; cursor: pointer;">
1846
1397
  <!-- Hour grid lines -->
1847
- ${i.map(() => `<div style="height: ${this.hourHeight}px; border-bottom: 1px solid var(--fc-background-hover);"></div>`).join("")}
1398
+ ${r.map(() => `<div style="height: ${this.hourHeight}px; border-bottom: 1px solid var(--fc-background-hover);"></div>`).join("")}
1848
1399
 
1849
1400
  <!-- Now indicator for today -->
1850
- ${e ? this.renderNowIndicator() : ""}
1401
+ ${t ? this.renderNowIndicator() : ""}
1851
1402
 
1852
1403
  <!-- Timed events -->
1853
1404
  ${(() => {
1854
- const s = this.computeOverlapLayout(t);
1855
- return t.map(
1856
- (n) => this.renderTimedEvent(n, { compact: !1, overlapLayout: s })
1857
- ).join("");
1858
- })()}
1405
+ let t = this.computeOverlapLayout(e);
1406
+ return e.map((e) => this.renderTimedEvent(e, {
1407
+ compact: !1,
1408
+ overlapLayout: t
1409
+ })).join("");
1410
+ })()}
1859
1411
  </div>
1860
1412
  `;
1861
- }
1862
- _attachEventHandlers() {
1863
- this.addListener(this.container, "click", (t) => {
1864
- const e = t.target.closest(".fc-day-column");
1865
- if (!e || !this.container.contains(e) || t.target.closest(".fc-event")) return;
1866
- const r = new Date(e.dataset.date), i = this.container.querySelector("#day-scroll-container"), s = e.offsetTop, n = t.clientY - e.getBoundingClientRect().top + (i ? i.scrollTop : 0) - s, a = Math.max(0, Math.min(n + s, this.totalHeight));
1867
- r.setHours(
1868
- Math.floor(a / this.hourHeight),
1869
- Math.floor(a % this.hourHeight / (this.hourHeight / 60)),
1870
- 0,
1871
- 0
1872
- ), this.stateManager.selectDate(r);
1873
- }), this.attachCommonEventHandlers();
1874
- }
1875
- _scrollToCurrentTime() {
1876
- if (this._scrolled) return;
1877
- const t = this.container.querySelector("#day-scroll-container");
1878
- t && (t.scrollTop = 8 * this.hourHeight - 50, this._scrolled = !0);
1879
- }
1880
- }
1881
- class A extends $ {
1882
- constructor() {
1883
- super(), this._isVisible = !1, this._cleanupFocusTrap = null, this.config = {
1884
- title: "New Event",
1885
- defaultDuration: 60,
1886
- // minutes
1887
- colors: [
1888
- { color: "#2563EB", label: "Blue" },
1889
- { color: "#10B981", label: "Green" },
1890
- { color: "#F59E0B", label: "Amber" },
1891
- { color: "#EF4444", label: "Red" },
1892
- { color: "#8B5CF6", label: "Purple" },
1893
- { color: "#6B7280", label: "Gray" }
1894
- ]
1895
- }, this._formData = {
1896
- title: "",
1897
- start: /* @__PURE__ */ new Date(),
1898
- end: /* @__PURE__ */ new Date(),
1899
- allDay: !1,
1900
- color: this.config.colors[0].color
1901
- };
1902
- }
1903
- static get observedAttributes() {
1904
- return ["open"];
1905
- }
1906
- attributeChangedCallback(t, e, r) {
1907
- t === "open" && (r !== null ? this.open() : this.close());
1908
- }
1909
- getStyles() {
1910
- return `
1911
- ${f.getBaseStyles()}
1912
- ${f.getButtonStyles()}
1413
+ }
1414
+ _attachEventHandlers() {
1415
+ this.addListener(this.container, "click", (e) => {
1416
+ let t = e.target.closest(".fc-day-column");
1417
+ if (!t || !this.container.contains(t) || e.target.closest(".fc-event")) return;
1418
+ let n = new Date(t.dataset.date), r = this.container.querySelector("#day-scroll-container"), i = t.offsetTop, a = e.clientY - t.getBoundingClientRect().top + (r ? r.scrollTop : 0) - i, o = Math.max(0, Math.min(a + i, this.totalHeight));
1419
+ n.setHours(Math.floor(o / this.hourHeight), Math.floor(o % this.hourHeight / (this.hourHeight / 60)), 0, 0), this.stateManager.selectDate(n);
1420
+ }), this.attachCommonEventHandlers();
1421
+ }
1422
+ _scrollToCurrentTime() {
1423
+ if (this._scrolled) return;
1424
+ let e = this.container.querySelector("#day-scroll-container");
1425
+ e && (e.scrollTop = 8 * this.hourHeight - 50, this._scrolled = !0);
1426
+ }
1427
+ }, p = class extends n {
1428
+ constructor() {
1429
+ super(), this._isVisible = !1, this._cleanupFocusTrap = null, this.config = {
1430
+ title: "New Event",
1431
+ defaultDuration: 60,
1432
+ colors: [
1433
+ {
1434
+ color: "#2563EB",
1435
+ label: "Blue"
1436
+ },
1437
+ {
1438
+ color: "#10B981",
1439
+ label: "Green"
1440
+ },
1441
+ {
1442
+ color: "#F59E0B",
1443
+ label: "Amber"
1444
+ },
1445
+ {
1446
+ color: "#EF4444",
1447
+ label: "Red"
1448
+ },
1449
+ {
1450
+ color: "#8B5CF6",
1451
+ label: "Purple"
1452
+ },
1453
+ {
1454
+ color: "#6B7280",
1455
+ label: "Gray"
1456
+ }
1457
+ ]
1458
+ }, this._formData = {
1459
+ title: "",
1460
+ start: /* @__PURE__ */ new Date(),
1461
+ end: /* @__PURE__ */ new Date(),
1462
+ allDay: !1,
1463
+ color: this.config.colors[0].color
1464
+ };
1465
+ }
1466
+ static get observedAttributes() {
1467
+ return ["open"];
1468
+ }
1469
+ attributeChangedCallback(e, t, n) {
1470
+ e === "open" && (n === null ? this.close() : this.open());
1471
+ }
1472
+ getStyles() {
1473
+ return `
1474
+ ${c.getBaseStyles()}
1475
+ ${c.getButtonStyles()}
1913
1476
 
1914
1477
  :host {
1915
1478
  display: none;
@@ -2078,9 +1641,9 @@ class A extends $ {
2078
1641
  border-color: var(--fc-danger-color);
2079
1642
  }
2080
1643
  `;
2081
- }
2082
- template() {
2083
- return `
1644
+ }
1645
+ template() {
1646
+ return `
2084
1647
  <div class="modal-content" role="dialog" aria-modal="true" aria-labelledby="modal-title">
2085
1648
  <header class="modal-header">
2086
1649
  <h3 class="modal-title" id="modal-title">${this.config.title}</h3>
@@ -2113,18 +1676,16 @@ class A extends $ {
2113
1676
  <div class="form-group">
2114
1677
  <label id="color-label">Color</label>
2115
1678
  <div class="color-options" id="color-picker" role="radiogroup" aria-labelledby="color-label">
2116
- ${this.config.colors.map(
2117
- (t) => `
1679
+ ${this.config.colors.map((e) => `
2118
1680
  <button type="button"
2119
- class="color-btn ${t.color === this._formData.color ? "selected" : ""}"
2120
- style="background-color: ${f.sanitizeColor(t.color)}"
2121
- data-color="${g.escapeHTML(t.color)}"
2122
- title="${g.escapeHTML(t.label)}"
2123
- aria-label="${g.escapeHTML(t.label)}"
2124
- aria-checked="${t.color === this._formData.color ? "true" : "false"}"
1681
+ class="color-btn ${e.color === this._formData.color ? "selected" : ""}"
1682
+ style="background-color: ${c.sanitizeColor(e.color)}"
1683
+ data-color="${s.escapeHTML(e.color)}"
1684
+ title="${s.escapeHTML(e.label)}"
1685
+ aria-label="${s.escapeHTML(e.label)}"
1686
+ aria-checked="${e.color === this._formData.color ? "true" : "false"}"
2125
1687
  role="radio"></button>
2126
- `
2127
- ).join("")}
1688
+ `).join("")}
2128
1689
  </div>
2129
1690
  </div>
2130
1691
  </div>
@@ -2135,226 +1696,201 @@ class A extends $ {
2135
1696
  </footer>
2136
1697
  </div>
2137
1698
  `;
2138
- }
2139
- afterRender() {
2140
- this.modalContent = this.$(".modal-content"), this.titleInput = this.$("#event-title"), this.startInput = this.$("#event-start"), this.endInput = this.$("#event-end"), this.colorContainer = this.$("#color-picker"), this.titleGroup = this.$("#title-group"), this.endGroup = this.$("#end-group"), this.addListener(this.$("#close-x"), "click", () => this.close()), this.addListener(this.$("#cancel-btn"), "click", () => this.close()), this.addListener(this.$("#save-btn"), "click", () => this.save()), this.colorContainer.querySelectorAll(".color-btn").forEach((t) => {
2141
- this.addListener(t, "click", (e) => {
2142
- this._formData.color = e.currentTarget.dataset.color, this.updateColorSelection();
2143
- });
2144
- }), this.addListener(this, "click", (t) => {
2145
- t.target === this && this.close();
2146
- }), this._keydownListenerAdded || (this._handleKeyDown = (t) => {
2147
- t.key === "Escape" && this.hasAttribute("open") && this.close();
2148
- }, window.addEventListener("keydown", this._handleKeyDown), this._keydownListenerAdded = !0);
2149
- }
2150
- updateColorSelection() {
2151
- this.colorContainer.querySelectorAll(".color-btn").forEach((e) => {
2152
- const r = e.dataset.color === this._formData.color;
2153
- e.classList.toggle("selected", r), e.setAttribute("aria-checked", r ? "true" : "false");
2154
- });
2155
- }
2156
- open(t = /* @__PURE__ */ new Date()) {
2157
- this.hasAttribute("open") || this.setAttribute("open", ""), this.titleGroup.classList.remove("has-error"), this.endGroup.classList.remove("has-error"), this._formData.start = t, this._formData.end = new Date(t.getTime() + this.config.defaultDuration * 60 * 1e3), this._formData.title = "", this._formData.color = this.config.colors[0].color, this.startInput && (this.titleInput.value = "", this.startInput.value = this.formatDateForInput(this._formData.start), this.endInput.value = this.formatDateForInput(this._formData.end), this.updateColorSelection(), this._cleanupFocusTrap && this._cleanupFocusTrap(), this._cleanupFocusTrap = g.trapFocus(this.modalContent));
2158
- }
2159
- close() {
2160
- this.removeAttribute("open"), this._cleanupFocusTrap && (this._cleanupFocusTrap(), this._cleanupFocusTrap = null);
2161
- }
2162
- validate() {
2163
- let t = !0;
2164
- this.titleGroup.classList.remove("has-error"), this.endGroup.classList.remove("has-error"), this.titleInput.value.trim() || (this.titleGroup.classList.add("has-error"), t = !1);
2165
- const e = new Date(this.startInput.value);
2166
- return new Date(this.endInput.value) <= e && (this.endGroup.classList.add("has-error"), t = !1), t;
2167
- }
2168
- save() {
2169
- if (!this.validate()) return;
2170
- const t = {
2171
- title: this.titleInput.value.trim(),
2172
- start: new Date(this.startInput.value),
2173
- end: new Date(this.endInput.value),
2174
- backgroundColor: this._formData.color
2175
- };
2176
- this.emit("save", t), this.close();
2177
- }
2178
- formatDateForInput(t) {
2179
- const e = (o) => String(o).padStart(2, "0"), r = t.getFullYear(), i = e(t.getMonth() + 1), s = e(t.getDate()), n = e(t.getHours()), a = e(t.getMinutes());
2180
- return `${r}-${i}-${s}T${n}:${a}`;
2181
- }
2182
- unmount() {
2183
- this._cleanupFocusTrap && this._cleanupFocusTrap(), this._handleKeyDown && (window.removeEventListener("keydown", this._handleKeyDown), this._handleKeyDown = null, this._keydownListenerAdded = !1);
2184
- }
2185
- }
2186
- customElements.get("forcecal-event-form") || customElements.define("forcecal-event-form", A);
2187
- const b = class b extends $ {
2188
- static get observedAttributes() {
2189
- return ["view", "date", "locale", "timezone", "week-starts-on", "height"];
2190
- }
2191
- constructor() {
2192
- super(), this.stateManager = null, this.currentView = null, this._hasRendered = !1, this._busUnsubscribers = [];
2193
- }
2194
- /**
2195
- * Route observed-attribute changes into the StateManager.
2196
- * Without this, attribute updates after mount (view, locale, timezone,
2197
- * week-starts-on, date) only re-rendered from stale state and had no
2198
- * visible effect. 'height' is presentational and picked up by render().
2199
- */
2200
- propChanged(t, e, r) {
2201
- if (!(!this.stateManager || e === r))
2202
- switch (t) {
2203
- case "view":
2204
- r && this.stateManager.setView(r);
2205
- break;
2206
- case "date": {
2207
- const i = r ? new Date(r) : null;
2208
- i && !isNaN(i.getTime()) && this.stateManager.setDate(i);
2209
- break;
2210
- }
2211
- case "locale":
2212
- r && this.stateManager.updateConfig({ locale: r });
2213
- break;
2214
- case "timezone":
2215
- r && this.stateManager.updateConfig({ timeZone: r });
2216
- break;
2217
- case "week-starts-on": {
2218
- const i = parseInt(r, 10);
2219
- Number.isNaN(i) || this.stateManager.updateConfig({ weekStartsOn: i });
2220
- break;
2221
- }
2222
- }
2223
- }
2224
- initialize() {
2225
- const t = {
2226
- view: this.getAttribute("view") || "month",
2227
- date: this.getAttribute("date") ? new Date(this.getAttribute("date")) : /* @__PURE__ */ new Date(),
2228
- locale: this.getAttribute("locale") || "en-US",
2229
- timeZone: this.getAttribute("timezone") || Intl.DateTimeFormat().resolvedOptions().timeZone,
2230
- weekStartsOn: parseInt(this.getAttribute("week-starts-on") || "0")
2231
- };
2232
- this.stateManager = new z(t), this._stateUnsubscribe = this.stateManager.subscribe(this.handleStateChange.bind(this)), this.setupEventListeners();
2233
- }
2234
- setupEventListeners() {
2235
- this._busUnsubscribers.forEach((r) => r()), this._busUnsubscribers = [];
2236
- const t = this.stateManager.eventBus;
2237
- this._busUnsubscribers.push(
2238
- t.on("navigation:*", (r, i) => {
2239
- this.emit("calendar-navigate", { action: i.split(":")[1], ...r });
2240
- })
2241
- ), this._busUnsubscribers.push(
2242
- t.on("view:changed", (r) => {
2243
- this.emit("calendar-view-change", r);
2244
- })
2245
- );
2246
- const e = (r, i) => {
2247
- this.emit(`calendar-event-${r}`, i);
2248
- };
2249
- this._busUnsubscribers.push(
2250
- t.on("event:add", (r) => {
2251
- e("add", r);
2252
- })
2253
- ), this._busUnsubscribers.push(
2254
- t.on("event:update", (r) => {
2255
- e("update", r);
2256
- })
2257
- ), this._busUnsubscribers.push(
2258
- t.on("event:remove", (r) => {
2259
- e("remove", r);
2260
- })
2261
- ), this._busUnsubscribers.push(
2262
- t.on("event:added", (r) => {
2263
- this.emit("calendar-event-added", r);
2264
- })
2265
- ), this._busUnsubscribers.push(
2266
- t.on("event:updated", (r) => {
2267
- this.emit("calendar-event-updated", r);
2268
- })
2269
- ), this._busUnsubscribers.push(
2270
- t.on("event:deleted", (r) => {
2271
- this.emit("calendar-event-deleted", r);
2272
- })
2273
- ), this._busUnsubscribers.push(
2274
- t.on("date:selected", (r) => {
2275
- this.emit("calendar-date-select", r);
2276
- })
2277
- );
2278
- }
2279
- handleStateChange(t, e) {
2280
- var o, c;
2281
- if (!this._hasRendered)
2282
- return;
2283
- const r = t.view !== (e == null ? void 0 : e.view), i = ((o = t.currentDate) == null ? void 0 : o.getTime()) !== ((c = e == null ? void 0 : e.currentDate) == null ? void 0 : c.getTime()), s = t.events !== (e == null ? void 0 : e.events), n = t.loading !== (e == null ? void 0 : e.loading);
2284
- if (t.error !== (e == null ? void 0 : e.error)) {
2285
- this.render();
2286
- return;
2287
- }
2288
- if (n) {
2289
- this._updateLoadingState(t.loading);
2290
- return;
2291
- }
2292
- r && (this.currentView = t.view), r ? (this._updateTitle(), this._updateViewButtons(), this._switchView()) : i ? (this._updateTitle(), this._updateViewContent()) : s && this._updateViewContent();
2293
- }
2294
- /**
2295
- * Update only the title text (no DOM recreation)
2296
- */
2297
- _updateTitle() {
2298
- const t = this.$(".fc-title");
2299
- if (t) {
2300
- const e = this.stateManager.getState();
2301
- t.textContent = this.getTitle(e.currentDate, e.view);
2302
- }
2303
- }
2304
- /**
2305
- * Update view button active states (no DOM recreation)
2306
- */
2307
- _updateViewButtons() {
2308
- const t = this.stateManager.getState();
2309
- this.$$("[data-view]").forEach((e) => {
2310
- const r = e.dataset.view === t.view;
2311
- e.classList.toggle("active", r);
2312
- });
2313
- }
2314
- /**
2315
- * Switch to a different view type
2316
- */
2317
- _switchView() {
2318
- const t = this.$("#calendar-view-container");
2319
- if (t) {
2320
- this._currentViewInstance && this._currentViewInstance.cleanup && this._currentViewInstance.cleanup();
2321
- try {
2322
- const e = b.RENDERERS[this.currentView] || _, r = new e(t, this.stateManager);
2323
- r._viewType = this.currentView, this._currentViewInstance = r, r.render();
2324
- } catch (e) {
2325
- console.error("[ForceCalendar] Error switching view:", e);
2326
- }
2327
- }
2328
- }
2329
- /**
2330
- * Re-render only the view content (not header)
2331
- */
2332
- _updateViewContent() {
2333
- this._currentViewInstance && this._currentViewInstance.render && this._currentViewInstance.render();
2334
- }
2335
- /**
2336
- * Toggle loading overlay without rebuilding the component tree.
2337
- */
2338
- _updateLoadingState(t) {
2339
- const e = this.$(".fc-loading"), r = this.$(".fc-view-container");
2340
- e && (e.style.display = t ? "flex" : "none"), r && (r.style.display = t ? "none" : "flex");
2341
- }
2342
- mount() {
2343
- this.currentView = this.stateManager.getView(), super.mount();
2344
- }
2345
- loadView(t) {
2346
- !t || this.currentView === t || (this.currentView = t, this._switchView(), this._updateViewButtons(), this._updateTitle());
2347
- }
2348
- getStyles() {
2349
- const t = this.getAttribute("height") || "800px";
2350
- return `
2351
- ${f.getBaseStyles()}
2352
- ${f.getButtonStyles()}
2353
- ${f.getGridStyles()}
2354
- ${f.getAnimations()}
1699
+ }
1700
+ afterRender() {
1701
+ this.modalContent = this.$(".modal-content"), this.titleInput = this.$("#event-title"), this.startInput = this.$("#event-start"), this.endInput = this.$("#event-end"), this.colorContainer = this.$("#color-picker"), this.titleGroup = this.$("#title-group"), this.endGroup = this.$("#end-group"), this.addListener(this.$("#close-x"), "click", () => this.close()), this.addListener(this.$("#cancel-btn"), "click", () => this.close()), this.addListener(this.$("#save-btn"), "click", () => this.save()), this.colorContainer.querySelectorAll(".color-btn").forEach((e) => {
1702
+ this.addListener(e, "click", (e) => {
1703
+ this._formData.color = e.currentTarget.dataset.color, this.updateColorSelection();
1704
+ });
1705
+ }), this.addListener(this, "click", (e) => {
1706
+ e.target === this && this.close();
1707
+ }), this._keydownListenerAdded ||= (this._handleKeyDown = (e) => {
1708
+ e.key === "Escape" && this.hasAttribute("open") && this.close();
1709
+ }, window.addEventListener("keydown", this._handleKeyDown), !0);
1710
+ }
1711
+ updateColorSelection() {
1712
+ this.colorContainer.querySelectorAll(".color-btn").forEach((e) => {
1713
+ let t = e.dataset.color === this._formData.color;
1714
+ e.classList.toggle("selected", t), e.setAttribute("aria-checked", t ? "true" : "false");
1715
+ });
1716
+ }
1717
+ open(e = /* @__PURE__ */ new Date()) {
1718
+ this.hasAttribute("open") || this.setAttribute("open", ""), this.titleGroup.classList.remove("has-error"), this.endGroup.classList.remove("has-error"), this._formData.start = e, this._formData.end = new Date(e.getTime() + this.config.defaultDuration * 60 * 1e3), this._formData.title = "", this._formData.color = this.config.colors[0].color, this.startInput && (this.titleInput.value = "", this.startInput.value = this.formatDateForInput(this._formData.start), this.endInput.value = this.formatDateForInput(this._formData.end), this.updateColorSelection(), this._cleanupFocusTrap && this._cleanupFocusTrap(), this._cleanupFocusTrap = s.trapFocus(this.modalContent));
1719
+ }
1720
+ close() {
1721
+ this.removeAttribute("open"), this._cleanupFocusTrap &&= (this._cleanupFocusTrap(), null);
1722
+ }
1723
+ validate() {
1724
+ let e = !0;
1725
+ this.titleGroup.classList.remove("has-error"), this.endGroup.classList.remove("has-error"), this.titleInput.value.trim() || (this.titleGroup.classList.add("has-error"), e = !1);
1726
+ let t = new Date(this.startInput.value);
1727
+ return new Date(this.endInput.value) <= t && (this.endGroup.classList.add("has-error"), e = !1), e;
1728
+ }
1729
+ save() {
1730
+ if (!this.validate()) return;
1731
+ let e = {
1732
+ title: this.titleInput.value.trim(),
1733
+ start: new Date(this.startInput.value),
1734
+ end: new Date(this.endInput.value),
1735
+ backgroundColor: this._formData.color
1736
+ };
1737
+ this.emit("save", e), this.close();
1738
+ }
1739
+ formatDateForInput(e) {
1740
+ let t = (e) => String(e).padStart(2, "0");
1741
+ return `${e.getFullYear()}-${t(e.getMonth() + 1)}-${t(e.getDate())}T${t(e.getHours())}:${t(e.getMinutes())}`;
1742
+ }
1743
+ unmount() {
1744
+ this._cleanupFocusTrap && this._cleanupFocusTrap(), this._handleKeyDown && (window.removeEventListener("keydown", this._handleKeyDown), this._handleKeyDown = null, this._keydownListenerAdded = !1);
1745
+ }
1746
+ };
1747
+ customElements.get("forcecal-event-form") || customElements.define("forcecal-event-form", p);
1748
+ //#endregion
1749
+ //#region src/components/ForceCalendar.js
1750
+ var m = class e extends n {
1751
+ static RENDERERS = {
1752
+ month: u,
1753
+ week: d,
1754
+ day: f
1755
+ };
1756
+ static get observedAttributes() {
1757
+ return [
1758
+ "view",
1759
+ "date",
1760
+ "locale",
1761
+ "timezone",
1762
+ "week-starts-on",
1763
+ "height"
1764
+ ];
1765
+ }
1766
+ constructor() {
1767
+ super(), this.stateManager = null, this.currentView = null, this._hasRendered = !1, this._busUnsubscribers = [];
1768
+ }
1769
+ propChanged(e, t, n) {
1770
+ if (!(!this.stateManager || t === n)) switch (e) {
1771
+ case "view":
1772
+ n && this.stateManager.setView(n);
1773
+ break;
1774
+ case "date": {
1775
+ let e = n ? new Date(n) : null;
1776
+ e && !isNaN(e.getTime()) && this.stateManager.setDate(e);
1777
+ break;
1778
+ }
1779
+ case "locale":
1780
+ n && this.stateManager.updateConfig({ locale: n });
1781
+ break;
1782
+ case "timezone":
1783
+ n && this.stateManager.updateConfig({ timeZone: n });
1784
+ break;
1785
+ case "week-starts-on": {
1786
+ let e = parseInt(n, 10);
1787
+ Number.isNaN(e) || this.stateManager.updateConfig({ weekStartsOn: e });
1788
+ break;
1789
+ }
1790
+ }
1791
+ }
1792
+ initialize() {
1793
+ let e = {
1794
+ view: this.getAttribute("view") || "month",
1795
+ date: this.getAttribute("date") ? new Date(this.getAttribute("date")) : /* @__PURE__ */ new Date(),
1796
+ locale: this.getAttribute("locale") || "en-US",
1797
+ timeZone: this.getAttribute("timezone") || Intl.DateTimeFormat().resolvedOptions().timeZone,
1798
+ weekStartsOn: parseInt(this.getAttribute("week-starts-on") || "0")
1799
+ };
1800
+ this.stateManager = new a(e), this._stateUnsubscribe = this.stateManager.subscribe(this.handleStateChange.bind(this)), this.setupEventListeners();
1801
+ }
1802
+ setupEventListeners() {
1803
+ this._busUnsubscribers.forEach((e) => e()), this._busUnsubscribers = [];
1804
+ let e = this.stateManager.eventBus;
1805
+ this._busUnsubscribers.push(e.on("navigation:*", (e, t) => {
1806
+ this.emit("calendar-navigate", {
1807
+ action: t.split(":")[1],
1808
+ ...e
1809
+ });
1810
+ })), this._busUnsubscribers.push(e.on("view:changed", (e) => {
1811
+ this.emit("calendar-view-change", e);
1812
+ }));
1813
+ let t = (e, t) => {
1814
+ this.emit(`calendar-event-${e}`, t);
1815
+ };
1816
+ this._busUnsubscribers.push(e.on("event:add", (e) => {
1817
+ t("add", e);
1818
+ })), this._busUnsubscribers.push(e.on("event:update", (e) => {
1819
+ t("update", e);
1820
+ })), this._busUnsubscribers.push(e.on("event:remove", (e) => {
1821
+ t("remove", e);
1822
+ })), this._busUnsubscribers.push(e.on("event:added", (e) => {
1823
+ this.emit("calendar-event-added", e);
1824
+ })), this._busUnsubscribers.push(e.on("event:updated", (e) => {
1825
+ this.emit("calendar-event-updated", e);
1826
+ })), this._busUnsubscribers.push(e.on("event:deleted", (e) => {
1827
+ this.emit("calendar-event-deleted", e);
1828
+ })), this._busUnsubscribers.push(e.on("date:selected", (e) => {
1829
+ this.emit("calendar-date-select", e);
1830
+ }));
1831
+ }
1832
+ handleStateChange(e, t) {
1833
+ if (!this._hasRendered) return;
1834
+ let n = e.view !== t?.view, r = e.currentDate?.getTime() !== t?.currentDate?.getTime(), i = e.events !== t?.events, a = e.loading !== t?.loading;
1835
+ if (e.error !== t?.error) {
1836
+ this.render();
1837
+ return;
1838
+ }
1839
+ if (a) {
1840
+ this._updateLoadingState(e.loading);
1841
+ return;
1842
+ }
1843
+ n && (this.currentView = e.view), n ? (this._updateTitle(), this._updateViewButtons(), this._switchView()) : r ? (this._updateTitle(), this._updateViewContent()) : i && this._updateViewContent();
1844
+ }
1845
+ _updateTitle() {
1846
+ let e = this.$(".fc-title");
1847
+ if (e) {
1848
+ let t = this.stateManager.getState();
1849
+ e.textContent = this.getTitle(t.currentDate, t.view);
1850
+ }
1851
+ }
1852
+ _updateViewButtons() {
1853
+ let e = this.stateManager.getState();
1854
+ this.$$("[data-view]").forEach((t) => {
1855
+ let n = t.dataset.view === e.view;
1856
+ t.classList.toggle("active", n);
1857
+ });
1858
+ }
1859
+ _switchView() {
1860
+ let t = this.$("#calendar-view-container");
1861
+ if (t) {
1862
+ this._currentViewInstance && this._currentViewInstance.cleanup && this._currentViewInstance.cleanup();
1863
+ try {
1864
+ let n = new (e.RENDERERS[this.currentView] || u)(t, this.stateManager);
1865
+ n._viewType = this.currentView, this._currentViewInstance = n, n.render();
1866
+ } catch (e) {
1867
+ console.error("[ForceCalendar] Error switching view:", e);
1868
+ }
1869
+ }
1870
+ }
1871
+ _updateViewContent() {
1872
+ this._currentViewInstance && this._currentViewInstance.render && this._currentViewInstance.render();
1873
+ }
1874
+ _updateLoadingState(e) {
1875
+ let t = this.$(".fc-loading"), n = this.$(".fc-view-container");
1876
+ t && (t.style.display = e ? "flex" : "none"), n && (n.style.display = e ? "none" : "flex");
1877
+ }
1878
+ mount() {
1879
+ this.currentView = this.stateManager.getView(), super.mount();
1880
+ }
1881
+ loadView(e) {
1882
+ !e || this.currentView === e || (this.currentView = e, this._switchView(), this._updateViewButtons(), this._updateTitle());
1883
+ }
1884
+ getStyles() {
1885
+ let e = this.getAttribute("height") || "800px";
1886
+ return `
1887
+ ${c.getBaseStyles()}
1888
+ ${c.getButtonStyles()}
1889
+ ${c.getGridStyles()}
1890
+ ${c.getAnimations()}
2355
1891
 
2356
1892
  :host {
2357
- --calendar-height: ${t};
1893
+ --calendar-height: ${e};
2358
1894
  display: block;
2359
1895
  font-family: var(--fc-font-family);
2360
1896
  }
@@ -2452,6 +1988,20 @@ const b = class b extends $ {
2452
1988
  padding: 0;
2453
1989
  }
2454
1990
 
1991
+ .fc-month-day:focus-visible,
1992
+ .fc-event:focus-visible {
1993
+ outline: 2px solid var(--fc-primary-color);
1994
+ outline-offset: -2px;
1995
+ }
1996
+
1997
+ .fc-nav-arrow:focus-visible,
1998
+ .fc-btn:focus-visible,
1999
+ .fc-btn-today:focus-visible,
2000
+ .fc-view-button:focus-visible {
2001
+ outline: 2px solid var(--fc-primary-color);
2002
+ outline-offset: 2px;
2003
+ }
2004
+
2455
2005
  .fc-nav-arrow:hover {
2456
2006
  background: var(--fc-background-hover);
2457
2007
  color: var(--fc-text-color);
@@ -2733,19 +2283,18 @@ const b = class b extends $ {
2733
2283
  background: var(--fc-background);
2734
2284
  }
2735
2285
  `;
2736
- }
2737
- template() {
2738
- const t = this.stateManager.getState(), { currentDate: e, view: r, loading: i, error: s } = t;
2739
- if (s)
2740
- return `
2286
+ }
2287
+ template() {
2288
+ let { currentDate: e, view: t, loading: n, error: r } = this.stateManager.getState();
2289
+ if (r) return `
2741
2290
  <div class="force-calendar">
2742
2291
  <div class="fc-error">
2743
- <p><strong>Error:</strong> ${g.escapeHTML(s.message || "An error occurred")}</p>
2292
+ <p><strong>Error:</strong> ${s.escapeHTML(r.message || "An error occurred")}</p>
2744
2293
  </div>
2745
2294
  </div>
2746
2295
  `;
2747
- const n = this.getTitle(e, r);
2748
- return `
2296
+ let i = this.getTitle(e, t);
2297
+ return `
2749
2298
  <div class="force-calendar">
2750
2299
  <header class="fc-header">
2751
2300
  <div class="fc-header-left">
@@ -2755,11 +2304,11 @@ const b = class b extends $ {
2755
2304
  </div>
2756
2305
 
2757
2306
  <div class="fc-header-center">
2758
- <button class="fc-nav-arrow" data-action="previous" title="Previous">
2307
+ <button class="fc-nav-arrow" data-action="previous" title="Previous" aria-label="Previous ${t}">
2759
2308
  ${this.getIcon("chevron-left")}
2760
2309
  </button>
2761
- <h2 class="fc-title">${g.escapeHTML(n)}</h2>
2762
- <button class="fc-nav-arrow" data-action="next" title="Next">
2310
+ <h2 class="fc-title" aria-live="polite">${s.escapeHTML(i)}</h2>
2311
+ <button class="fc-nav-arrow" data-action="next" title="Next" aria-label="Next ${t}">
2763
2312
  ${this.getIcon("chevron-right")}
2764
2313
  </button>
2765
2314
  </div>
@@ -2768,23 +2317,26 @@ const b = class b extends $ {
2768
2317
  <button class="fc-btn fc-btn-primary" id="create-event-btn" style="height: 28px; padding: 0 12px; font-size: 12px;">
2769
2318
  + New Event
2770
2319
  </button>
2771
- <div class="fc-view-buttons" role="group">
2772
- <button class="fc-view-button ${r === "month" ? "active" : ""}"
2320
+ <div class="fc-view-buttons" role="group" aria-label="Calendar view">
2321
+ <button class="fc-view-button ${t === "month" ? "active" : ""}"
2322
+ aria-pressed="${t === "month" ? "true" : "false"}"
2773
2323
  data-view="month">Month</button>
2774
- <button class="fc-view-button ${r === "week" ? "active" : ""}"
2324
+ <button class="fc-view-button ${t === "week" ? "active" : ""}"
2325
+ aria-pressed="${t === "week" ? "true" : "false"}"
2775
2326
  data-view="week">Week</button>
2776
- <button class="fc-view-button ${r === "day" ? "active" : ""}"
2327
+ <button class="fc-view-button ${t === "day" ? "active" : ""}"
2328
+ aria-pressed="${t === "day" ? "true" : "false"}"
2777
2329
  data-view="day">Day</button>
2778
2330
  </div>
2779
2331
  </div>
2780
2332
  </header>
2781
2333
 
2782
2334
  <div class="fc-body">
2783
- <div class="fc-loading" style="display: ${i ? "flex" : "none"};">
2335
+ <div class="fc-loading" style="display: ${n ? "flex" : "none"};">
2784
2336
  <div class="fc-spinner"></div>
2785
2337
  <span>Loading...</span>
2786
2338
  </div>
2787
- <div class="fc-view-container" style="display: ${i ? "none" : "flex"};">
2339
+ <div class="fc-view-container" style="display: ${n ? "none" : "flex"};">
2788
2340
  ${this.renderView()}
2789
2341
  </div>
2790
2342
  </div>
@@ -2792,146 +2344,112 @@ const b = class b extends $ {
2792
2344
  <forcecal-event-form id="event-modal"></forcecal-event-form>
2793
2345
  </div>
2794
2346
  `;
2795
- }
2796
- renderView() {
2797
- return '<div id="calendar-view-container"></div>';
2798
- }
2799
- afterRender() {
2800
- const t = this.$("#calendar-view-container");
2801
- if (t && this.stateManager && this.currentView) {
2802
- if (this._currentViewInstance && this._currentViewInstance._viewType === this.currentView && t.children.length > 0)
2803
- return;
2804
- this._currentViewInstance && (this._currentViewInstance.cleanup && this._currentViewInstance.cleanup(), this._viewUnsubscribe && (this._viewUnsubscribe(), this._viewUnsubscribe = null));
2805
- try {
2806
- const i = b.RENDERERS[this.currentView] || _, s = new i(t, this.stateManager);
2807
- s._viewType = this.currentView, this._currentViewInstance = s, s.render();
2808
- } catch (i) {
2809
- console.error("[ForceCalendar] Error creating/rendering view:", i);
2810
- }
2811
- }
2812
- this.$$("[data-action]").forEach((i) => {
2813
- this.addListener(i, "click", this.handleNavigation);
2814
- }), this.$$("[data-view]").forEach((i) => {
2815
- this.addListener(i, "click", this.handleViewChange);
2816
- });
2817
- const e = this.$("#event-modal"), r = this.$("#create-event-btn");
2818
- r && e && this.addListener(r, "click", () => {
2819
- e.open(/* @__PURE__ */ new Date());
2820
- }), this.addListener(this.shadowRoot, "day-click", (i) => {
2821
- e && e.open(i.detail.date);
2822
- }), e && this.addListener(e, "save", (i) => {
2823
- const s = i.detail, n = window.crypto && typeof window.crypto.randomUUID == "function" ? window.crypto.randomUUID() : Math.random().toString(36).substring(2, 15);
2824
- this.stateManager.addEvent({
2825
- id: n,
2826
- ...s
2827
- });
2828
- }), this._hasRendered = !0;
2829
- }
2830
- handleNavigation(t) {
2831
- switch (t.currentTarget.dataset.action) {
2832
- case "today":
2833
- this.stateManager.today();
2834
- break;
2835
- case "previous":
2836
- this.stateManager.previous();
2837
- break;
2838
- case "next":
2839
- this.stateManager.next();
2840
- break;
2841
- }
2842
- }
2843
- handleViewChange(t) {
2844
- const e = t.currentTarget.dataset.view;
2845
- this.stateManager.setView(e);
2846
- }
2847
- getTitle(t, e) {
2848
- const r = this.stateManager.getState().config.locale;
2849
- switch (e) {
2850
- case "month":
2851
- return u.formatDate(t, "month", r);
2852
- case "week": {
2853
- const i = u.startOfWeek(t), s = u.endOfWeek(t);
2854
- return u.formatDateRange(i, s, r);
2855
- }
2856
- case "day":
2857
- return u.formatDate(t, "long", r);
2858
- default:
2859
- return u.formatDate(t, "month", r);
2860
- }
2861
- }
2862
- getIcon(t) {
2863
- return {
2864
- "chevron-left": `
2865
- <svg class="fc-icon" viewBox="0 0 24 24">
2866
- <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
2867
- </svg>
2868
- `,
2869
- "chevron-right": `
2870
- <svg class="fc-icon" viewBox="0 0 24 24">
2871
- <path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
2872
- </svg>
2873
- `,
2874
- calendar: `
2875
- <svg class="fc-icon" viewBox="0 0 24 24">
2876
- <path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"/>
2877
- </svg>
2878
- `
2879
- }[t] || "";
2880
- }
2881
- // Public API methods
2882
- addEvent(t) {
2883
- return this.stateManager.addEvent(t);
2884
- }
2885
- updateEvent(t, e) {
2886
- return this.stateManager.updateEvent(t, e);
2887
- }
2888
- deleteEvent(t) {
2889
- return this.stateManager.deleteEvent(t);
2890
- }
2891
- getEvents() {
2892
- return this.stateManager.getEvents();
2893
- }
2894
- setView(t) {
2895
- this.stateManager.setView(t);
2896
- }
2897
- setDate(t) {
2898
- this.stateManager.setDate(t);
2899
- }
2900
- next() {
2901
- this.stateManager.next();
2902
- }
2903
- previous() {
2904
- this.stateManager.previous();
2905
- }
2906
- today() {
2907
- this.stateManager.today();
2908
- }
2909
- unmount() {
2910
- this.destroy();
2911
- }
2912
- destroy() {
2913
- this._busUnsubscribers.forEach((t) => t()), this._busUnsubscribers = [], this._stateUnsubscribe && (this._stateUnsubscribe(), this._stateUnsubscribe = null), this._currentViewInstance && this._currentViewInstance.cleanup && (this._currentViewInstance.cleanup(), this._currentViewInstance = null), this.stateManager && this.stateManager.destroy(), super.cleanup();
2914
- }
2915
- };
2916
- m(b, "RENDERERS", {
2917
- month: _,
2918
- week: V,
2919
- day: B
2920
- });
2921
- let k = b;
2922
- customElements.get("forcecal-main") || customElements.define("forcecal-main", k);
2923
- export {
2924
- $ as BaseComponent,
2925
- D as BaseViewRenderer,
2926
- g as DOMUtils,
2927
- u as DateUtils,
2928
- B as DayViewRenderer,
2929
- S as EventBus,
2930
- k as ForceCalendar,
2931
- _ as MonthViewRenderer,
2932
- z as StateManager,
2933
- f as StyleUtils,
2934
- V as WeekViewRenderer,
2935
- j as eventBus
2347
+ }
2348
+ renderView() {
2349
+ return "<div id=\"calendar-view-container\"></div>";
2350
+ }
2351
+ afterRender() {
2352
+ let t = this.$("#calendar-view-container");
2353
+ if (t && this.stateManager && this.currentView) {
2354
+ if (this._currentViewInstance && this._currentViewInstance._viewType === this.currentView && t.children.length > 0) return;
2355
+ this._currentViewInstance && (this._currentViewInstance.cleanup && this._currentViewInstance.cleanup(), this._viewUnsubscribe &&= (this._viewUnsubscribe(), null));
2356
+ try {
2357
+ let n = new (e.RENDERERS[this.currentView] || u)(t, this.stateManager);
2358
+ n._viewType = this.currentView, this._currentViewInstance = n, n.render();
2359
+ } catch (e) {
2360
+ console.error("[ForceCalendar] Error creating/rendering view:", e);
2361
+ }
2362
+ }
2363
+ this.$$("[data-action]").forEach((e) => {
2364
+ this.addListener(e, "click", this.handleNavigation);
2365
+ }), this.$$("[data-view]").forEach((e) => {
2366
+ this.addListener(e, "click", this.handleViewChange);
2367
+ });
2368
+ let n = this.$("#event-modal"), r = this.$("#create-event-btn");
2369
+ r && n && this.addListener(r, "click", () => {
2370
+ n.open(/* @__PURE__ */ new Date());
2371
+ }), this.addListener(this.shadowRoot, "day-click", (e) => {
2372
+ n && n.open(e.detail.date);
2373
+ }), n && this.addListener(n, "save", (e) => {
2374
+ let t = e.detail, n = window.crypto && typeof window.crypto.randomUUID == "function" ? window.crypto.randomUUID() : Math.random().toString(36).substring(2, 15);
2375
+ this.stateManager.addEvent({
2376
+ id: n,
2377
+ ...t
2378
+ });
2379
+ }), this._hasRendered = !0;
2380
+ }
2381
+ handleNavigation(e) {
2382
+ switch (e.currentTarget.dataset.action) {
2383
+ case "today":
2384
+ this.stateManager.today();
2385
+ break;
2386
+ case "previous":
2387
+ this.stateManager.previous();
2388
+ break;
2389
+ case "next":
2390
+ this.stateManager.next();
2391
+ break;
2392
+ }
2393
+ }
2394
+ handleViewChange(e) {
2395
+ let t = e.currentTarget.dataset.view;
2396
+ this.stateManager.setView(t);
2397
+ }
2398
+ getTitle(e, t) {
2399
+ let n = this.stateManager.getState().config.locale;
2400
+ switch (t) {
2401
+ case "month": return o.formatDate(e, "month", n);
2402
+ case "week": {
2403
+ let t = o.startOfWeek(e), r = o.endOfWeek(e);
2404
+ return o.formatDateRange(t, r, n);
2405
+ }
2406
+ case "day": return o.formatDate(e, "long", n);
2407
+ default: return o.formatDate(e, "month", n);
2408
+ }
2409
+ }
2410
+ getIcon(e) {
2411
+ return {
2412
+ "chevron-left": "\n <svg class=\"fc-icon\" viewBox=\"0 0 24 24\">\n <path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\"/>\n </svg>\n ",
2413
+ "chevron-right": "\n <svg class=\"fc-icon\" viewBox=\"0 0 24 24\">\n <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\"/>\n </svg>\n ",
2414
+ calendar: "\n <svg class=\"fc-icon\" viewBox=\"0 0 24 24\">\n <path d=\"M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z\"/>\n </svg>\n "
2415
+ }[e] || "";
2416
+ }
2417
+ addEvent(e) {
2418
+ return this.stateManager.addEvent(e);
2419
+ }
2420
+ updateEvent(e, t) {
2421
+ return this.stateManager.updateEvent(e, t);
2422
+ }
2423
+ deleteEvent(e) {
2424
+ return this.stateManager.deleteEvent(e);
2425
+ }
2426
+ getEvents() {
2427
+ return this.stateManager.getEvents();
2428
+ }
2429
+ setView(e) {
2430
+ this.stateManager.setView(e);
2431
+ }
2432
+ setDate(e) {
2433
+ this.stateManager.setDate(e);
2434
+ }
2435
+ next() {
2436
+ this.stateManager.next();
2437
+ }
2438
+ previous() {
2439
+ this.stateManager.previous();
2440
+ }
2441
+ today() {
2442
+ this.stateManager.today();
2443
+ }
2444
+ unmount() {
2445
+ this.destroy();
2446
+ }
2447
+ destroy() {
2448
+ this._busUnsubscribers.forEach((e) => e()), this._busUnsubscribers = [], this._stateUnsubscribe &&= (this._stateUnsubscribe(), null), this._currentViewInstance && this._currentViewInstance.cleanup && (this._currentViewInstance.cleanup(), this._currentViewInstance = null), this.stateManager && this.stateManager.destroy(), super.cleanup();
2449
+ }
2936
2450
  };
2937
- //# sourceMappingURL=force-calendar-interface.esm.js.map
2451
+ customElements.get("forcecal-main") || customElements.define("forcecal-main", m);
2452
+ //#endregion
2453
+ export { n as BaseComponent, l as BaseViewRenderer, s as DOMUtils, o as DateUtils, f as DayViewRenderer, r as EventBus, m as ForceCalendar, u as MonthViewRenderer, a as StateManager, c as StyleUtils, d as WeekViewRenderer, i as eventBus };
2454
+
2455
+ //# sourceMappingURL=force-calendar-interface.esm.js.map