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