@creative-web-solution/front-library 7.1.59 → 7.1.60

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,10 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 7.1.60
4
+
5
+ - [Accordion]: Updates for accessibility
6
+ - [Tabs]: Add manual selection handling
7
+
3
8
  ## 7.1.59
4
9
 
5
10
  - [DragSlider]: Refresh elements on first interaction
@@ -1,51 +1,65 @@
1
- import { next } from "../../DOM/Traversing";
2
-
3
1
  /**
4
2
  * Tab of an accordion
5
3
  */
6
4
  export default class Tab {
7
- #isOpen: boolean;
8
- #originalOpenedState: boolean;
9
- #$TAB_PANNEL: HTMLElement | null;
10
- #options;
11
- #$TAB;
5
+ #isOpen: boolean = false;
6
+ #originalOpenedState: boolean = false;
7
+ #$panel!: HTMLElement;
8
+ #options: FLib.Accordion.TabOptions;
9
+ #$tab: HTMLElement;
12
10
 
13
11
  get isOpen(): boolean {
14
12
  return this.#isOpen;
15
13
  }
16
14
 
17
- constructor($TAB: HTMLElement, options: FLib.Accordion.TabOptions) {
15
+ constructor($tab: HTMLElement, options: FLib.Accordion.TabOptions) {
18
16
  this.#options = options;
19
- this.#$TAB = $TAB;
17
+ this.#$tab = $tab;
18
+ this.#init();
19
+ }
20
20
 
21
- const ID = $TAB.getAttribute("aria-controls");
22
- this.#$TAB_PANNEL = ID
21
+ #init(): void {
22
+ const ID = this.#$tab.getAttribute("aria-controls");
23
+ const $panel = ID
23
24
  ? document.getElementById(ID)
24
- : (next($TAB) as HTMLElement);
25
+ : this.#$tab.nextElementSibling as HTMLElement;
26
+
27
+ if (!$panel) {
28
+ let idError = "";
29
+ if (ID) {
30
+ idError = ` with id "${ ID }"`;
31
+ }
32
+ throw `[TAB] Missing tab panel${ idError }.`;
33
+ }
25
34
 
35
+ this.#$panel = $panel;
26
36
  this.#isOpen = this.#originalOpenedState =
27
- $TAB.getAttribute("aria-expanded") === "true";
37
+ this.#$tab.getAttribute("aria-expanded") === "true";
38
+
39
+ this.#$panel.inert = true;
28
40
 
29
41
  if (this.#isOpen) {
30
42
  this.#openTab(true);
31
43
  }
44
+
32
45
  }
33
46
 
34
47
  #changeTabState = (): void => {
35
- this.#$TAB.setAttribute(
48
+ this.#$tab.setAttribute(
36
49
  "aria-expanded",
37
50
  this.#isOpen ? "true" : "false",
38
51
  );
39
52
  };
40
53
 
41
54
  #openTab = (isOpenAtStart?: boolean): void => {
55
+ this.#$panel.inert = false;
42
56
  this.#options.animations
