@dodlhuat/basix 1.2.0 → 1.2.2
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/README.md +266 -6
- package/css/accordion.scss +86 -87
- package/css/alert.scss +137 -137
- package/css/button.scss +48 -0
- package/css/calendar.scss +957 -0
- package/css/card.scss +65 -65
- package/css/chart.scss +270 -157
- package/css/chat-bubbles.scss +134 -68
- package/css/chips.scss +109 -19
- package/css/colors.scss +32 -32
- package/css/datepicker.scss +336 -336
- package/css/defaults.scss +90 -90
- package/css/docs.scss +529 -0
- package/css/editor.scss +36 -0
- package/css/file-uploader.scss +1 -1
- package/css/flyout-menu.scss +361 -361
- package/css/form.scss +0 -15
- package/css/gallery.scss +65 -6
- package/css/grid.scss +41 -40
- package/css/group-picker.scss +345 -0
- package/css/guitar-chords.css +250 -250
- package/css/icons.scss +330 -330
- package/css/parameters.scss +3 -3
- package/css/placeholder.scss +33 -33
- package/css/popover.scss +206 -0
- package/css/progress.scss +76 -32
- package/css/properties.scss +51 -36
- package/css/push-menu.scss +302 -174
- package/css/reset.scss +39 -39
- package/css/scrollbar.scss +62 -5
- package/css/sidebar-nav.scss +92 -0
- package/css/spinner.scss +65 -65
- package/css/stepper.scss +48 -12
- package/css/style.css +3155 -254
- package/css/style.css.map +1 -1
- package/css/style.min.css +1 -1
- package/css/style.scss +51 -45
- package/css/table.scss +199 -199
- package/css/tabs.scss +154 -123
- package/css/timeline.scss +83 -38
- package/css/timepicker.scss +100 -5
- package/css/toast.scss +81 -81
- package/css/virtual-dropdown.scss +35 -29
- package/js/calendar.js +532 -0
- package/js/calendar.ts +706 -0
- package/js/chart.js +573 -257
- package/js/chart.ts +692 -0
- package/js/code-viewer.js +10 -10
- package/js/code-viewer.ts +188 -188
- package/js/datepicker.ts +627 -627
- package/js/docs-nav.js +204 -0
- package/js/dropdown.ts +179 -179
- package/js/editor.js +50 -6
- package/js/editor.ts +483 -444
- package/js/file-uploader.js +1 -0
- package/js/file-uploader.ts +1 -0
- package/js/flyout-menu.js +14 -14
- package/js/flyout-menu.ts +249 -249
- package/js/form-builder.js +106 -106
- package/js/gallery.js +14 -8
- package/js/gallery.ts +245 -236
- package/js/group-picker.js +342 -0
- package/js/group-picker.ts +447 -0
- package/js/guitar-chords.js +268 -268
- package/js/lazy-loader.js +121 -121
- package/js/modal.ts +166 -166
- package/js/popover.js +163 -0
- package/js/popover.ts +219 -0
- package/js/position.js +108 -0
- package/js/position.ts +111 -0
- package/js/push-menu.js +113 -0
- package/js/push-menu.ts +284 -145
- package/js/request.js +50 -50
- package/js/scroll.ts +47 -47
- package/js/scrollbar.js +13 -0
- package/js/scrollbar.ts +324 -307
- package/js/select.ts +216 -216
- package/js/sidebar-nav.js +41 -0
- package/js/sidebar-nav.ts +66 -0
- package/js/table.ts +452 -452
- package/js/tabs.ts +279 -279
- package/js/theme.js +17 -6
- package/js/theme.ts +234 -224
- package/js/toast.ts +137 -137
- package/js/tooltip.js +6 -60
- package/js/tooltip.ts +184 -251
- package/js/tsconfig.json +18 -18
- package/js/utils.ts +83 -83
- package/js/virtual-dropdown.js +25 -25
- package/js/virtual-dropdown.ts +365 -365
- package/package.json +37 -39
- package/js/index.js +0 -816
- package/js/index.ts +0 -987
package/js/tabs.ts
CHANGED
|
@@ -1,280 +1,280 @@
|
|
|
1
|
-
type TabLayout = 'horizontal' | 'vertical';
|
|
2
|
-
type MenuPosition = 'top' | 'bottom' | 'left' | 'right';
|
|
3
|
-
|
|
4
|
-
interface TabsOptions {
|
|
5
|
-
layout?: TabLayout;
|
|
6
|
-
defaultTab?: number;
|
|
7
|
-
menuPos?: MenuPosition;
|
|
8
|
-
onChange?: (index: number) => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
class Tabs {
|
|
12
|
-
private container: HTMLElement;
|
|
13
|
-
private options: Required<Omit<TabsOptions, 'onChange'>> & Pick<TabsOptions, 'onChange'>;
|
|
14
|
-
private tabItems: NodeListOf<Element>;
|
|
15
|
-
private tabPanels: NodeListOf<Element>;
|
|
16
|
-
private currentTab: number;
|
|
17
|
-
|
|
18
|
-
constructor(elementOrSelector: string | HTMLElement, options: TabsOptions = {}) {
|
|
19
|
-
const element = typeof elementOrSelector === 'string'
|
|
20
|
-
? document.querySelector<HTMLElement>(elementOrSelector)
|
|
21
|
-
: elementOrSelector;
|
|
22
|
-
|
|
23
|
-
if (!element) {
|
|
24
|
-
throw new Error(`Tabs: Element not found for selector "${elementOrSelector}"`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
this.container = element;
|
|
28
|
-
|
|
29
|
-
// Set default options
|
|
30
|
-
const layout = options.layout || 'horizontal';
|
|
31
|
-
this.options = {
|
|
32
|
-
layout,
|
|
33
|
-
defaultTab: options.defaultTab ?? 0,
|
|
34
|
-
menuPos: options.menuPos || (layout === 'vertical' ? 'left' : 'top'),
|
|
35
|
-
onChange: options.onChange
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
this.currentTab = this.options.defaultTab;
|
|
39
|
-
this.tabItems = document.querySelectorAll('.tab-item'); // Will be set in init
|
|
40
|
-
this.tabPanels = document.querySelectorAll('.tab-panel'); // Will be set in init
|
|
41
|
-
|
|
42
|
-
this.init();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Initializes the tabs component
|
|
47
|
-
*/
|
|
48
|
-
private init(): void {
|
|
49
|
-
// Apply layout class
|
|
50
|
-
if (this.options.layout === 'vertical') {
|
|
51
|
-
this.container.classList.add('tabs-vertical');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
this.tabItems = this.container.querySelectorAll('.tab-item');
|
|
55
|
-
this.tabPanels = this.container.querySelectorAll('.tab-panel');
|
|
56
|
-
|
|
57
|
-
// Validate that we have tabs and panels
|
|
58
|
-
if (this.tabItems.length === 0) {
|
|
59
|
-
console.warn('No tab items found in container');
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (this.tabPanels.length === 0) {
|
|
64
|
-
console.warn('No tab panels found in container');
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (this.tabItems.length !== this.tabPanels.length) {
|
|
69
|
-
console.warn('Number of tab items does not match number of tab panels');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
this.bindEvents();
|
|
73
|
-
this.activateTab(this.options.defaultTab);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Binds click events to tab items
|
|
78
|
-
*/
|
|
79
|
-
private bindEvents(): void {
|
|
80
|
-
this.tabItems.forEach((item, index) => {
|
|
81
|
-
item.addEventListener('click', (e: Event) => {
|
|
82
|
-
e.preventDefault();
|
|
83
|
-
this.activateTab(index);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// Add keyboard navigation for accessibility
|
|
87
|
-
item.addEventListener('keydown', (e: Event) => {
|
|
88
|
-
const keyEvent = e as KeyboardEvent;
|
|
89
|
-
this.handleKeyboardNavigation(keyEvent, index);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// Set ARIA attributes
|
|
93
|
-
item.setAttribute('role', 'tab');
|
|
94
|
-
item.setAttribute('tabindex', index === this.options.defaultTab ? '0' : '-1');
|
|
95
|
-
item.setAttribute('aria-selected', index === this.options.defaultTab ? 'true' : 'false');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Set ARIA attributes for panels
|
|
99
|
-
this.tabPanels.forEach((panel, index) => {
|
|
100
|
-
panel.setAttribute('role', 'tabpanel');
|
|
101
|
-
panel.setAttribute('aria-hidden', index === this.options.defaultTab ? 'false' : 'true');
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Handles keyboard navigation (Arrow keys, Home, End)
|
|
107
|
-
*/
|
|
108
|
-
private handleKeyboardNavigation(e: KeyboardEvent, currentIndex: number): void {
|
|
109
|
-
let newIndex = currentIndex;
|
|
110
|
-
const isVertical = this.options.layout === 'vertical';
|
|
111
|
-
|
|
112
|
-
switch (e.key) {
|
|
113
|
-
case 'ArrowLeft':
|
|
114
|
-
if (!isVertical) {
|
|
115
|
-
newIndex = currentIndex > 0 ? currentIndex - 1 : this.tabItems.length - 1;
|
|
116
|
-
e.preventDefault();
|
|
117
|
-
}
|
|
118
|
-
break;
|
|
119
|
-
case 'ArrowRight':
|
|
120
|
-
if (!isVertical) {
|
|
121
|
-
newIndex = currentIndex < this.tabItems.length - 1 ? currentIndex + 1 : 0;
|
|
122
|
-
e.preventDefault();
|
|
123
|
-
}
|
|
124
|
-
break;
|
|
125
|
-
case 'ArrowUp':
|
|
126
|
-
if (isVertical) {
|
|
127
|
-
newIndex = currentIndex > 0 ? currentIndex - 1 : this.tabItems.length - 1;
|
|
128
|
-
e.preventDefault();
|
|
129
|
-
}
|
|
130
|
-
break;
|
|
131
|
-
case 'ArrowDown':
|
|
132
|
-
if (isVertical) {
|
|
133
|
-
newIndex = currentIndex < this.tabItems.length - 1 ? currentIndex + 1 : 0;
|
|
134
|
-
e.preventDefault();
|
|
135
|
-
}
|
|
136
|
-
break;
|
|
137
|
-
case 'Home':
|
|
138
|
-
newIndex = 0;
|
|
139
|
-
e.preventDefault();
|
|
140
|
-
break;
|
|
141
|
-
case 'End':
|
|
142
|
-
newIndex = this.tabItems.length - 1;
|
|
143
|
-
e.preventDefault();
|
|
144
|
-
break;
|
|
145
|
-
default:
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (newIndex !== currentIndex) {
|
|
150
|
-
this.activateTab(newIndex);
|
|
151
|
-
(this.tabItems[newIndex] as HTMLElement).focus();
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Activates a tab by index
|
|
157
|
-
*/
|
|
158
|
-
private activateTab(index: number): void {
|
|
159
|
-
if (index < 0 || index >= this.tabItems.length) {
|
|
160
|
-
console.warn(`Invalid tab index: ${index}`);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Remove active class from all
|
|
165
|
-
this.tabItems.forEach((item, i) => {
|
|
166
|
-
item.classList.remove('active');
|
|
167
|
-
item.setAttribute('tabindex', '-1');
|
|
168
|
-
item.setAttribute('aria-selected', 'false');
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
this.tabPanels.forEach((panel) => {
|
|
172
|
-
panel.classList.remove('active');
|
|
173
|
-
panel.setAttribute('aria-hidden', 'true');
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// Add active class to selected
|
|
177
|
-
this.tabItems[index].classList.add('active');
|
|
178
|
-
this.tabItems[index].setAttribute('tabindex', '0');
|
|
179
|
-
this.tabItems[index].setAttribute('aria-selected', 'true');
|
|
180
|
-
|
|
181
|
-
this.tabPanels[index].classList.add('active');
|
|
182
|
-
this.tabPanels[index].setAttribute('aria-hidden', 'false');
|
|
183
|
-
|
|
184
|
-
const previousTab = this.currentTab;
|
|
185
|
-
this.currentTab = index;
|
|
186
|
-
|
|
187
|
-
// Call onChange callback if provided
|
|
188
|
-
if (this.options.onChange && previousTab !== index) {
|
|
189
|
-
this.options.onChange(index);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Public API: Programmatically activate a tab
|
|
195
|
-
*/
|
|
196
|
-
public goToTab(index: number): void {
|
|
197
|
-
this.activateTab(index);
|
|
198
|
-
|
|
199
|
-
// Focus the tab for keyboard users
|
|
200
|
-
if (this.tabItems[index]) {
|
|
201
|
-
(this.tabItems[index] as HTMLElement).focus();
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Public API: Get the currently active tab index
|
|
207
|
-
*/
|
|
208
|
-
public getCurrentTab(): number {
|
|
209
|
-
return this.currentTab;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Public API: Get the total number of tabs
|
|
214
|
-
*/
|
|
215
|
-
public getTabCount(): number {
|
|
216
|
-
return this.tabItems.length;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Public API: Enable a tab
|
|
221
|
-
*/
|
|
222
|
-
public enableTab(index: number): void {
|
|
223
|
-
if (index < 0 || index >= this.tabItems.length) return;
|
|
224
|
-
|
|
225
|
-
const tab = this.tabItems[index] as HTMLElement;
|
|
226
|
-
tab.classList.remove('disabled');
|
|
227
|
-
tab.removeAttribute('aria-disabled');
|
|
228
|
-
tab.style.pointerEvents = '';
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Public API: Disable a tab
|
|
233
|
-
*/
|
|
234
|
-
public disableTab(index: number): void {
|
|
235
|
-
if (index < 0 || index >= this.tabItems.length) return;
|
|
236
|
-
|
|
237
|
-
const tab = this.tabItems[index] as HTMLElement;
|
|
238
|
-
tab.classList.add('disabled');
|
|
239
|
-
tab.setAttribute('aria-disabled', 'true');
|
|
240
|
-
tab.style.pointerEvents = 'none';
|
|
241
|
-
|
|
242
|
-
// If disabling the current tab, switch to the first enabled tab
|
|
243
|
-
if (index === this.currentTab) {
|
|
244
|
-
const firstEnabled = Array.from(this.tabItems).findIndex(
|
|
245
|
-
(item) => !item.classList.contains('disabled')
|
|
246
|
-
);
|
|
247
|
-
if (firstEnabled !== -1) {
|
|
248
|
-
this.activateTab(firstEnabled);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Public API: Destroy the tabs instance and clean up
|
|
255
|
-
*/
|
|
256
|
-
public destroy(): void {
|
|
257
|
-
// Remove event listeners by cloning and replacing nodes
|
|
258
|
-
this.tabItems.forEach((item) => {
|
|
259
|
-
const newItem = item.cloneNode(true);
|
|
260
|
-
item.parentNode?.replaceChild(newItem, item);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
// Remove classes
|
|
264
|
-
this.container.classList.remove('tabs-vertical');
|
|
265
|
-
|
|
266
|
-
// Remove ARIA attributes
|
|
267
|
-
this.tabItems.forEach((item) => {
|
|
268
|
-
item.removeAttribute('role');
|
|
269
|
-
item.removeAttribute('tabindex');
|
|
270
|
-
item.removeAttribute('aria-selected');
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
this.tabPanels.forEach((panel) => {
|
|
274
|
-
panel.removeAttribute('role');
|
|
275
|
-
panel.removeAttribute('aria-hidden');
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
1
|
+
type TabLayout = 'horizontal' | 'vertical';
|
|
2
|
+
type MenuPosition = 'top' | 'bottom' | 'left' | 'right';
|
|
3
|
+
|
|
4
|
+
interface TabsOptions {
|
|
5
|
+
layout?: TabLayout;
|
|
6
|
+
defaultTab?: number;
|
|
7
|
+
menuPos?: MenuPosition;
|
|
8
|
+
onChange?: (index: number) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class Tabs {
|
|
12
|
+
private container: HTMLElement;
|
|
13
|
+
private options: Required<Omit<TabsOptions, 'onChange'>> & Pick<TabsOptions, 'onChange'>;
|
|
14
|
+
private tabItems: NodeListOf<Element>;
|
|
15
|
+
private tabPanels: NodeListOf<Element>;
|
|
16
|
+
private currentTab: number;
|
|
17
|
+
|
|
18
|
+
constructor(elementOrSelector: string | HTMLElement, options: TabsOptions = {}) {
|
|
19
|
+
const element = typeof elementOrSelector === 'string'
|
|
20
|
+
? document.querySelector<HTMLElement>(elementOrSelector)
|
|
21
|
+
: elementOrSelector;
|
|
22
|
+
|
|
23
|
+
if (!element) {
|
|
24
|
+
throw new Error(`Tabs: Element not found for selector "${elementOrSelector}"`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.container = element;
|
|
28
|
+
|
|
29
|
+
// Set default options
|
|
30
|
+
const layout = options.layout || 'horizontal';
|
|
31
|
+
this.options = {
|
|
32
|
+
layout,
|
|
33
|
+
defaultTab: options.defaultTab ?? 0,
|
|
34
|
+
menuPos: options.menuPos || (layout === 'vertical' ? 'left' : 'top'),
|
|
35
|
+
onChange: options.onChange
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this.currentTab = this.options.defaultTab;
|
|
39
|
+
this.tabItems = document.querySelectorAll('.tab-item'); // Will be set in init
|
|
40
|
+
this.tabPanels = document.querySelectorAll('.tab-panel'); // Will be set in init
|
|
41
|
+
|
|
42
|
+
this.init();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initializes the tabs component
|
|
47
|
+
*/
|
|
48
|
+
private init(): void {
|
|
49
|
+
// Apply layout class
|
|
50
|
+
if (this.options.layout === 'vertical') {
|
|
51
|
+
this.container.classList.add('tabs-vertical');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.tabItems = this.container.querySelectorAll('.tab-item');
|
|
55
|
+
this.tabPanels = this.container.querySelectorAll('.tab-panel');
|
|
56
|
+
|
|
57
|
+
// Validate that we have tabs and panels
|
|
58
|
+
if (this.tabItems.length === 0) {
|
|
59
|
+
console.warn('No tab items found in container');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (this.tabPanels.length === 0) {
|
|
64
|
+
console.warn('No tab panels found in container');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (this.tabItems.length !== this.tabPanels.length) {
|
|
69
|
+
console.warn('Number of tab items does not match number of tab panels');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.bindEvents();
|
|
73
|
+
this.activateTab(this.options.defaultTab);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Binds click events to tab items
|
|
78
|
+
*/
|
|
79
|
+
private bindEvents(): void {
|
|
80
|
+
this.tabItems.forEach((item, index) => {
|
|
81
|
+
item.addEventListener('click', (e: Event) => {
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
this.activateTab(index);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Add keyboard navigation for accessibility
|
|
87
|
+
item.addEventListener('keydown', (e: Event) => {
|
|
88
|
+
const keyEvent = e as KeyboardEvent;
|
|
89
|
+
this.handleKeyboardNavigation(keyEvent, index);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Set ARIA attributes
|
|
93
|
+
item.setAttribute('role', 'tab');
|
|
94
|
+
item.setAttribute('tabindex', index === this.options.defaultTab ? '0' : '-1');
|
|
95
|
+
item.setAttribute('aria-selected', index === this.options.defaultTab ? 'true' : 'false');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Set ARIA attributes for panels
|
|
99
|
+
this.tabPanels.forEach((panel, index) => {
|
|
100
|
+
panel.setAttribute('role', 'tabpanel');
|
|
101
|
+
panel.setAttribute('aria-hidden', index === this.options.defaultTab ? 'false' : 'true');
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Handles keyboard navigation (Arrow keys, Home, End)
|
|
107
|
+
*/
|
|
108
|
+
private handleKeyboardNavigation(e: KeyboardEvent, currentIndex: number): void {
|
|
109
|
+
let newIndex = currentIndex;
|
|
110
|
+
const isVertical = this.options.layout === 'vertical';
|
|
111
|
+
|
|
112
|
+
switch (e.key) {
|
|
113
|
+
case 'ArrowLeft':
|
|
114
|
+
if (!isVertical) {
|
|
115
|
+
newIndex = currentIndex > 0 ? currentIndex - 1 : this.tabItems.length - 1;
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
case 'ArrowRight':
|
|
120
|
+
if (!isVertical) {
|
|
121
|
+
newIndex = currentIndex < this.tabItems.length - 1 ? currentIndex + 1 : 0;
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
case 'ArrowUp':
|
|
126
|
+
if (isVertical) {
|
|
127
|
+
newIndex = currentIndex > 0 ? currentIndex - 1 : this.tabItems.length - 1;
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
case 'ArrowDown':
|
|
132
|
+
if (isVertical) {
|
|
133
|
+
newIndex = currentIndex < this.tabItems.length - 1 ? currentIndex + 1 : 0;
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
case 'Home':
|
|
138
|
+
newIndex = 0;
|
|
139
|
+
e.preventDefault();
|
|
140
|
+
break;
|
|
141
|
+
case 'End':
|
|
142
|
+
newIndex = this.tabItems.length - 1;
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
break;
|
|
145
|
+
default:
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (newIndex !== currentIndex) {
|
|
150
|
+
this.activateTab(newIndex);
|
|
151
|
+
(this.tabItems[newIndex] as HTMLElement).focus();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Activates a tab by index
|
|
157
|
+
*/
|
|
158
|
+
private activateTab(index: number): void {
|
|
159
|
+
if (index < 0 || index >= this.tabItems.length) {
|
|
160
|
+
console.warn(`Invalid tab index: ${index}`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Remove active class from all
|
|
165
|
+
this.tabItems.forEach((item, i) => {
|
|
166
|
+
item.classList.remove('active');
|
|
167
|
+
item.setAttribute('tabindex', '-1');
|
|
168
|
+
item.setAttribute('aria-selected', 'false');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
this.tabPanels.forEach((panel) => {
|
|
172
|
+
panel.classList.remove('active');
|
|
173
|
+
panel.setAttribute('aria-hidden', 'true');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Add active class to selected
|
|
177
|
+
this.tabItems[index].classList.add('active');
|
|
178
|
+
this.tabItems[index].setAttribute('tabindex', '0');
|
|
179
|
+
this.tabItems[index].setAttribute('aria-selected', 'true');
|
|
180
|
+
|
|
181
|
+
this.tabPanels[index].classList.add('active');
|
|
182
|
+
this.tabPanels[index].setAttribute('aria-hidden', 'false');
|
|
183
|
+
|
|
184
|
+
const previousTab = this.currentTab;
|
|
185
|
+
this.currentTab = index;
|
|
186
|
+
|
|
187
|
+
// Call onChange callback if provided
|
|
188
|
+
if (this.options.onChange && previousTab !== index) {
|
|
189
|
+
this.options.onChange(index);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Public API: Programmatically activate a tab
|
|
195
|
+
*/
|
|
196
|
+
public goToTab(index: number): void {
|
|
197
|
+
this.activateTab(index);
|
|
198
|
+
|
|
199
|
+
// Focus the tab for keyboard users
|
|
200
|
+
if (this.tabItems[index]) {
|
|
201
|
+
(this.tabItems[index] as HTMLElement).focus();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Public API: Get the currently active tab index
|
|
207
|
+
*/
|
|
208
|
+
public getCurrentTab(): number {
|
|
209
|
+
return this.currentTab;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Public API: Get the total number of tabs
|
|
214
|
+
*/
|
|
215
|
+
public getTabCount(): number {
|
|
216
|
+
return this.tabItems.length;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Public API: Enable a tab
|
|
221
|
+
*/
|
|
222
|
+
public enableTab(index: number): void {
|
|
223
|
+
if (index < 0 || index >= this.tabItems.length) return;
|
|
224
|
+
|
|
225
|
+
const tab = this.tabItems[index] as HTMLElement;
|
|
226
|
+
tab.classList.remove('disabled');
|
|
227
|
+
tab.removeAttribute('aria-disabled');
|
|
228
|
+
tab.style.pointerEvents = '';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Public API: Disable a tab
|
|
233
|
+
*/
|
|
234
|
+
public disableTab(index: number): void {
|
|
235
|
+
if (index < 0 || index >= this.tabItems.length) return;
|
|
236
|
+
|
|
237
|
+
const tab = this.tabItems[index] as HTMLElement;
|
|
238
|
+
tab.classList.add('disabled');
|
|
239
|
+
tab.setAttribute('aria-disabled', 'true');
|
|
240
|
+
tab.style.pointerEvents = 'none';
|
|
241
|
+
|
|
242
|
+
// If disabling the current tab, switch to the first enabled tab
|
|
243
|
+
if (index === this.currentTab) {
|
|
244
|
+
const firstEnabled = Array.from(this.tabItems).findIndex(
|
|
245
|
+
(item) => !item.classList.contains('disabled')
|
|
246
|
+
);
|
|
247
|
+
if (firstEnabled !== -1) {
|
|
248
|
+
this.activateTab(firstEnabled);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Public API: Destroy the tabs instance and clean up
|
|
255
|
+
*/
|
|
256
|
+
public destroy(): void {
|
|
257
|
+
// Remove event listeners by cloning and replacing nodes
|
|
258
|
+
this.tabItems.forEach((item) => {
|
|
259
|
+
const newItem = item.cloneNode(true);
|
|
260
|
+
item.parentNode?.replaceChild(newItem, item);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Remove classes
|
|
264
|
+
this.container.classList.remove('tabs-vertical');
|
|
265
|
+
|
|
266
|
+
// Remove ARIA attributes
|
|
267
|
+
this.tabItems.forEach((item) => {
|
|
268
|
+
item.removeAttribute('role');
|
|
269
|
+
item.removeAttribute('tabindex');
|
|
270
|
+
item.removeAttribute('aria-selected');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
this.tabPanels.forEach((panel) => {
|
|
274
|
+
panel.removeAttribute('role');
|
|
275
|
+
panel.removeAttribute('aria-hidden');
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
280
|
export { Tabs };
|
package/js/theme.js
CHANGED
|
@@ -80,14 +80,25 @@ class Theme {
|
|
|
80
80
|
// Update button state
|
|
81
81
|
toggleBtn.setAttribute('aria-pressed', String(isDark));
|
|
82
82
|
toggleBtn.setAttribute('aria-label', `Switch to ${isDark ? 'light' : 'dark'} mode`);
|
|
83
|
-
// Update icon
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
icon.
|
|
83
|
+
// Update icon — SVG sprite via <use> or font icon via class
|
|
84
|
+
const useEl = icon.querySelector('use');
|
|
85
|
+
if (useEl) {
|
|
86
|
+
const iconName = isDark ? icon.dataset.iconDark : icon.dataset.iconLight;
|
|
87
|
+
if (iconName) {
|
|
88
|
+
const existingHref = useEl.getAttribute('href') ?? '';
|
|
89
|
+
const basePath = existingHref.includes('#') ? existingHref.split('#')[0] : '';
|
|
90
|
+
useEl.setAttribute('href', `${basePath}#${iconName}`);
|
|
91
|
+
}
|
|
87
92
|
}
|
|
88
93
|
else {
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
if (isDark) {
|
|
95
|
+
icon.classList.remove('icon-light');
|
|
96
|
+
icon.classList.add('icon-dark');
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
icon.classList.remove('icon-dark');
|
|
100
|
+
icon.classList.add('icon-light');
|
|
101
|
+
}
|
|
91
102
|
}
|
|
92
103
|
}
|
|
93
104
|
/**
|