diff --git a/README.md b/README.md
index 707ae59..e9f295d 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ A FullCalendar component for Angular.
## Supported Angular Versions
-`@talentia/fullcalendar` version `4.4.14` supports Angular 18.
+`@talentia/fullcalendar` version `4.4.15` supports Angular 18.
# FullCalendar Angular Component [](https://travis-ci.com/fullcalendar/fullcalendar-angular)
diff --git a/package-lock.json b/package-lock.json
index 5c55b36..465c71d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "fullcalendar",
- "version": "4.4.14",
+ "version": "4.4.15",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fullcalendar",
- "version": "4.4.14",
+ "version": "4.4.15",
"dependencies": {
"@angular/animations": "20.3.18",
"@angular/common": "20.3.18",
diff --git a/package.json b/package.json
index 0a75310..45465b8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "fullcalendar",
- "version": "4.4.14",
+ "version": "4.4.15",
"scripts": {
"ng": "ng",
"build": "npm-run-all -s clean:build build:prod",
diff --git a/projects/fullcalendar/README.md b/projects/fullcalendar/README.md
index 707ae59..e9f295d 100644
--- a/projects/fullcalendar/README.md
+++ b/projects/fullcalendar/README.md
@@ -5,7 +5,7 @@ A FullCalendar component for Angular.
## Supported Angular Versions
-`@talentia/fullcalendar` version `4.4.14` supports Angular 18.
+`@talentia/fullcalendar` version `4.4.15` supports Angular 18.
# FullCalendar Angular Component [](https://travis-ci.com/fullcalendar/fullcalendar-angular)
diff --git a/projects/fullcalendar/package.json b/projects/fullcalendar/package.json
index f3c2070..e88bd09 100644
--- a/projects/fullcalendar/package.json
+++ b/projects/fullcalendar/package.json
@@ -1,6 +1,6 @@
{
"name": "@talentia/fullcalendar",
- "version": "4.4.14",
+ "version": "4.4.15",
"title": "FullCalendar Angular Component",
"description": "A FullCalendar component for Angular",
"license": "MIT",
diff --git a/projects/fullcalendar/src/lib/fullcalendar-options.ts b/projects/fullcalendar/src/lib/fullcalendar-options.ts
index a6dfbc0..9dae83a 100644
--- a/projects/fullcalendar/src/lib/fullcalendar-options.ts
+++ b/projects/fullcalendar/src/lib/fullcalendar-options.ts
@@ -115,7 +115,7 @@ export const INPUT_NAMES = [
'forceEventDuration',
'progressiveEventRendering',
'selectLongPressDelay',
- 'selectMinDistance',
+ // 'selectMinDistance',
'timeZoneParam',
'titleRangeSeparator',
// compound OptionsInput...
diff --git a/projects/fullcalendar/src/lib/fullcalendar.component.ts b/projects/fullcalendar/src/lib/fullcalendar.component.ts
index d69b155..2786d6f 100644
--- a/projects/fullcalendar/src/lib/fullcalendar.component.ts
+++ b/projects/fullcalendar/src/lib/fullcalendar.component.ts
@@ -36,10 +36,62 @@ export class FullCalendarComponent implements AfterViewInit, DoCheck, OnChanges,
private calendar: Calendar;
private dirtyProps: any = {};
private deepCopies: any = {};
+ private a11yObserver?: MutationObserver;
+ private a11yPatchScheduled = false;
+ private initialRenderInProgress = true;
ngAfterViewInit() {
this.calendar = new Calendar(this.element.nativeElement, this.buildOptions());
this.calendar.render();
+ this.initialRenderInProgress = false;
+ this.patchAccessibility();
+
+ // Primary trigger: datesRender/viewSkeletonRender (wired in buildOptions) fire whenever
+ // FullCalendar (re)builds the header/body table skeleton, which is cheaper and more
+ // targeted than scanning on every DOM mutation.
+ //
+ // Fallback: a MutationObserver, in case some table rebuild path doesn't go through those
+ // hooks. Its callback only re-patches when an added node actually looks like calendar
+ // table markup, instead of unconditionally rescanning the whole tree on any mutation.
+ // Attribute writes done by patchAccessibility don't trigger childList mutations, so this
+ // cannot loop on itself.
+ this.a11yObserver = new MutationObserver((mutations) => {
+ const hasTableMarkup = mutations.some((mutation) =>
+ Array.from(mutation.addedNodes).some((node) =>
+ node instanceof Element && (node.matches('table, th') || !!node.querySelector('table, th'))
+ )
+ );
+ if (hasTableMarkup) this.scheduleAccessibilityPatch();
+ });
+ this.a11yObserver.observe(this.element.nativeElement, { childList: true, subtree: true });
+ }
+
+ private scheduleAccessibilityPatch() {
+ if (this.a11yPatchScheduled) return;
+ this.a11yPatchScheduled = true;
+ queueMicrotask(() => {
+ this.a11yPatchScheduled = false;
+ this.patchAccessibility();
+ });
+ }
+
+ /*
+ Fix: FullCalendar (v4 through at least v6) never declares its day-grid/timeline
+
markup as data tables with associated headers, nor marks the purely positional ones as
+ presentational. Two independent tables are involved: the header row (, e.g.
+ fc-day-header in dayGrid, unclassed in resource-timeline) and the per-week/day/resource body
+ grids ( with | cells only, no | at all). Neither carries scope/role, so this patches
+ both after every render. All observed | are column headers (no scope="row" case found across
+ dayGrid/timeGrid/resource-timeline views), hence the unqualified selector below.
+ */
+ private patchAccessibility() {
+ const root: HTMLElement = this.element.nativeElement;
+ root.querySelectorAll('th:not([scope])').forEach((th) => th.setAttribute('scope', 'col'));
+ root.querySelectorAll('table:not([role])').forEach((table) => {
+ if (!table.querySelector('th')) {
+ table.setAttribute('role', 'presentation');
+ }
+ });
}
private buildOptions() {
@@ -51,6 +103,20 @@ export class FullCalendarComponent implements AfterViewInit, DoCheck, OnChanges,
};
});
+ // datesRender/viewSkeletonRender fire whenever FullCalendar (re)builds the header/body
+ // table skeleton, so they're the cheapest, most targeted trigger for the a11y patch —
+ // wrap rather than replace so consumers still receive the event. Skipped during the
+ // initial render() call since ngAfterViewInit already patches synchronously right after it.
+ ['datesRender', 'viewSkeletonRender'].forEach(outputName => {
+ const emit = options[outputName];
+ options[outputName] = (...args) => {
+ if (!this.initialRenderInProgress) {
+ this.scheduleAccessibilityPatch();
+ }
+ emit(...args);
+ };
+ });
+
// do after outputs, so that inputs with same name override
INPUT_NAMES.forEach(inputName => {
let inputVal = this[inputName];
@@ -121,6 +187,8 @@ export class FullCalendarComponent implements AfterViewInit, DoCheck, OnChanges,
}
ngOnDestroy() {
+ this.a11yObserver?.disconnect();
+ this.a11yObserver = undefined;
if (this.calendar) {
this.calendar.destroy();
}
diff --git a/tsconfig.json b/tsconfig.json
index f4fdc11..1a3a241 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -8,6 +8,7 @@
"./dist/fullcalendar"
]
},
+ "rootDir": "./projects/fullcalendar/src",
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
| |