43
- .open(this.#$TAB, this.#$TAB_PANNEL)
57
+ .open(this.#$tab, this.#$panel)
44
58
  .then(() => {
45
59
  if (isOpenAtStart && this.#options.onOpenAtStart) {
46
- this.#options.onOpenAtStart(this.#$TAB, this.#$TAB_PANNEL);
60
+ this.#options.onOpenAtStart(this.#$tab, this.#$panel);
47
61
  } else if (!isOpenAtStart && this.#options.onOpen) {
48
- this.#options.onOpen(this.#$TAB, this.#$TAB_PANNEL);
62
+ this.#options.onOpen(this.#$tab, this.#$panel);
49
63
  }
50
64
  });
51
65
 
@@ -54,13 +68,14 @@ export default class Tab {
54
68
  };
55
69
 
56
70
  #closeTab = (autoClose?: boolean): void => {
71
+ this.#$panel.inert = true;
57
72
  this.#options.animations
58
- .close(this.#$TAB, this.#$TAB_PANNEL)
73
+ .close(this.#$tab, this.#$panel)
59
74
  .then(() => {
60
75
  this.#options.onClose?.(
61
- this.#$TAB,
62
- this.#$TAB_PANNEL,
63
- autoClose,
76
+ this.#$tab,
77
+ this.#$panel,
78
+ autoClose ?? false,
64
79
  );
65
80
  });
66
81
 
@@ -84,12 +99,13 @@ export default class Tab {
84
99
  }
85
100
 
86
101
  destroy(): this {
87
- this.#options.animations.destroy(this.#$TAB, this.#$TAB_PANNEL);
102
+ this.#options.animations.destroy(this.#$tab, this.#$panel);
88
103
 
89
- this.#$TAB.setAttribute(
104
+ this.#$tab.setAttribute(
90
105
  "aria-expanded",
91
106
  this.#originalOpenedState ? "true" : "false",
92
107
  );
108
+ this.#$panel.inert = false;
93
109
 
94
110
  return this;
95
111
  }
@@ -8,18 +8,18 @@ const DEFAULT_OPTIONS = {
8
8
  allowMultipleTab: false,
9
9
  atLeastOneOpen: false,
10
10
  animations: {
11
- open: function ($TAB, $TAB_PANNEL) {
12
- aClass([$TAB, $TAB_PANNEL], "on");
11
+ open: function ($tab: HTMLElement, $panel: HTMLElement) {
12
+ aClass([$tab, $panel], "on");
13
13
 
14
14
  return Promise.resolve();
15
15
  },
16
- close: function ($TAB, $TAB_PANNEL) {
17
- rClass([$TAB, $TAB_PANNEL], "on");
16
+ close: function ($tab: HTMLElement, $panel: HTMLElement) {
17
+ rClass([$tab, $panel], "on");
18
18
 
19
19
  return Promise.resolve();
20
20
  },
21
- destroy: function ($TAB, $TAB_PANNEL) {
22
- rClass([$TAB, $TAB_PANNEL], "on");
21
+ destroy: function ($tab: HTMLElement, $panel: HTMLElement) {
22
+ rClass([$tab, $panel], "on");
23
23
 
24
24
  return Promise.resolve();
25
25
  },
@@ -4,13 +4,13 @@ import { on, off } from '../../Events/EventsManager';
4
4
  /**
5
5
  * Tab of a tabs list
6
6
  */
7
- export default class Tab implements FLib.Tabs.Tab {
7
+ export default class Tab {
8
8
 
9
- #options: FLib.Tabs.TabOptions;
10
- #$TAB: HTMLElement;
11
- #$TAB_PANNEL: HTMLElement;
12
- #isOpen: boolean;
13
- #originalOpenedState: boolean;
9
+ #options: FLib.Tabs.TabOptions;
10
+ #$tab: HTMLElement;
11
+ #panel!: HTMLElement;
12
+ #isOpen: boolean = false;
13
+ #originalOpenedState: boolean = false;
14
14
 
15
15
 
16
16
  get isOpened(): boolean {
@@ -22,58 +22,72 @@ export default class Tab implements FLib.Tabs.Tab {
22
22
  }
23
23
 
24
24
 
25
- constructor( $TAB: HTMLElement, options: FLib.Tabs.TabOptions ) {
25
+ constructor( $tab: HTMLElement, options: FLib.Tabs.TabOptions ) {
26
26
  this.#options = options;
27
- this.#$TAB = $TAB;
27
+ this.#$tab = $tab;
28
28
 
29
- const ID = $TAB.getAttribute( 'aria-controls' );
29
+ this.#init();
30
+ }
31
+
32
+ #init(): void {
33
+ const ID = this.#$tab.getAttribute( 'aria-controls' );
30
34
 
31
35
  if ( !ID ) {
32
36
  throw `Missing "aria-controls" attributes on tab element`;
33
37
  }
34
38
 
35
- this.#$TAB_PANNEL = document.getElementById( ID ) as HTMLElement;
39
+ this.#panel = document.getElementById( ID ) as HTMLElement;
36
40
 
37
- if ( !this.#$TAB_PANNEL ) {
41
+ if ( !this.#panel ) {
38
42
  throw `Unable to find panel element id="${ ID }" attributes on tab element`;
39
43
  }
40
44
 
41
- this.#isOpen = this.#originalOpenedState = $TAB.getAttribute( 'aria-selected' ) === 'true';
45
+ this.#isOpen = this.#originalOpenedState = this.#$tab.getAttribute( 'aria-selected' ) === 'true';
46
+ this.#panel.inert = true;
42
47
 
43
- on( $TAB, {
48
+ on( this.#$tab, {
44
49
  "eventsName": "click",
45
50
  "callback": this.#toggleTab
46
51
  } );
47
52
 
53
+ on( this.#$tab, {
54
+ "eventsName": "focus",
55
+ "callback": this.#onFocusTab
56
+ } );
57
+
48
58
  if ( this.#isOpen ) {
49
59
  this.#openTab( true );
50
60
  }
51
61
  }
52
62
 
63
+ #onFocusTab = (): void => {
64
+ this.#options.onFocusTab?.(this);
65
+ }
53
66
 
54
- #changeTabState = ( isOpenAtStart?: boolean ): void => {
55
- this.#$TAB.setAttribute( 'aria-selected', this.#isOpen ? 'true' : 'false' );
56
- this.#$TAB.setAttribute( 'tabindex', this.#isOpen ? '0' : '-1' );
67
+ #toggleTab = ( e: Event ): void => {
68
+ e.preventDefault();
57
69
 
58
- if ( this.#isOpen && !isOpenAtStart ) {
59
- this.#$TAB.focus();
70
+ if( this.#isOpen ) {
71
+ this.#closeTab();
72
+ }
73
+ else {
74
+ this.#openTab();
60
75
  }
61
-
62
76
  }
63
77
 
64
78
 
65
79
  #openTab = ( isOpenAtStart?: boolean ): void => {
80
+ this.#panel.inert = false;
66
81
  this.#options.animations
67
- .open( this.#$TAB, this.#$TAB_PANNEL )
68
- .then( () => {
69
-
70
- if ( isOpenAtStart && this.#options.onOpenAtStart ) {
71
- this.#options.onOpenAtStart( this.#$TAB, this.#$TAB_PANNEL );
72
- }
73
- else if ( !isOpenAtStart && this.#options.onOpen ) {
74
- this.#options.onOpen( this.#$TAB, this.#$TAB_PANNEL );
75
- }
76
- } );
82
+ .open( this.#$tab, this.#panel )
83
+ .then( () => {
84
+ if ( isOpenAtStart && this.#options.onOpenAtStart ) {
85
+ this.#options.onOpenAtStart( this.#$tab, this.#panel );
86
+ }
87
+ else if ( !isOpenAtStart && this.#options.onOpen ) {
88
+ this.#options.onOpen( this.#$tab, this.#panel );
89
+ }
90
+ } );
77
91
 
78
92
  if ( this.#options.onOpenTab ) {
79
93
  this.#options.onOpenTab( this );
@@ -84,11 +98,12 @@ export default class Tab implements FLib.Tabs.Tab {
84
98
 
85
99
 
86
100
  #closeTab = ( autoClose?: boolean ): void => {
101
+ this.#panel.inert = true;
87
102
  this.#options.animations
88
- .close( this.#$TAB, this.#$TAB_PANNEL )
103
+ .close( this.#$tab, this.#panel )
89
104
  .then( () => {
90
105
  if ( this.#options.onClose ) {
91
- this.#options.onClose( this.#$TAB, this.#$TAB_PANNEL, autoClose );
106
+ this.#options.onClose( this.#$tab, this.#panel, autoClose );
92
107
  }
93
108
  } );
94
109
  this.#isOpen = false;
@@ -96,14 +111,12 @@ export default class Tab implements FLib.Tabs.Tab {
96
111
  }
97
112
 
98
113
 
99
- #toggleTab = ( e: Event ): void => {
100
- e.preventDefault();
114
+ #changeTabState = ( isOpenAtStart?: boolean ): void => {
115
+ this.#$tab.setAttribute( 'aria-selected', this.#isOpen ? 'true' : 'false' );
116
+ this.#$tab.setAttribute( 'tabindex', this.#isOpen ? '0' : '-1' );
101
117
 
102
- if( this.#isOpen ) {
103
- this.#closeTab();
104
- }
105
- else {
106
- this.#openTab();
118
+ if ( this.#isOpen && !isOpenAtStart ) {
119
+ this.#$tab.focus();
107
120
  }
108
121
  }
109
122
 
@@ -125,16 +138,22 @@ export default class Tab implements FLib.Tabs.Tab {
125
138
  return this;
126
139
  }
127
140
 
141
+ focusTab(): this {
142
+ this.#$tab.focus();
143
+ return this;
144
+ }
145
+
128
146
 
129
147
  destroy(): this {
130
- this.#options.animations.destroy( this.#$TAB, this.#$TAB_PANNEL );
148
+ this.#options.animations.destroy( this.#$tab, this.#panel );
149
+ this.#panel.inert = false;
131
150
 
132
- off( this.#$TAB, {
151
+ off( this.#$tab, {
133
152
  "eventsName": "click",
134
153
  "callback": this.#toggleTab
135
154
  } );
136
155
 
137
- this.#$TAB.setAttribute( 'aria-selected', this.#originalOpenedState ? 'true' : 'false' );
156
+ this.#$tab.setAttribute( 'aria-selected', this.#originalOpenedState ? 'true' : 'false' );
138
157
 
139
158
  return this;
140
159
  }
@@ -6,19 +6,20 @@ import Tab from './Tab';
6
6
 
7
7
  const DEFAULT_OPTIONS = {
8
8
  "tabSelector": "li[aria-selected]",
9
+ "selectOnFocus": false,
9
10
  "animations": {
10
- "open": function( $TAB, $TAB_PANNEL ) {
11
- aClass( [ $TAB, $TAB_PANNEL ], 'on' );
11
+ "open": function( $tab: HTMLElement, $panel: HTMLElement ) {
12
+ aClass( [ $tab, $panel ], 'on' );
12
13
 
13
14
  return Promise.resolve();
14
15
  },
15
- "close": function( $TAB, $TAB_PANNEL ) {
16
- rClass( [ $TAB, $TAB_PANNEL ], 'on' );
16
+ "close": function( $tab: HTMLElement, $panel: HTMLElement ) {
17
+ rClass( [ $tab, $panel ], 'on' );
17
18
 
18
19
  return Promise.resolve();
19
20
  },
20
- "destroy": function( $TAB, $TAB_PANNEL ) {
21
- rClass( [ $TAB, $TAB_PANNEL ], 'on' );
21
+ "destroy": function( $tab: HTMLElement, $panel: HTMLElement ) {
22
+ rClass( [ $tab, $panel ], 'on' );
22
23
 
23
24
  return Promise.resolve();
24
25
  }
@@ -98,10 +99,11 @@ export default class Tabs {
98
99
  #options: FLib.Tabs.Options;
99
100
  #$TABS_LIST: HTMLElement;
100
101
  #$TABS: NodeList;
101
- #tablist: FLib.Tabs.Tab[];
102
+ #tablist: Tab[];
102
103
  #status: string;
103
104
  #VERTICAL_MODE: boolean;
104
- #lastOpenedTab: FLib.Tabs.Tab | undefined;
105
+ #lastOpenedTab: Tab | undefined;
106
+ #lastFocusedTab: Tab | undefined;
105
107
  #keyboard: KeyboardHandler | undefined;
106
108
  #$tabsWrapper: HTMLElement;
107
109
 
@@ -124,7 +126,7 @@ export default class Tabs {
124
126
  }
125
127
 
126
128
 
127
- #onOpenTab = ( tab: FLib.Tabs.Tab ): void => {
129
+ #onOpenTab = ( tab: Tab ): void => {
128
130
  if ( this.#lastOpenedTab ) {
129
131
  this.#lastOpenedTab.close( true );
130
132
  }
@@ -134,18 +136,54 @@ export default class Tabs {
134
136
 
135
137
 
136
138
  #onNext = (): void => {
137
- const lastIndex = this.#lastOpenedTab ? this.#lastOpenedTab.index : 0;
139
+ if (this.#options.selectOnFocus) {
140
+ const lastIndex = this.#lastOpenedTab ? this.#lastOpenedTab.index : 0;
141
+ const indexToOpen = lastIndex + 1 >= this.#tablist.length ? 0 : lastIndex + 1;
142
+
143
+ this.#tablist[ indexToOpen ].open();
144
+ return;
145
+ }
146
+
147
+ const lastIndex = this.#lastFocusedTab ? this.#lastFocusedTab.index : 0;
138
148
  const indexToOpen = lastIndex + 1 >= this.#tablist.length ? 0 : lastIndex + 1;
139
149
 
140
- this.#tablist[ indexToOpen ].open();
150
+ this.#tablist[ indexToOpen ].focusTab();
141
151
  }
142
152
 
143
153
 
144
154
  #onPrevious = (): void => {
145
- const lastIndex = this.#lastOpenedTab ? this.#lastOpenedTab.index : 0;
155
+ if (this.#options.selectOnFocus) {
156
+ const lastIndex = this.#lastOpenedTab ? this.#lastOpenedTab.index : 0;
157
+ const indexToOpen = lastIndex - 1 < 0 ? this.#tablist.length - 1 : lastIndex - 1;
158
+
159
+ this.#tablist[ indexToOpen ].open();
160
+ return;
161
+ }
162
+
163
+ const lastIndex = this.#lastFocusedTab ? this.#lastFocusedTab.index : 0;
146
164
  const indexToOpen = lastIndex - 1 < 0 ? this.#tablist.length - 1 : lastIndex - 1;
147
165
 
148
- this.#tablist[ indexToOpen ].open();
166
+ this.#tablist[ indexToOpen ].focusTab();
167
+ }
168
+
169
+
170
+ #onFirst = (): void => {
171
+ if (this.#options.selectOnFocus) {
172
+ this.#tablist[ 0 ].open();
173
+ return;
174
+ }
175
+
176
+ this.#tablist[ 0 ].focusTab();
177
+ }
178
+
179
+
180
+ #onLast = (): void => {
181
+ if (this.#options.selectOnFocus) {
182
+ this.#tablist[ this.#tablist.length - 1 ].open();
183
+ return;
184
+ }
185
+
186
+ this.#tablist[ this.#tablist.length - 1 ].focusTab();
149
187
  }
150
188
 
151
189
 
@@ -162,7 +200,8 @@ export default class Tabs {
162
200
  const tab = new Tab( $tab as HTMLElement, {
163
201
  ...this.#options,
164
202
  index,
165
- "onOpenTab": this.#onOpenTab
203
+ "onOpenTab": this.#onOpenTab,
204
+ "onFocusTab": this.#onFocusTab
166
205
  } );
167
206
 
168
207
  this.#tablist.push( tab );
@@ -180,16 +219,35 @@ export default class Tabs {
180
219
  this.#keyboard = new KeyboardHandler( this.#$tabsWrapper, {
181
220
  "selector": this.#options.tabSelector,
182
221
  "onUp": this.#onPrevious,
183
- "onDown": this.#onNext
222
+ "onDown": this.#onNext,
223
+ "onHome": this.#onFirst,
224
+ "onEnd": this.#onLast,
225
+ "onSelect": this.#onSelect
184
226
  } );
227
+
228
+ return;
185
229
  }
186
- else {
187
- this.#keyboard = new KeyboardHandler( this.#$tabsWrapper, {
188
- "selector": this.#options.tabSelector,
189
- "onRight": this.#onNext,
190
- "onLeft": this.#onPrevious
191
- } );
230
+
231
+ this.#keyboard = new KeyboardHandler( this.#$tabsWrapper, {
232
+ "selector": this.#options.tabSelector,
233
+ "onRight": this.#onNext,
234
+ "onLeft": this.#onPrevious,
235
+ "onHome": this.#onFirst,
236
+ "onEnd": this.#onLast,
237
+ "onSelect": this.#onSelect
238
+ } );
239
+ }
240
+
241
+ #onSelect = (): void => {
242
+ if (!this.#lastFocusedTab) {
243
+ return;
192
244
  }
245
+
246
+ this.#lastFocusedTab.open();
247
+ }
248
+
249
+ #onFocusTab = (tab: Tab): void => {
250
+ this.#lastFocusedTab = tab;
193
251
  }
194
252
 
195
253
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Front Library
2
2
 
3
- @version: 7.1.59
3
+ @version: 7.1.60
4
4
 
5
5
 
6
6
  ## Use
@@ -27,15 +27,14 @@ declare namespace FLib {
27
27
  onOpenAtStart: Callback;
28
28
  onOpen: Callback;
29
29
  onClose: CloseCallback;
30
- animations: Partial<AnimationOptions>;
30
+ animations: AnimationOptions;
31
31
  }
32
32
 
33
- interface OptionsInit extends Partial<Options> {
34
- animations?: Partial<AnimationOptions>;
35
- }
33
+ type OptionsInit = Partial<Options>
36
34
 
37
35
  interface TabOptions extends Options {
38
36
  index: number;
37
+ animations: AnimationOptions;
39
38
  }
40
39
  }
41
40
  }
package/Types/Tabs.d.ts CHANGED
@@ -3,17 +3,11 @@ declare namespace FLib {
3
3
  type AnimationFunction = ( $tab: HTMLElement, $panel: HTMLElement ) => Promise<void>;
4
4
  type Callback = ( $tab: HTMLElement, $panel: HTMLElement, autoClose?: boolean ) => void;
5
5
 
6
- interface Tab {
7
- isOpened: boolean;
8
- index: number;
9
- close( autoClose?: boolean ): this;
10
- open( autoOpen?: boolean ): this;
11
- destroy(): this;
12
- }
13
-
14
6
  type Options = {
15
7
  /** @defaultValue 'li[aria-selected]' */
16
8
  tabSelector: string;
9
+ /** @defaultValue false */
10
+ selectOnFocus?: boolean;
17
11
  onOpenAtStart?: Callback;
18
12
  onOpen?: Callback;
19
13
  onClose?: Callback;
@@ -27,6 +21,7 @@ declare namespace FLib {
27
21
 
28
22
  type TabOptions = Options & {
29
23
  onOpenTab?: ( tab: Tab ) => void;
24
+ onFocusTab?: ( tab: Tab ) => void;
30
25
  index: number;
31
26
  }
32
27
  }
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.59",
5
+ "version": "7.1.60",
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": [],