@forcecalendar/interface 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/force-calendar-interface.esm.js +97 -2
- package/dist/force-calendar-interface.esm.js.map +1 -1
- package/dist/force-calendar-interface.umd.js +6 -5
- package/dist/force-calendar-interface.umd.js.map +1 -1
- package/package.json +10 -4
- package/src/components/ForceCalendar.js +1 -0
- package/src/renderers/BaseViewRenderer.js +140 -0
- package/src/renderers/DayViewRenderer.js +6 -1
- package/src/renderers/WeekViewRenderer.js +7 -1
- package/types/components/EventForm.d.ts +42 -0
- package/types/components/ForceCalendar.d.ts +78 -0
- package/types/core/BaseComponent.d.ts +68 -0
- package/types/core/EventBus.d.ts +71 -0
- package/types/core/StateManager.d.ts +112 -0
- package/types/index.d.ts +17 -0
- package/types/renderers/BaseViewRenderer.d.ts +151 -0
- package/types/renderers/DayViewRenderer.d.ts +28 -0
- package/types/renderers/MonthViewRenderer.d.ts +28 -0
- package/types/renderers/WeekViewRenderer.d.ts +21 -0
- package/types/renderers/index.d.ts +10 -0
- package/types/utils/DOMUtils.d.ts +121 -0
- package/types/utils/DateUtils.d.ts +61 -0
- package/types/utils/StyleUtils.d.ts +146 -0
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forcecalendar/interface",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Official interface layer for forceCalendar Core - Enterprise calendar components",
|
|
6
6
|
"main": "dist/force-calendar-interface.umd.js",
|
|
7
7
|
"module": "dist/force-calendar-interface.esm.js",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
+
"types": "./types/index.d.ts",
|
|
10
11
|
"import": "./dist/force-calendar-interface.esm.js",
|
|
11
12
|
"require": "./dist/force-calendar-interface.umd.js"
|
|
12
13
|
}
|
|
@@ -15,7 +16,8 @@
|
|
|
15
16
|
"jsdelivr": "dist/force-calendar-interface.umd.js",
|
|
16
17
|
"files": [
|
|
17
18
|
"dist",
|
|
18
|
-
"src"
|
|
19
|
+
"src",
|
|
20
|
+
"types/**/*.d.ts"
|
|
19
21
|
],
|
|
20
22
|
"sideEffects": [
|
|
21
23
|
"./src/index.js",
|
|
@@ -26,7 +28,9 @@
|
|
|
26
28
|
"dev": "vite",
|
|
27
29
|
"build": "vite build",
|
|
28
30
|
"preview": "vite preview",
|
|
29
|
-
"test": "jest --testTimeout=10000"
|
|
31
|
+
"test": "jest --testTimeout=10000",
|
|
32
|
+
"build:types": "tsc -p tsconfig.types.json",
|
|
33
|
+
"prepublishOnly": "npm run build && npm run build:types"
|
|
30
34
|
},
|
|
31
35
|
"repository": {
|
|
32
36
|
"type": "git",
|
|
@@ -60,6 +64,8 @@
|
|
|
60
64
|
"jest": "^30.2.0",
|
|
61
65
|
"jest-environment-jsdom": "^30.2.0",
|
|
62
66
|
"prettier": "^3.8.1",
|
|
67
|
+
"typescript": "^7.0.2",
|
|
63
68
|
"vite": "^8.1.4"
|
|
64
|
-
}
|
|
69
|
+
},
|
|
70
|
+
"types": "./types/index.d.ts"
|
|
65
71
|
}
|
|
@@ -329,6 +329,146 @@ export class BaseViewRenderer {
|
|
|
329
329
|
}
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
/**
|
|
333
|
+
* WAI-ARIA semantics and keyboard navigation for time grids (week/day
|
|
334
|
+
* views). The DOM is column-major (a column per day, an hour line per
|
|
335
|
+
* slot), so each day column is exposed as a row of 24 hour gridcells.
|
|
336
|
+
* Arrow keys navigate visually: Up/Down moves hours, Left/Right moves
|
|
337
|
+
* days, Home/End jumps to the day's bounds, PageUp/PageDown navigates
|
|
338
|
+
* periods, Enter/Space selects the slot's date and time.
|
|
339
|
+
* @param {string} columnSelector - Selector for the day columns
|
|
340
|
+
* @param {string} gridLabel - Accessible label for the grid
|
|
341
|
+
*/
|
|
342
|
+
_enhanceTimeGridAccessibility(columnSelector, gridLabel) {
|
|
343
|
+
const grid = this.container.querySelector('.fc-time-grid');
|
|
344
|
+
if (!grid) return;
|
|
345
|
+
grid.setAttribute('role', 'grid');
|
|
346
|
+
grid.setAttribute('aria-label', gridLabel);
|
|
347
|
+
const gutter = this.container.querySelector('.fc-time-gutter');
|
|
348
|
+
if (gutter) gutter.setAttribute('aria-hidden', 'true');
|
|
349
|
+
|
|
350
|
+
const locale = this.stateManager.getState().config.locale || 'en-US';
|
|
351
|
+
const dayFormatter = new Intl.DateTimeFormat(locale, {
|
|
352
|
+
weekday: 'long',
|
|
353
|
+
month: 'long',
|
|
354
|
+
day: 'numeric'
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const columns = Array.from(this.container.querySelectorAll(columnSelector));
|
|
358
|
+
for (const col of columns) {
|
|
359
|
+
col.setAttribute('role', 'row');
|
|
360
|
+
const colDate = new Date(col.dataset.date);
|
|
361
|
+
const colLabel = dayFormatter.format(colDate);
|
|
362
|
+
col.setAttribute('aria-label', colLabel);
|
|
363
|
+
for (const slot of col.querySelectorAll('.fc-hour-slot')) {
|
|
364
|
+
slot.setAttribute('role', 'gridcell');
|
|
365
|
+
slot.setAttribute('tabindex', '-1');
|
|
366
|
+
slot.setAttribute(
|
|
367
|
+
'aria-label',
|
|
368
|
+
`${colLabel}, ${this.formatHour(Number(slot.dataset.hour))}`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
this._applySlotRovingTabindex(columns);
|
|
374
|
+
|
|
375
|
+
if (this._pendingSlotFocus) {
|
|
376
|
+
const { colIndex, hour } = this._pendingSlotFocus;
|
|
377
|
+
this._pendingSlotFocus = null;
|
|
378
|
+
const target = this._slotAt(columns, colIndex, hour);
|
|
379
|
+
if (target) {
|
|
380
|
+
this._applySlotRovingTabindex(columns, target);
|
|
381
|
+
target.focus();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
this.addListener(this.container, 'keydown', e => {
|
|
386
|
+
const slot = e.target.closest('.fc-hour-slot');
|
|
387
|
+
if (!slot || !this.container.contains(slot)) return;
|
|
388
|
+
const col = slot.closest(columnSelector);
|
|
389
|
+
const colIndex = columns.indexOf(col);
|
|
390
|
+
const hour = Number(slot.dataset.hour);
|
|
391
|
+
let target = null;
|
|
392
|
+
|
|
393
|
+
switch (e.key) {
|
|
394
|
+
case 'ArrowDown':
|
|
395
|
+
target = this._slotAt(columns, colIndex, hour + 1);
|
|
396
|
+
break;
|
|
397
|
+
case 'ArrowUp':
|
|
398
|
+
target = this._slotAt(columns, colIndex, hour - 1);
|
|
399
|
+
break;
|
|
400
|
+
case 'ArrowRight':
|
|
401
|
+
target = this._slotAt(columns, colIndex + 1, hour);
|
|
402
|
+
break;
|
|
403
|
+
case 'ArrowLeft':
|
|
404
|
+
target = this._slotAt(columns, colIndex - 1, hour);
|
|
405
|
+
break;
|
|
406
|
+
case 'Home':
|
|
407
|
+
target = this._slotAt(columns, colIndex, 0);
|
|
408
|
+
break;
|
|
409
|
+
case 'End':
|
|
410
|
+
target = this._slotAt(columns, colIndex, 23);
|
|
411
|
+
break;
|
|
412
|
+
case 'PageUp':
|
|
413
|
+
case 'PageDown':
|
|
414
|
+
e.preventDefault();
|
|
415
|
+
this._pendingSlotFocus = { colIndex, hour };
|
|
416
|
+
if (e.key === 'PageDown') {
|
|
417
|
+
this.stateManager.next();
|
|
418
|
+
} else {
|
|
419
|
+
this.stateManager.previous();
|
|
420
|
+
}
|
|
421
|
+
return;
|
|
422
|
+
case 'Enter':
|
|
423
|
+
case ' ': {
|
|
424
|
+
e.preventDefault();
|
|
425
|
+
const date = new Date(col.dataset.date);
|
|
426
|
+
date.setHours(hour, 0, 0, 0);
|
|
427
|
+
this._pendingSlotFocus = { colIndex, hour };
|
|
428
|
+
this.stateManager.selectDate(date);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
default:
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
e.preventDefault();
|
|
436
|
+
if (target) {
|
|
437
|
+
this._applySlotRovingTabindex(columns, target);
|
|
438
|
+
target.scrollIntoView?.({ block: 'nearest' });
|
|
439
|
+
target.focus();
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Get the hour slot at a column/hour position
|
|
446
|
+
* @private
|
|
447
|
+
*/
|
|
448
|
+
_slotAt(columns, colIndex, hour) {
|
|
449
|
+
if (colIndex < 0 || colIndex >= columns.length || hour < 0 || hour > 23) return null;
|
|
450
|
+
return columns[colIndex].querySelector(`.fc-hour-slot[data-hour="${hour}"]`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Keep exactly one hour slot tabbable. Defaults to 9:00 AM in the
|
|
455
|
+
* first column (today's column when present).
|
|
456
|
+
* @private
|
|
457
|
+
*/
|
|
458
|
+
_applySlotRovingTabindex(columns, active = null) {
|
|
459
|
+
const slots = this.container.querySelectorAll('.fc-hour-slot');
|
|
460
|
+
if (slots.length === 0) return;
|
|
461
|
+
if (!active) {
|
|
462
|
+
const todayCol =
|
|
463
|
+
columns.find(c => this.isToday(new Date(c.dataset.date))) || columns[0];
|
|
464
|
+
active = todayCol && todayCol.querySelector('.fc-hour-slot[data-hour="9"]');
|
|
465
|
+
active = active || slots[0];
|
|
466
|
+
}
|
|
467
|
+
for (const s of slots) {
|
|
468
|
+
s.setAttribute('tabindex', s === active ? '0' : '-1');
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
332
472
|
/**
|
|
333
473
|
* Make rendered events keyboard-reachable and screen-reader labeled.
|
|
334
474
|
* Runs after every render, across all views.
|
|
@@ -165,7 +165,7 @@ export class DayViewRenderer extends BaseViewRenderer {
|
|
|
165
165
|
return `
|
|
166
166
|
<div class="fc-day-column" data-date="${dayDate.toISOString()}" style="position: relative; cursor: pointer;">
|
|
167
167
|
<!-- Hour grid lines -->
|
|
168
|
-
${hours.map(
|
|
168
|
+
${hours.map(h => `<div class="fc-hour-slot" data-hour="${h}" style="height: ${this.hourHeight}px; border-bottom: 1px solid var(--fc-background-hover);"></div>`).join('')}
|
|
169
169
|
|
|
170
170
|
<!-- Now indicator for today -->
|
|
171
171
|
${isToday ? this.renderNowIndicator() : ''}
|
|
@@ -211,6 +211,11 @@ export class DayViewRenderer extends BaseViewRenderer {
|
|
|
211
211
|
|
|
212
212
|
// Common event handlers (event clicks)
|
|
213
213
|
this.attachCommonEventHandlers();
|
|
214
|
+
|
|
215
|
+
const locale = this.stateManager.getState().config.locale || 'en-US';
|
|
216
|
+
const current = this.stateManager.getState().currentDate || new Date();
|
|
217
|
+
const label = new Intl.DateTimeFormat(locale, { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }).format(current);
|
|
218
|
+
this._enhanceTimeGridAccessibility('.fc-day-column', label);
|
|
214
219
|
}
|
|
215
220
|
|
|
216
221
|
_scrollToCurrentTime() {
|
|
@@ -144,7 +144,7 @@ export class WeekViewRenderer extends BaseViewRenderer {
|
|
|
144
144
|
return `
|
|
145
145
|
<div class="fc-week-day-column" data-date="${day.date.toISOString()}" style="border-right: 1px solid var(--fc-border-color); position: relative; cursor: pointer;">
|
|
146
146
|
<!-- Hour grid lines -->
|
|
147
|
-
${hours.map(
|
|
147
|
+
${hours.map(h => `<div class="fc-hour-slot" data-hour="${h}" style="height: ${this.hourHeight}px; border-bottom: 1px solid var(--fc-background-hover);"></div>`).join('')}
|
|
148
148
|
|
|
149
149
|
<!-- Now indicator for today -->
|
|
150
150
|
${day.isToday ? this.renderNowIndicator() : ''}
|
|
@@ -190,6 +190,12 @@ export class WeekViewRenderer extends BaseViewRenderer {
|
|
|
190
190
|
|
|
191
191
|
// Common event handlers (event clicks)
|
|
192
192
|
this.attachCommonEventHandlers();
|
|
193
|
+
|
|
194
|
+
const days = this.stateManager.getViewData()?.days || [];
|
|
195
|
+
const locale = this.stateManager.getState().config.locale || 'en-US';
|
|
196
|
+
const first = days[0] ? new Date(days[0].date) : new Date();
|
|
197
|
+
const label = `Week of ${new Intl.DateTimeFormat(locale, { month: 'long', day: 'numeric', year: 'numeric' }).format(first)}`;
|
|
198
|
+
this._enhanceTimeGridAccessibility('.fc-week-day-column', label);
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
_scrollToCurrentTime() {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { BaseComponent } from '../core/BaseComponent.js';
|
|
2
|
+
export declare class EventForm extends BaseComponent {
|
|
3
|
+
_isVisible: boolean;
|
|
4
|
+
_cleanupFocusTrap: (() => any) | null;
|
|
5
|
+
config: {
|
|
6
|
+
title: string;
|
|
7
|
+
defaultDuration: number;
|
|
8
|
+
colors: {
|
|
9
|
+
color: string;
|
|
10
|
+
label: string;
|
|
11
|
+
}[];
|
|
12
|
+
};
|
|
13
|
+
_formData: {
|
|
14
|
+
title: string;
|
|
15
|
+
start: Date;
|
|
16
|
+
end: Date;
|
|
17
|
+
allDay: boolean;
|
|
18
|
+
color: string;
|
|
19
|
+
};
|
|
20
|
+
modalContent: any;
|
|
21
|
+
titleInput: any;
|
|
22
|
+
startInput: any;
|
|
23
|
+
endInput: any;
|
|
24
|
+
colorContainer: any;
|
|
25
|
+
titleGroup: any;
|
|
26
|
+
endGroup: any;
|
|
27
|
+
_handleKeyDown: ((e: any) => void) | null | undefined;
|
|
28
|
+
_keydownListenerAdded: boolean | undefined;
|
|
29
|
+
constructor();
|
|
30
|
+
static get observedAttributes(): string[];
|
|
31
|
+
attributeChangedCallback(name: any, oldValue: any, newValue: any): void;
|
|
32
|
+
getStyles(): string;
|
|
33
|
+
template(): string;
|
|
34
|
+
afterRender(): void;
|
|
35
|
+
updateColorSelection(): void;
|
|
36
|
+
open(initialDate?: Date): void;
|
|
37
|
+
close(): void;
|
|
38
|
+
validate(): boolean;
|
|
39
|
+
save(): void;
|
|
40
|
+
formatDateForInput(date: any): string;
|
|
41
|
+
unmount(): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ForceCalendar - Main calendar component
|
|
3
|
+
*
|
|
4
|
+
* The primary interface component that integrates all views and features
|
|
5
|
+
*/
|
|
6
|
+
import { BaseComponent } from '../core/BaseComponent.js';
|
|
7
|
+
import StateManager from '../core/StateManager.js';
|
|
8
|
+
import { MonthViewRenderer } from '../renderers/MonthViewRenderer.js';
|
|
9
|
+
import { WeekViewRenderer } from '../renderers/WeekViewRenderer.js';
|
|
10
|
+
import { DayViewRenderer } from '../renderers/DayViewRenderer.js';
|
|
11
|
+
import './EventForm.js';
|
|
12
|
+
export declare class ForceCalendar extends BaseComponent {
|
|
13
|
+
stateManager: StateManager | null;
|
|
14
|
+
currentView: any;
|
|
15
|
+
_hasRendered: boolean;
|
|
16
|
+
_busUnsubscribers: any[];
|
|
17
|
+
_stateUnsubscribe: (() => void) | null | undefined;
|
|
18
|
+
_currentViewInstance: any;
|
|
19
|
+
_viewUnsubscribe: any;
|
|
20
|
+
static RENDERERS: {
|
|
21
|
+
month: typeof MonthViewRenderer;
|
|
22
|
+
week: typeof WeekViewRenderer;
|
|
23
|
+
day: typeof DayViewRenderer;
|
|
24
|
+
};
|
|
25
|
+
static get observedAttributes(): string[];
|
|
26
|
+
constructor();
|
|
27
|
+
/**
|
|
28
|
+
* Route observed-attribute changes into the StateManager.
|
|
29
|
+
* Without this, attribute updates after mount (view, locale, timezone,
|
|
30
|
+
* week-starts-on, date) only re-rendered from stale state and had no
|
|
31
|
+
* visible effect. 'height' is presentational and picked up by render().
|
|
32
|
+
*/
|
|
33
|
+
propChanged(name: any, oldValue: any, newValue: any): void;
|
|
34
|
+
initialize(): void;
|
|
35
|
+
setupEventListeners(): void;
|
|
36
|
+
handleStateChange(newState: any, oldState: any): void;
|
|
37
|
+
/**
|
|
38
|
+
* Update only the title text (no DOM recreation)
|
|
39
|
+
*/
|
|
40
|
+
_updateTitle(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Update view button active states (no DOM recreation)
|
|
43
|
+
*/
|
|
44
|
+
_updateViewButtons(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Switch to a different view type
|
|
47
|
+
*/
|
|
48
|
+
_switchView(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Re-render only the view content (not header)
|
|
51
|
+
*/
|
|
52
|
+
_updateViewContent(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Toggle loading overlay without rebuilding the component tree.
|
|
55
|
+
*/
|
|
56
|
+
_updateLoadingState(isLoading: any): void;
|
|
57
|
+
mount(): void;
|
|
58
|
+
loadView(viewType: any): void;
|
|
59
|
+
getStyles(): string;
|
|
60
|
+
template(): string;
|
|
61
|
+
renderView(): string;
|
|
62
|
+
afterRender(): void;
|
|
63
|
+
handleNavigation(event: any): void;
|
|
64
|
+
handleViewChange(event: any): void;
|
|
65
|
+
getTitle(date: any, view: any): string;
|
|
66
|
+
getIcon(name: any): any;
|
|
67
|
+
addEvent(event: any): any;
|
|
68
|
+
updateEvent(eventId: any, updates: any): any;
|
|
69
|
+
deleteEvent(eventId: any): boolean;
|
|
70
|
+
getEvents(): any;
|
|
71
|
+
setView(view: any): void;
|
|
72
|
+
setDate(date: any): void;
|
|
73
|
+
next(): void;
|
|
74
|
+
previous(): void;
|
|
75
|
+
today(): void;
|
|
76
|
+
unmount(): void;
|
|
77
|
+
destroy(): void;
|
|
78
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseComponent - Foundation for all Force Calendar Web Components
|
|
3
|
+
*
|
|
4
|
+
* Provides common functionality:
|
|
5
|
+
* - Shadow DOM setup
|
|
6
|
+
* - Event handling
|
|
7
|
+
* - State management integration
|
|
8
|
+
* - Lifecycle management
|
|
9
|
+
* - Style management
|
|
10
|
+
*/
|
|
11
|
+
export declare class BaseComponent extends HTMLElement {
|
|
12
|
+
_listeners: any[];
|
|
13
|
+
_state: any;
|
|
14
|
+
_props: Map<any, any>;
|
|
15
|
+
_initialized: boolean;
|
|
16
|
+
_styleEl: HTMLStyleElement | null | undefined;
|
|
17
|
+
_contentWrapper: HTMLDivElement | null | undefined;
|
|
18
|
+
constructor();
|
|
19
|
+
connectedCallback(): void;
|
|
20
|
+
disconnectedCallback(): void;
|
|
21
|
+
initialize(): void;
|
|
22
|
+
mount(): void;
|
|
23
|
+
unmount(): void;
|
|
24
|
+
cleanup(): void;
|
|
25
|
+
setState(newState: any): void;
|
|
26
|
+
getState(): any;
|
|
27
|
+
stateChanged(_oldState: any, _newState: any): void;
|
|
28
|
+
setProp(key: any, value: any): void;
|
|
29
|
+
getProp(key: any): any;
|
|
30
|
+
propChanged(_key: any, _oldValue: any, _newValue: any): void;
|
|
31
|
+
addListener(element: any, event: any, handler: any): void;
|
|
32
|
+
emit(eventName: any, detail?: {}): void;
|
|
33
|
+
getStyles(): string;
|
|
34
|
+
getBaseStyles(): string;
|
|
35
|
+
render(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Save scroll positions of all scrollable containers within shadow DOM
|
|
38
|
+
* @returns {Map<string, {top: number, left: number}>}
|
|
39
|
+
*/
|
|
40
|
+
_saveScrollPositions(): Map<string, {
|
|
41
|
+
top: number;
|
|
42
|
+
left: number;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Restore previously saved scroll positions
|
|
46
|
+
* @param {Map<string, {top: number, left: number}>} positions
|
|
47
|
+
*/
|
|
48
|
+
_restoreScrollPositions(positions: Map<string, {
|
|
49
|
+
top: number;
|
|
50
|
+
left: number;
|
|
51
|
+
}>): void;
|
|
52
|
+
/**
|
|
53
|
+
* Get a CSS selector for the currently focused element within shadow DOM
|
|
54
|
+
* @returns {string|null}
|
|
55
|
+
*/
|
|
56
|
+
_getActiveElementSelector(): string | null;
|
|
57
|
+
/**
|
|
58
|
+
* Restore focus to a previously focused element
|
|
59
|
+
* @param {string|null} selector
|
|
60
|
+
*/
|
|
61
|
+
_restoreFocus(selector: string | null): void;
|
|
62
|
+
template(): string;
|
|
63
|
+
afterRender(): void;
|
|
64
|
+
$(selector: any): any;
|
|
65
|
+
$$(selector: any): NodeListOf<any>;
|
|
66
|
+
static get observedAttributes(): never[];
|
|
67
|
+
attributeChangedCallback(name: any, oldValue: any, newValue: any): void;
|
|
68
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventBus - Central event communication system
|
|
3
|
+
*
|
|
4
|
+
* Enables decoupled communication between components
|
|
5
|
+
* Supports event namespacing and wildcard subscriptions
|
|
6
|
+
*/
|
|
7
|
+
declare class EventBus {
|
|
8
|
+
events: Map<any, any>;
|
|
9
|
+
wildcardHandlers: Set<any>;
|
|
10
|
+
constructor();
|
|
11
|
+
/**
|
|
12
|
+
* Subscribe to an event
|
|
13
|
+
* @param {string} eventName - Event name or pattern (supports wildcards)
|
|
14
|
+
* @param {Function} handler - Event handler function
|
|
15
|
+
* @param {Object} options - Subscription options
|
|
16
|
+
* @returns {Function} Unsubscribe function
|
|
17
|
+
*/
|
|
18
|
+
on(eventName: string, handler: Function, options?: Object): Function;
|
|
19
|
+
/**
|
|
20
|
+
* Subscribe to an event that fires only once
|
|
21
|
+
*/
|
|
22
|
+
once(eventName: any, handler: any, options?: {}): Function;
|
|
23
|
+
/**
|
|
24
|
+
* Unsubscribe from an event
|
|
25
|
+
*/
|
|
26
|
+
off(eventName: any, handler: any): void;
|
|
27
|
+
/**
|
|
28
|
+
* Remove all wildcard handlers matching a pattern
|
|
29
|
+
* @param {string} pattern - Pattern to match (e.g., 'event:*')
|
|
30
|
+
*/
|
|
31
|
+
offWildcard(pattern: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* Remove all handlers (regular and wildcard) for a specific handler function
|
|
34
|
+
* Useful for cleanup when a component is destroyed
|
|
35
|
+
* @param {Function} handler - Handler function to remove
|
|
36
|
+
*/
|
|
37
|
+
offAll(handler: Function): void;
|
|
38
|
+
/**
|
|
39
|
+
* Emit an event synchronously
|
|
40
|
+
* @param {string} eventName - Event name
|
|
41
|
+
* @param {*} data - Event data
|
|
42
|
+
*/
|
|
43
|
+
emit(eventName: string, data: any): void;
|
|
44
|
+
/**
|
|
45
|
+
* Check if event name matches a pattern
|
|
46
|
+
* Only `*` acts as a wildcard; all other characters match literally
|
|
47
|
+
*/
|
|
48
|
+
matchesPattern(eventName: any, pattern: any): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Clear all event subscriptions
|
|
51
|
+
*/
|
|
52
|
+
clear(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Get all registered event names
|
|
55
|
+
*/
|
|
56
|
+
getEventNames(): any[];
|
|
57
|
+
/**
|
|
58
|
+
* Get handler count for an event
|
|
59
|
+
*/
|
|
60
|
+
getHandlerCount(eventName: any): any;
|
|
61
|
+
/**
|
|
62
|
+
* Get wildcard handler count
|
|
63
|
+
*/
|
|
64
|
+
getWildcardHandlerCount(): number;
|
|
65
|
+
/**
|
|
66
|
+
* Get total handler count (for debugging/monitoring)
|
|
67
|
+
*/
|
|
68
|
+
getTotalHandlerCount(): number;
|
|
69
|
+
}
|
|
70
|
+
declare const eventBus: EventBus;
|
|
71
|
+
export { EventBus, eventBus as default };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StateManager - Centralized state management for Force Calendar
|
|
3
|
+
*
|
|
4
|
+
* Wraps the @forcecalendar/core Calendar instance
|
|
5
|
+
* Provides reactive state updates and component synchronization
|
|
6
|
+
*/
|
|
7
|
+
import { EventBus } from './EventBus.js';
|
|
8
|
+
declare class StateManager {
|
|
9
|
+
eventBus: EventBus;
|
|
10
|
+
calendar: any;
|
|
11
|
+
state: {
|
|
12
|
+
view: any;
|
|
13
|
+
currentDate: any;
|
|
14
|
+
events: never[];
|
|
15
|
+
selectedEvent: null;
|
|
16
|
+
selectedDate: null;
|
|
17
|
+
loading: boolean;
|
|
18
|
+
error: null;
|
|
19
|
+
config: {};
|
|
20
|
+
};
|
|
21
|
+
subscribers: Set<any>;
|
|
22
|
+
_subscriberIds: Map<any, any> | null | undefined;
|
|
23
|
+
constructor(config?: {});
|
|
24
|
+
/**
|
|
25
|
+
* Sync state.events from Core calendar (single source of truth)
|
|
26
|
+
* This ensures state.events always matches Core's event store.
|
|
27
|
+
*
|
|
28
|
+
* @param {object} options
|
|
29
|
+
* @param {boolean} options.silent - suppress subscriber notifications
|
|
30
|
+
* @param {boolean} options.force - always update even when IDs match
|
|
31
|
+
* (required after updateEvent where IDs
|
|
32
|
+
* are unchanged but content has changed)
|
|
33
|
+
*/
|
|
34
|
+
_syncEventsFromCore(options?: {
|
|
35
|
+
silent: boolean;
|
|
36
|
+
force: boolean;
|
|
37
|
+
}): any;
|
|
38
|
+
/**
|
|
39
|
+
* Check if two event arrays have the same events by id.
|
|
40
|
+
* Only used for add/delete guards — updateEvent must pass force:true
|
|
41
|
+
* to bypass this check because IDs are unchanged after an update.
|
|
42
|
+
*/
|
|
43
|
+
_eventsMatch(arr1: any, arr2: any): any;
|
|
44
|
+
getState(): {
|
|
45
|
+
view: any;
|
|
46
|
+
currentDate: any;
|
|
47
|
+
selectedEvent: null;
|
|
48
|
+
selectedDate: null;
|
|
49
|
+
loading: boolean;
|
|
50
|
+
error: null;
|
|
51
|
+
config: {};
|
|
52
|
+
events: never[];
|
|
53
|
+
};
|
|
54
|
+
setState(updates: any, options?: {}): {
|
|
55
|
+
view: any;
|
|
56
|
+
currentDate: any;
|
|
57
|
+
events: never[];
|
|
58
|
+
selectedEvent: null;
|
|
59
|
+
selectedDate: null;
|
|
60
|
+
loading: boolean;
|
|
61
|
+
error: null;
|
|
62
|
+
config: {};
|
|
63
|
+
};
|
|
64
|
+
subscribe(callback: any, subscriberId?: null): () => void;
|
|
65
|
+
unsubscribe(callback: any, subscriberId?: null): void;
|
|
66
|
+
/**
|
|
67
|
+
* Unsubscribe by subscriber ID
|
|
68
|
+
* @param {string} subscriberId - ID used when subscribing
|
|
69
|
+
*/
|
|
70
|
+
unsubscribeById(subscriberId: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Get subscriber count (for debugging/monitoring)
|
|
73
|
+
*/
|
|
74
|
+
getSubscriberCount(): number;
|
|
75
|
+
notifySubscribers(oldState: any, newState: any): void;
|
|
76
|
+
emitStateChange(oldState: any, newState: any): void;
|
|
77
|
+
setView(view: any): void;
|
|
78
|
+
getView(): any;
|
|
79
|
+
setDate(date: any): void;
|
|
80
|
+
getCurrentDate(): any;
|
|
81
|
+
next(): void;
|
|
82
|
+
previous(): void;
|
|
83
|
+
today(): void;
|
|
84
|
+
goToDate(date: any): void;
|
|
85
|
+
addEvent(event: any): any;
|
|
86
|
+
updateEvent(eventId: any, updates: any): any;
|
|
87
|
+
deleteEvent(eventId: any): boolean;
|
|
88
|
+
getEvents(): any;
|
|
89
|
+
/**
|
|
90
|
+
* Force sync state.events from Core calendar
|
|
91
|
+
* Use this if you've modified events directly on the Core calendar
|
|
92
|
+
*/
|
|
93
|
+
syncEvents(): any;
|
|
94
|
+
getEventsForDate(date: any): any;
|
|
95
|
+
getEventsInRange(start: any, end: any): any;
|
|
96
|
+
getViewData(): any;
|
|
97
|
+
enrichViewData(viewData: any): any;
|
|
98
|
+
selectEvent(event: any): void;
|
|
99
|
+
selectEventById(eventId: any): void;
|
|
100
|
+
deselectEvent(): void;
|
|
101
|
+
selectDate(date: any): void;
|
|
102
|
+
deselectDate(): void;
|
|
103
|
+
isToday(date: any): boolean;
|
|
104
|
+
isSelectedDate(date: any): null;
|
|
105
|
+
isWeekend(date: any): boolean;
|
|
106
|
+
setLoading(loading: any): void;
|
|
107
|
+
setError(error: any): void;
|
|
108
|
+
clearError(): void;
|
|
109
|
+
updateConfig(config: any): void;
|
|
110
|
+
destroy(): void;
|
|
111
|
+
}
|
|
112
|
+
export default StateManager;
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Force Calendar Interface
|
|
3
|
+
* Main entry point for the component library
|
|
4
|
+
*
|
|
5
|
+
* A solid foundation for calendar interfaces built on @forcecalendar/core
|
|
6
|
+
*/
|
|
7
|
+
export { BaseComponent } from './core/BaseComponent.js';
|
|
8
|
+
export { default as StateManager } from './core/StateManager.js';
|
|
9
|
+
export { default as eventBus, EventBus } from './core/EventBus.js';
|
|
10
|
+
export { DateUtils } from './utils/DateUtils.js';
|
|
11
|
+
export { DOMUtils } from './utils/DOMUtils.js';
|
|
12
|
+
export { StyleUtils } from './utils/StyleUtils.js';
|
|
13
|
+
export { BaseViewRenderer } from './renderers/BaseViewRenderer.js';
|
|
14
|
+
export { MonthViewRenderer } from './renderers/MonthViewRenderer.js';
|
|
15
|
+
export { WeekViewRenderer } from './renderers/WeekViewRenderer.js';
|
|
16
|
+
export { DayViewRenderer } from './renderers/DayViewRenderer.js';
|
|
17
|
+
export { ForceCalendar } from './components/ForceCalendar.js';
|