@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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 7.1.53
4
+
5
+ - Add AriaAnnouncementHelper
6
+
7
+ ## 7.1.52
8
+
9
+ - [Popin]: Fix tabulation inside popin
10
+
3
11
  ## 7.1.51
4
12
 
5
13
  - [Validator]: Update error message selection
@@ -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
+ }
@@ -39,7 +39,7 @@ export default class PopinAccessibility {
39
39
 
40
40
 
41
41
  handleForwardTab( e: Event ): void {
42
- if ( this.#$elements?.length || !this.#$lastElement ) {
42
+ if ( !this.#$elements?.length || !this.#$lastElement ) {
43
43
  e.preventDefault();
44
44
  return;
45
45
  }
@@ -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
@@ -1,6 +1,6 @@
1
1
  # Front Library
2
2
 
3
- @version: 7.1.51
3
+ @version: 7.1.53
4
4
 
5
5
 
6
6
  ## Use
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.51",
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": [],