@creative-web-solution/front-library 7.1.51 → 7.1.53
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/CHANGELOG.md +8 -0
- package/Helpers/AriaAnnouncementHelper.ts +148 -0
- package/Modules/Popin/PopinAccessibility.ts +1 -1
- package/Modules/Popin/Tools.ts +1 -1
- package/README.md +1 -1
- package/Types/Global.d.ts +25 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
type AriaAnnouncementHelperBaseOptions = {
|
|
2
|
+
/** In words/minute. Average is between 150 and 200
|
|
3
|
+
* @default 175
|
|
4
|
+
*/
|
|
5
|
+
estimatedSpeechSpeed?: number;
|
|
6
|
+
};
|
|
7
|
+
type AriaAnnouncementHelperSelectorsOptions =
|
|
8
|
+
AriaAnnouncementHelperBaseOptions & {
|
|
9
|
+
assertiveAreaSelector: string;
|
|
10
|
+
politeAreaSelector: string;
|
|
11
|
+
};
|
|
12
|
+
type AriaAnnouncementHelperCssClassOptions =
|
|
13
|
+
AriaAnnouncementHelperBaseOptions & {
|
|
14
|
+
areaCssClass?: string;
|
|
15
|
+
};
|
|
16
|
+
type AriaAnnouncementOptions = OneOf<
|
|
17
|
+
[
|
|
18
|
+
AriaAnnouncementHelperSelectorsOptions,
|
|
19
|
+
AriaAnnouncementHelperCssClassOptions,
|
|
20
|
+
]
|
|
21
|
+
>;
|
|
22
|
+
|
|
23
|
+
type MessagePriority = "polite" | "assertive";
|
|
24
|
+
|
|
25
|
+
const PRIORITIES: Record<MessagePriority, MessagePriority> = {
|
|
26
|
+
assertive: "assertive",
|
|
27
|
+
polite: "polite",
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
const ESTIMATED_WORDS_BY_MINUTES_SPEED = 175;
|
|
31
|
+
|
|
32
|
+
export default class AriaAnnouncementHelper {
|
|
33
|
+
#$assertiveArea!: HTMLElement;
|
|
34
|
+
#estimatedSpeechSpeed: number;
|
|
35
|
+
#isSpeeching: boolean = false;
|
|
36
|
+
#messages: {
|
|
37
|
+
message: string;
|
|
38
|
+
priority: MessagePriority;
|
|
39
|
+
}[] = [];
|
|
40
|
+
#options?: AriaAnnouncementOptions;
|
|
41
|
+
#$politeArea!: HTMLElement;
|
|
42
|
+
#rafId: number = -1;
|
|
43
|
+
#timeoutId: number = -1;
|
|
44
|
+
|
|
45
|
+
constructor(options?: AriaAnnouncementOptions) {
|
|
46
|
+
this.#options = options;
|
|
47
|
+
this.#estimatedSpeechSpeed =
|
|
48
|
+
this.#options?.estimatedSpeechSpeed ??
|
|
49
|
+
ESTIMATED_WORDS_BY_MINUTES_SPEED;
|
|
50
|
+
this.#createElements();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#createElements(): void {
|
|
54
|
+
this.#$politeArea = this.#createArea(
|
|
55
|
+
PRIORITIES.polite,
|
|
56
|
+
(this.#options as AriaAnnouncementHelperSelectorsOptions)
|
|
57
|
+
?.politeAreaSelector,
|
|
58
|
+
);
|
|
59
|
+
this.#$assertiveArea = this.#createArea(
|
|
60
|
+
PRIORITIES.assertive,
|
|
61
|
+
(this.#options as AriaAnnouncementHelperSelectorsOptions)
|
|
62
|
+
?.assertiveAreaSelector,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#createArea(priority: MessagePriority, selector?: string): HTMLElement {
|
|
67
|
+
let $element: HTMLElement | null = null;
|
|
68
|
+
if (selector) {
|
|
69
|
+
$element = document.querySelector<HTMLElement>(selector);
|
|
70
|
+
}
|
|
71
|
+
if (!$element) {
|
|
72
|
+
$element = document.createElement("div");
|
|
73
|
+
$element.id = `announcement-${priority}`;
|
|
74
|
+
$element.setAttribute("aria-live", priority);
|
|
75
|
+
$element.setAttribute("aria-atomic", "true");
|
|
76
|
+
const areaCssClass = (
|
|
77
|
+
this.#options as AriaAnnouncementHelperCssClassOptions
|
|
78
|
+
)?.areaCssClass;
|
|
79
|
+
if (areaCssClass) {
|
|
80
|
+
$element.classList.add(areaCssClass);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
document.body.appendChild($element);
|
|
84
|
+
|
|
85
|
+
return $element;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
assertiveMessage(message: string): this {
|
|
89
|
+
this.#addMessage(message, PRIORITIES.assertive);
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
politeMessage(message: string): this {
|
|
94
|
+
this.#addMessage(message, PRIORITIES.polite);
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#addMessage(message: string, priority: MessagePriority): void {
|
|
99
|
+
this.#messages.push({ message, priority });
|
|
100
|
+
this.#processMessages();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#processMessages(): void {
|
|
104
|
+
if (this.#isSpeeching || this.#messages.length === 0) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
this.#isSpeeching = true;
|
|
108
|
+
this.#clearZones();
|
|
109
|
+
|
|
110
|
+
const { message, priority } = this.#messages.shift()!;
|
|
111
|
+
|
|
112
|
+
this.#updateZone(
|
|
113
|
+
message,
|
|
114
|
+
priority === PRIORITIES.assertive
|
|
115
|
+
? this.#$assertiveArea
|
|
116
|
+
: this.#$politeArea,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
this.#timeoutId = setTimeout(() => {
|
|
120
|
+
this.#isSpeeching = false;
|
|
121
|
+
this.#processMessages();
|
|
122
|
+
}, this.#timeEstimationInMs(message));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#updateZone(message: string, $element: HTMLElement): void {
|
|
126
|
+
this.#rafId = requestAnimationFrame(() => {
|
|
127
|
+
$element.textContent = message;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#timeEstimationInMs(texte: string): number {
|
|
132
|
+
const wordsCount = texte.split(" ").length;
|
|
133
|
+
return (wordsCount / this.#estimatedSpeechSpeed) * 60 * 1000;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
clearAll(): this {
|
|
137
|
+
this.#messages.length = 0;
|
|
138
|
+
clearTimeout(this.#timeoutId);
|
|
139
|
+
cancelAnimationFrame(this.#rafId);
|
|
140
|
+
this.#clearZones();
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#clearZones(): void {
|
|
145
|
+
this.#$politeArea.textContent = "";
|
|
146
|
+
this.#$assertiveArea.textContent = "";
|
|
147
|
+
}
|
|
148
|
+
}
|
package/Modules/Popin/Tools.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
export const FOCUSABLE_ELEMENTS_SELECTOR = 'a,button,input,select,textarea';
|
|
2
|
+
export const FOCUSABLE_ELEMENTS_SELECTOR = 'a,button,input,select,textarea,iframe';
|
|
3
3
|
|
|
4
4
|
export function toggleTabIndex( $elements: NodeListOf<HTMLElement> | undefined | null, $popin: HTMLElement, activate: boolean ): void {
|
|
5
5
|
$elements = $elements || $popin.querySelectorAll( FOCUSABLE_ELEMENTS_SELECTOR );
|
package/README.md
CHANGED
package/Types/Global.d.ts
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type ArrayElement<ArrayType extends readonly unknown[]> =
|
|
2
|
+
ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
|
|
3
|
+
|
|
4
|
+
type MergeTypes<TypesArray extends any[], Res = {}> = TypesArray extends [
|
|
5
|
+
infer Head,
|
|
6
|
+
...infer Rem,
|
|
7
|
+
]
|
|
8
|
+
? MergeTypes<Rem, Res & Head>
|
|
9
|
+
: Res;
|
|
10
|
+
|
|
11
|
+
type OnlyFirst<F, S> = F & { [Key in keyof Omit<S, keyof F>]?: never };
|
|
12
|
+
|
|
13
|
+
type OneOf<
|
|
14
|
+
TypesArray extends any[],
|
|
15
|
+
Res = never,
|
|
16
|
+
AllProperties = MergeTypes<TypesArray>,
|
|
17
|
+
> = TypesArray extends [infer Head, ...infer Rem]
|
|
18
|
+
? OneOf<Rem, Res | OnlyFirst<Head, AllProperties>, AllProperties>
|
|
19
|
+
: Res;
|
|
20
|
+
|
|
21
|
+
type Nullable<T> = T | null | undefined;
|
|
22
|
+
|
|
23
|
+
type NullableRecord<T> = {
|
|
24
|
+
[K in keyof T]: T[K] | null | undefined;
|
|
25
|
+
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@creative-web-solution/front-library",
|
|
3
3
|
"title": "Frontend library",
|
|
4
4
|
"description": "Frontend functions and modules",
|
|
5
|
-
"version": "7.1.
|
|
5
|
+
"version": "7.1.53",
|
|
6
6
|
"homepage": "https://github.com/creative-web-solution/front-library",
|
|
7
7
|
"author": "Creative Web Solution <contact@cws-studio.com> (https://www.cws-studio.com)",
|
|
8
8
|
"keywords": [],
|