@creative-web-solution/front-library 7.1.52 → 7.1.54

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.54
4
+
5
+ - [Autocomplete]: Remove shouldMarkValue configuration
6
+
7
+ ## 7.1.53
8
+
9
+ - Add AriaAnnouncementHelper
10
+
3
11
  ## 7.1.52
4
12
 
5
13
  - [Popin]: Fix tabulation inside popin
@@ -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
+ }
@@ -8,12 +8,12 @@ export default abstract class AbstractAutocompleteAdapter<ItemDataType> {
8
8
  query: string,
9
9
  $input: HTMLElement,
10
10
  ): Record<string, any>;
11
- abstract markValue(options: {
11
+ markValue?(options: {
12
12
  item: ItemDataType;
13
13
  index: number;
14
14
  query: string;
15
15
  }): ItemDataType;
16
- abstract updateInputValueFromItem?(options: {
16
+ updateInputValueFromItem?(options: {
17
17
  item: ItemDataType;
18
18
  index: number;
19
19
  query: string;
@@ -25,7 +25,6 @@ type AutocompleteOptions<ItemDataType> = {
25
25
  results: ItemDataType[];
26
26
  }) => void;
27
27
  $searchField: HTMLInputElement;
28
- shouldMarkValue?: boolean;
29
28
  };
30
29
 
31
30
  const PREVENT_DEFAULT_KEY_LIST = [
@@ -49,7 +48,6 @@ const DEFAULT_OPTIONS = {
49
48
  layerPosition: "top",
50
49
  $layerWrapper: document.body,
51
50
  minChar: 3,
52
- shouldMarkValue: false,
53
51
  };
54
52
 
55
53
  export default class Autocomplete<ItemDataType> {
@@ -222,13 +220,11 @@ export default class Autocomplete<ItemDataType> {
222
220
  this.resetResults();
223
221
 
224
222
  this.#currentResults = results.map((item, index) => {
225
- const markedItem = this.#options.shouldMarkValue
226
- ? this.#adapter.markValue({
223
+ const markedItem = this.#adapter.markValue?.({
227
224
  item,
228
225
  index,
229
226
  query: this.#currentQuery,
230
- })
231
- : item;
227
+ }) ?? item;
232
228
  const $item = strToDOM(
233
229
  this.#adapter.itemRender({
234
230
  item: markedItem,
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Front Library
2
2
 
3
- @version: 7.1.52
3
+ @version: 7.1.54
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.52",
5
+ "version": "7.1.54",
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": [],