@forcecalendar/interface 1.0.61 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/force-calendar-interface.esm.js +1669 -2151
- package/dist/force-calendar-interface.esm.js.map +1 -1
- package/dist/force-calendar-interface.umd.js +141 -119
- package/dist/force-calendar-interface.umd.js.map +1 -1
- package/package.json +11 -5
- package/src/components/ForceCalendar.js +21 -4
- package/src/renderers/BaseViewRenderer.js +39 -5
- package/src/renderers/MonthViewRenderer.js +140 -4
- 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 +122 -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.0
|
|
3
|
+
"version": "1.2.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",
|
|
63
|
-
"
|
|
64
|
-
|
|
67
|
+
"typescript": "^7.0.2",
|
|
68
|
+
"vite": "^8.1.4"
|
|
69
|
+
},
|
|
70
|
+
"types": "./types/index.d.ts"
|
|
65
71
|
}
|
|
@@ -396,6 +396,20 @@ export class ForceCalendar extends BaseComponent {
|
|
|
396
396
|
padding: 0;
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
.fc-month-day:focus-visible,
|
|
400
|
+
.fc-event:focus-visible {
|
|
401
|
+
outline: 2px solid var(--fc-primary-color);
|
|
402
|
+
outline-offset: -2px;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.fc-nav-arrow:focus-visible,
|
|
406
|
+
.fc-btn:focus-visible,
|
|
407
|
+
.fc-btn-today:focus-visible,
|
|
408
|
+
.fc-view-button:focus-visible {
|
|
409
|
+
outline: 2px solid var(--fc-primary-color);
|
|
410
|
+
outline-offset: 2px;
|
|
411
|
+
}
|
|
412
|
+
|
|
399
413
|
.fc-nav-arrow:hover {
|
|
400
414
|
background: var(--fc-background-hover);
|
|
401
415
|
color: var(--fc-text-color);
|
|
@@ -705,11 +719,11 @@ export class ForceCalendar extends BaseComponent {
|
|
|
705
719
|
</div>
|
|
706
720
|
|
|
707
721
|
<div class="fc-header-center">
|
|
708
|
-
<button class="fc-nav-arrow" data-action="previous" title="Previous">
|
|
722
|
+
<button class="fc-nav-arrow" data-action="previous" title="Previous" aria-label="Previous ${view}">
|
|
709
723
|
${this.getIcon('chevron-left')}
|
|
710
724
|
</button>
|
|
711
|
-
<h2 class="fc-title">${DOMUtils.escapeHTML(title)}</h2>
|
|
712
|
-
<button class="fc-nav-arrow" data-action="next" title="Next">
|
|
725
|
+
<h2 class="fc-title" aria-live="polite">${DOMUtils.escapeHTML(title)}</h2>
|
|
726
|
+
<button class="fc-nav-arrow" data-action="next" title="Next" aria-label="Next ${view}">
|
|
713
727
|
${this.getIcon('chevron-right')}
|
|
714
728
|
</button>
|
|
715
729
|
</div>
|
|
@@ -718,12 +732,15 @@ export class ForceCalendar extends BaseComponent {
|
|
|
718
732
|
<button class="fc-btn fc-btn-primary" id="create-event-btn" style="height: 28px; padding: 0 12px; font-size: 12px;">
|
|
719
733
|
+ New Event
|
|
720
734
|
</button>
|
|
721
|
-
<div class="fc-view-buttons" role="group">
|
|
735
|
+
<div class="fc-view-buttons" role="group" aria-label="Calendar view">
|
|
722
736
|
<button class="fc-view-button ${view === 'month' ? 'active' : ''}"
|
|
737
|
+
aria-pressed="${view === 'month' ? 'true' : 'false'}"
|
|
723
738
|
data-view="month">Month</button>
|
|
724
739
|
<button class="fc-view-button ${view === 'week' ? 'active' : ''}"
|
|
740
|
+
aria-pressed="${view === 'week' ? 'true' : 'false'}"
|
|
725
741
|
data-view="week">Week</button>
|
|
726
742
|
<button class="fc-view-button ${view === 'day' ? 'active' : ''}"
|
|
743
|
+
aria-pressed="${view === 'day' ? 'true' : 'false'}"
|
|
727
744
|
data-view="day">Day</button>
|
|
728
745
|
</div>
|
|
729
746
|
</div>
|
|
@@ -304,12 +304,46 @@ export class BaseViewRenderer {
|
|
|
304
304
|
if (!eventEl || !this.container.contains(eventEl)) return;
|
|
305
305
|
|
|
306
306
|
e.stopPropagation();
|
|
307
|
-
|
|
308
|
-
const event = this.stateManager.getEvents().find(ev => ev.id === eventId);
|
|
309
|
-
if (event) {
|
|
310
|
-
this.stateManager.selectEvent(event);
|
|
311
|
-
}
|
|
307
|
+
this._selectEventFromElement(eventEl);
|
|
312
308
|
});
|
|
309
|
+
|
|
310
|
+
// Keyboard activation for events (Enter / Space)
|
|
311
|
+
this.addListener(this.container, 'keydown', e => {
|
|
312
|
+
if (e.key !== 'Enter' && e.key !== ' ') return;
|
|
313
|
+
const eventEl = e.target.closest('.fc-event');
|
|
314
|
+
if (!eventEl || !this.container.contains(eventEl)) return;
|
|
315
|
+
|
|
316
|
+
e.preventDefault();
|
|
317
|
+
e.stopPropagation();
|
|
318
|
+
this._selectEventFromElement(eventEl);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
this._enhanceEventAccessibility();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
_selectEventFromElement(eventEl) {
|
|
325
|
+
const eventId = eventEl.dataset.eventId;
|
|
326
|
+
const event = this.stateManager.getEvents().find(ev => ev.id === eventId);
|
|
327
|
+
if (event) {
|
|
328
|
+
this.stateManager.selectEvent(event);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Make rendered events keyboard-reachable and screen-reader labeled.
|
|
334
|
+
* Runs after every render, across all views.
|
|
335
|
+
*/
|
|
336
|
+
_enhanceEventAccessibility() {
|
|
337
|
+
for (const el of this.container.querySelectorAll('.fc-event')) {
|
|
338
|
+
el.setAttribute('role', 'button');
|
|
339
|
+
el.setAttribute('tabindex', '0');
|
|
340
|
+
if (!el.hasAttribute('aria-label')) {
|
|
341
|
+
const label = (el.getAttribute('title') || el.textContent || '').trim();
|
|
342
|
+
if (label) {
|
|
343
|
+
el.setAttribute('aria-label', `Event: ${label}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
313
347
|
}
|
|
314
348
|
}
|
|
315
349
|
|
|
@@ -28,16 +28,37 @@ export class MonthViewRenderer extends BaseViewRenderer {
|
|
|
28
28
|
const html = this._renderMonthView(viewData, config);
|
|
29
29
|
this.container.innerHTML = html;
|
|
30
30
|
this._attachEventHandlers();
|
|
31
|
+
|
|
32
|
+
// Roving tabindex: exactly one cell is tabbable; restore focus after
|
|
33
|
+
// a keyboard-initiated re-render (month navigation, selection)
|
|
34
|
+
const active = this._applyRovingTabindex(this._pendingFocusMs ?? null);
|
|
35
|
+
if (this._pendingFocusMs != null) {
|
|
36
|
+
this._pendingFocusMs = null;
|
|
37
|
+
if (active) {
|
|
38
|
+
active.focus();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
_renderMonthView(viewData, config) {
|
|
34
44
|
const weekStartsOn = config.weekStartsOn || 0;
|
|
35
45
|
const dayNames = this._getDayNames(weekStartsOn);
|
|
46
|
+
const locale = config.locale || 'en-US';
|
|
47
|
+
this._dayLabelFormatter = new Intl.DateTimeFormat(locale, {
|
|
48
|
+
weekday: 'long',
|
|
49
|
+
year: 'numeric',
|
|
50
|
+
month: 'long',
|
|
51
|
+
day: 'numeric'
|
|
52
|
+
});
|
|
53
|
+
const currentDate = this.stateManager.getState().currentDate;
|
|
54
|
+
const gridLabel = new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }).format(
|
|
55
|
+
currentDate || new Date()
|
|
56
|
+
);
|
|
36
57
|
|
|
37
58
|
let html = `
|
|
38
|
-
<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);">
|
|
39
|
-
<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);">
|
|
40
|
-
${dayNames.map(d => `<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(d)}</div>`).join('')}
|
|
59
|
+
<div class="fc-month-view" role="grid" aria-label="${this.escapeHTML(gridLabel)}" style="display: flex; flex-direction: column; height: 100%; min-height: 400px; background: var(--fc-background); border: 1px solid var(--fc-border-color);">
|
|
60
|
+
<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);">
|
|
61
|
+
${dayNames.map(d => `<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(d)}</div>`).join('')}
|
|
41
62
|
</div>
|
|
42
63
|
<div class="fc-month-body" style="display: flex; flex-direction: column; flex: 1;">
|
|
43
64
|
`;
|
|
@@ -62,7 +83,7 @@ export class MonthViewRenderer extends BaseViewRenderer {
|
|
|
62
83
|
|
|
63
84
|
_renderWeek(week) {
|
|
64
85
|
let html =
|
|
65
|
-
'<div class="fc-month-week" style="display: grid; grid-template-columns: repeat(7, 1fr); flex: 1; min-height: 80px;">';
|
|
86
|
+
'<div class="fc-month-week" role="row" style="display: grid; grid-template-columns: repeat(7, 1fr); flex: 1; min-height: 80px;">';
|
|
66
87
|
|
|
67
88
|
week.days.forEach(day => {
|
|
68
89
|
html += this._renderDay(day);
|
|
@@ -86,8 +107,20 @@ export class MonthViewRenderer extends BaseViewRenderer {
|
|
|
86
107
|
const visibleEvents = events.slice(0, this.maxEventsToShow);
|
|
87
108
|
const moreCount = events.length - this.maxEventsToShow;
|
|
88
109
|
|
|
110
|
+
let cellLabel = this._dayLabelFormatter
|
|
111
|
+
? this._dayLabelFormatter.format(new Date(day.date))
|
|
112
|
+
: day.date;
|
|
113
|
+
if (events.length > 0) {
|
|
114
|
+
cellLabel += `, ${events.length} ${events.length === 1 ? 'event' : 'events'}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
89
117
|
return `
|
|
90
118
|
<div class="fc-month-day" data-date="${this.escapeHTML(day.date)}"
|
|
119
|
+
role="gridcell" tabindex="-1"
|
|
120
|
+
aria-selected="${day.isSelected ? 'true' : 'false'}"
|
|
121
|
+
${isToday ? 'aria-current="date"' : ''}
|
|
122
|
+
${isOtherMonth ? 'data-other-month="true"' : ''}
|
|
123
|
+
aria-label="${this.escapeHTML(cellLabel)}"
|
|
91
124
|
style="background: ${dayBg}; 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;">
|
|
92
125
|
<div class="fc-day-number" style="font-size: 13px; font-weight: 500; color: ${dayNumColor}; padding: 2px 4px; margin-bottom: 4px; ${todayStyle}">
|
|
93
126
|
${this.escapeHTML(String(day.dayOfMonth))}
|
|
@@ -121,9 +154,112 @@ export class MonthViewRenderer extends BaseViewRenderer {
|
|
|
121
154
|
this.stateManager.selectDate(date);
|
|
122
155
|
});
|
|
123
156
|
|
|
157
|
+
// WAI-ARIA grid keyboard navigation
|
|
158
|
+
this.addListener(this.container, 'keydown', e => {
|
|
159
|
+
if (e.target.closest('.fc-event')) return; // events handle their own keys
|
|
160
|
+
const cell = e.target.closest('.fc-month-day');
|
|
161
|
+
if (!cell || !this.container.contains(cell)) return;
|
|
162
|
+
|
|
163
|
+
const cells = Array.from(this.container.querySelectorAll('.fc-month-day'));
|
|
164
|
+
const idx = cells.indexOf(cell);
|
|
165
|
+
let targetIdx;
|
|
166
|
+
|
|
167
|
+
switch (e.key) {
|
|
168
|
+
case 'ArrowRight':
|
|
169
|
+
targetIdx = idx + 1;
|
|
170
|
+
break;
|
|
171
|
+
case 'ArrowLeft':
|
|
172
|
+
targetIdx = idx - 1;
|
|
173
|
+
break;
|
|
174
|
+
case 'ArrowDown':
|
|
175
|
+
targetIdx = idx + 7;
|
|
176
|
+
break;
|
|
177
|
+
case 'ArrowUp':
|
|
178
|
+
targetIdx = idx - 7;
|
|
179
|
+
break;
|
|
180
|
+
case 'Home':
|
|
181
|
+
targetIdx = idx - (idx % 7);
|
|
182
|
+
break;
|
|
183
|
+
case 'End':
|
|
184
|
+
targetIdx = idx - (idx % 7) + 6;
|
|
185
|
+
break;
|
|
186
|
+
case 'PageUp':
|
|
187
|
+
case 'PageDown': {
|
|
188
|
+
e.preventDefault();
|
|
189
|
+
const d = new Date(cell.dataset.date);
|
|
190
|
+
d.setMonth(d.getMonth() + (e.key === 'PageDown' ? 1 : -1));
|
|
191
|
+
this._pendingFocusMs = d.getTime();
|
|
192
|
+
if (e.key === 'PageDown') {
|
|
193
|
+
this.stateManager.next();
|
|
194
|
+
} else {
|
|
195
|
+
this.stateManager.previous();
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
case 'Enter':
|
|
200
|
+
case ' ': {
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
const date = new Date(cell.dataset.date);
|
|
203
|
+
this._pendingFocusMs = date.getTime();
|
|
204
|
+
this.stateManager.selectDate(date);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
default:
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
e.preventDefault();
|
|
212
|
+
if (targetIdx >= 0 && targetIdx < cells.length) {
|
|
213
|
+
this._applyRovingTabindex(new Date(cells[targetIdx].dataset.date).getTime());
|
|
214
|
+
cells[targetIdx].focus();
|
|
215
|
+
} else {
|
|
216
|
+
// Focus target falls outside the rendered grid: navigate and
|
|
217
|
+
// restore focus on the equivalent date after re-render
|
|
218
|
+
const d = new Date(cell.dataset.date);
|
|
219
|
+
d.setDate(d.getDate() + (targetIdx - idx));
|
|
220
|
+
this._pendingFocusMs = d.getTime();
|
|
221
|
+
if (targetIdx < 0) {
|
|
222
|
+
this.stateManager.previous();
|
|
223
|
+
} else {
|
|
224
|
+
this.stateManager.next();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
124
229
|
// Common event handlers (event clicks)
|
|
125
230
|
this.attachCommonEventHandlers();
|
|
126
231
|
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Ensure exactly one grid cell participates in the tab order.
|
|
235
|
+
* Priority: requested focus date, selected date, today, first day of
|
|
236
|
+
* the current month.
|
|
237
|
+
* @param {number|null} focusMs - Preferred focus date as a timestamp
|
|
238
|
+
* @returns {HTMLElement|null} The tabbable cell
|
|
239
|
+
*/
|
|
240
|
+
_applyRovingTabindex(focusMs = null) {
|
|
241
|
+
const cells = Array.from(this.container.querySelectorAll('.fc-month-day'));
|
|
242
|
+
if (cells.length === 0) return null;
|
|
243
|
+
|
|
244
|
+
let active = null;
|
|
245
|
+
if (focusMs != null) {
|
|
246
|
+
active = cells.find(c => new Date(c.dataset.date).getTime() === focusMs);
|
|
247
|
+
}
|
|
248
|
+
if (!active) {
|
|
249
|
+
active = cells.find(c => c.getAttribute('aria-selected') === 'true');
|
|
250
|
+
}
|
|
251
|
+
if (!active) {
|
|
252
|
+
active = cells.find(c => c.hasAttribute('aria-current'));
|
|
253
|
+
}
|
|
254
|
+
if (!active) {
|
|
255
|
+
active = cells.find(c => !c.dataset.otherMonth) || cells[0];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (const c of cells) {
|
|
259
|
+
c.setAttribute('tabindex', c === active ? '0' : '-1');
|
|
260
|
+
}
|
|
261
|
+
return active;
|
|
262
|
+
}
|
|
127
263
|
}
|
|
128
264
|
|
|
129
265
|
export default MonthViewRenderer;
|
|
@@ -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 };
|