mtrl 0.3.1 → 0.3.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/.env +15 -0
- package/CONTRIBUTING.md +8 -8
- package/DOCS.md +3 -3
- package/README.md +43 -20
- package/TESTING.md +128 -18
- package/dist/index.js +14865 -0
- package/git-user-stats.js +545 -0
- package/index.ts +9 -67
- package/package.json +8 -3
- package/src/components/badge/api.ts +15 -1
- package/src/components/badge/badge.ts +43 -4
- package/src/components/badge/config.ts +40 -8
- package/src/components/badge/index.ts +64 -3
- package/src/components/badge/types.ts +175 -33
- package/src/components/button/api.ts +63 -1
- package/src/components/button/button.ts +39 -3
- package/src/components/button/config.ts +21 -4
- package/src/components/button/index.ts +26 -1
- package/src/components/button/types.ts +7 -1
- package/src/components/card/api.ts +78 -9
- package/src/components/card/card.ts +58 -3
- package/src/components/card/config.ts +41 -11
- package/src/components/card/features.ts +39 -12
- package/src/components/card/index.ts +84 -19
- package/src/components/card/types.ts +218 -29
- package/src/components/carousel/carousel.ts +92 -28
- package/src/components/carousel/constants.ts +107 -21
- package/src/components/carousel/index.ts +31 -13
- package/src/components/checkbox/checkbox.ts +83 -16
- package/src/components/checkbox/index.ts +43 -1
- package/src/components/checkbox/types.ts +219 -32
- package/src/components/chips/api.ts +194 -0
- package/src/components/{chip → chips/chip}/api.ts +42 -2
- package/src/components/chips/chip/chip.ts +131 -0
- package/src/components/{chip → chips/chip}/config.ts +3 -3
- package/src/components/chips/chip/index.ts +3 -0
- package/src/components/chips/chips.md +481 -0
- package/src/components/chips/chips.ts +75 -0
- package/src/components/chips/config.ts +109 -0
- package/src/components/chips/constants.ts +61 -0
- package/src/components/chips/features/chip-items.ts +33 -0
- package/src/components/chips/features/container.ts +77 -0
- package/src/components/chips/features/controller.ts +448 -0
- package/src/components/chips/features/index.ts +5 -0
- package/src/components/chips/features/label.ts +108 -0
- package/src/components/chips/index.ts +11 -0
- package/src/components/chips/schema.ts +61 -0
- package/src/components/{chip → chips}/types.ts +203 -92
- package/src/components/dialog/dialog.ts +99 -16
- package/src/components/dialog/index.ts +97 -1
- package/src/components/dialog/types.ts +375 -69
- package/src/components/divider/config.ts +90 -6
- package/src/components/divider/divider.ts +32 -2
- package/src/components/divider/features.ts +26 -0
- package/src/components/divider/index.ts +30 -0
- package/src/components/divider/types.ts +86 -9
- package/src/components/extended-fab/api.ts +53 -1
- package/src/components/extended-fab/config.ts +29 -1
- package/src/components/extended-fab/extended-fab.ts +28 -0
- package/src/components/extended-fab/index.ts +36 -0
- package/src/components/extended-fab/types.ts +458 -13
- package/src/components/fab/api.ts +42 -2
- package/src/components/fab/config.ts +29 -1
- package/src/components/fab/fab.ts +16 -2
- package/src/components/fab/index.ts +35 -0
- package/src/components/fab/types.ts +374 -10
- package/src/components/list/api.ts +12 -2
- package/src/components/list/config.ts +21 -0
- package/src/components/list/features.ts +6 -0
- package/src/components/list/index.ts +56 -1
- package/src/components/list/list-item.ts +46 -2
- package/src/components/list/list.ts +73 -2
- package/src/components/list/types.ts +172 -0
- package/src/components/list/utils.ts +26 -2
- package/src/components/menu/api.ts +217 -20
- package/src/components/menu/config.ts +27 -0
- package/src/components/menu/features/visibility.ts +55 -6
- package/src/components/menu/index.ts +64 -0
- package/src/components/menu/menu-item.ts +46 -3
- package/src/components/menu/menu.ts +77 -1
- package/src/components/menu/types.ts +404 -39
- package/src/components/sheet/config.ts +1 -2
- package/src/components/sheet/features/gestures.ts +1 -1
- package/src/components/sheet/features/position.ts +1 -2
- package/src/components/sheet/features/state.ts +1 -1
- package/src/components/sheet/index.ts +10 -2
- package/src/components/sheet/sheet.ts +1 -2
- package/src/components/sheet/types.ts +29 -1
- package/src/components/slider/api.ts +1 -1
- package/src/components/slider/config.ts +1 -1
- package/src/components/slider/features/controller.ts +1 -1
- package/src/components/slider/features/handlers.ts +1 -1
- package/src/components/slider/features/states.ts +1 -1
- package/src/components/slider/index.ts +12 -5
- package/src/components/slider/schema.ts +1 -1
- package/src/components/slider/types.ts +31 -0
- package/src/components/tabs/tab-api.ts +1 -1
- package/src/components/tabs/types.ts +1 -1
- package/src/components/tooltip/api.ts +6 -2
- package/src/components/tooltip/config.ts +9 -28
- package/src/components/tooltip/index.ts +10 -1
- package/src/components/tooltip/types.ts +38 -3
- package/src/index.ts +129 -31
- package/src/styles/abstract/_mixins.scss +23 -9
- package/src/styles/abstract/_variables.scss +14 -4
- package/src/styles/components/_card.scss +1 -1
- package/src/styles/components/_chip.scss +323 -113
- package/src/styles/components/_tabs.scss +1 -1
- package/CLAUDE.md +0 -33
- package/src/components/checkbox/constants.ts +0 -37
- package/src/components/chip/chip-set.ts +0 -225
- package/src/components/chip/chip.ts +0 -118
- package/src/components/chip/constants.ts +0 -28
- package/src/components/chip/index.ts +0 -12
- package/src/components/list/constants.ts +0 -116
- package/src/components/sheet/constants.ts +0 -20
- package/src/components/slider/constants.ts +0 -32
- package/src/components/tooltip/constants.ts +0 -27
- package/test/components/badge.test.ts +0 -545
- package/test/components/bottom-app-bar.test.ts +0 -303
- package/test/components/button.test.ts +0 -233
- package/test/components/card.test.ts +0 -560
- package/test/components/carousel.test.ts +0 -951
- package/test/components/checkbox.test.ts +0 -462
- package/test/components/chip.test.ts +0 -692
- package/test/components/datepicker.test.ts +0 -1124
- package/test/components/dialog.test.ts +0 -990
- package/test/components/divider.test.ts +0 -412
- package/test/components/extended-fab.test.ts +0 -672
- package/test/components/fab.test.ts +0 -561
- package/test/components/list.test.ts +0 -365
- package/test/components/menu.test.ts +0 -718
- package/test/components/navigation.test.ts +0 -186
- package/test/components/progress.test.ts +0 -567
- package/test/components/radios.test.ts +0 -699
- package/test/components/search.test.ts +0 -1135
- package/test/components/segmented-button.test.ts +0 -732
- package/test/components/sheet.test.ts +0 -641
- package/test/components/slider.test.ts +0 -1220
- package/test/components/snackbar.test.ts +0 -461
- package/test/components/switch.test.ts +0 -452
- package/test/components/tabs.test.ts +0 -1369
- package/test/components/textfield.test.ts +0 -400
- package/test/components/timepicker.test.ts +0 -592
- package/test/components/tooltip.test.ts +0 -630
- package/test/components/top-app-bar.test.ts +0 -566
- package/test/core/dom.attributes.test.ts +0 -148
- package/test/core/dom.classes.test.ts +0 -152
- package/test/core/dom.events.test.ts +0 -243
- package/test/core/emitter.test.ts +0 -141
- package/test/core/ripple.test.ts +0 -99
- package/test/core/state.store.test.ts +0 -189
- package/test/core/utils.normalize.test.ts +0 -61
- package/test/core/utils.object.test.ts +0 -120
- package/test/setup.js +0 -371
- package/test/setup.ts +0 -451
- package/tsconfig.json +0 -22
- package/typedoc.json +0 -28
- package/typedoc.simple.json +0 -14
|
@@ -1,1369 +0,0 @@
|
|
|
1
|
-
// test/components/tabs.test.ts
|
|
2
|
-
import { describe, test, expect } from 'bun:test';
|
|
3
|
-
import {
|
|
4
|
-
type TabComponent,
|
|
5
|
-
type TabConfig,
|
|
6
|
-
type TabsComponent,
|
|
7
|
-
type TabsConfig,
|
|
8
|
-
type TabsVariant,
|
|
9
|
-
type TabStates
|
|
10
|
-
} from '../../src/components/tabs/types';
|
|
11
|
-
|
|
12
|
-
// Constants for tabs variants
|
|
13
|
-
const TABS_VARIANTS = {
|
|
14
|
-
PRIMARY: 'primary',
|
|
15
|
-
SECONDARY: 'secondary'
|
|
16
|
-
} as const;
|
|
17
|
-
|
|
18
|
-
// Constants for tab states
|
|
19
|
-
const TAB_STATES = {
|
|
20
|
-
ACTIVE: 'active',
|
|
21
|
-
INACTIVE: 'inactive',
|
|
22
|
-
DISABLED: 'disabled'
|
|
23
|
-
} as const;
|
|
24
|
-
|
|
25
|
-
// Mock tab implementation
|
|
26
|
-
const createMockTab = (config: TabConfig = {}): TabComponent => {
|
|
27
|
-
// Create main element
|
|
28
|
-
const element = document.createElement('button');
|
|
29
|
-
element.className = 'mtrl-tab';
|
|
30
|
-
element.type = 'button';
|
|
31
|
-
|
|
32
|
-
// Default settings
|
|
33
|
-
const settings = {
|
|
34
|
-
state: config.state || TAB_STATES.INACTIVE,
|
|
35
|
-
disabled: config.disabled || false,
|
|
36
|
-
text: config.text || '',
|
|
37
|
-
icon: config.icon || '',
|
|
38
|
-
badge: config.badge || '',
|
|
39
|
-
value: config.value || '',
|
|
40
|
-
prefix: config.prefix || 'mtrl',
|
|
41
|
-
componentName: config.componentName || 'tab',
|
|
42
|
-
ripple: config.ripple !== undefined ? config.ripple : true,
|
|
43
|
-
variant: config.variant || 'primary'
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// Apply state class
|
|
47
|
-
element.classList.add(`mtrl-tab--${settings.state}`);
|
|
48
|
-
|
|
49
|
-
// Apply variant class
|
|
50
|
-
element.classList.add(`mtrl-tab--${settings.variant}`);
|
|
51
|
-
|
|
52
|
-
// Apply disabled state
|
|
53
|
-
if (settings.disabled) {
|
|
54
|
-
element.disabled = true;
|
|
55
|
-
element.classList.add('mtrl-tab--disabled');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Set value attribute
|
|
59
|
-
if (settings.value) {
|
|
60
|
-
element.setAttribute('data-value', settings.value);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Apply additional classes
|
|
64
|
-
if (config.class) {
|
|
65
|
-
const classes = config.class.split(' ');
|
|
66
|
-
classes.forEach(className => element.classList.add(className));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Create container for content (used for layout)
|
|
70
|
-
const contentContainer = document.createElement('div');
|
|
71
|
-
contentContainer.className = 'mtrl-tab__content';
|
|
72
|
-
|
|
73
|
-
// Create icon if provided
|
|
74
|
-
let iconElement: HTMLElement | null = null;
|
|
75
|
-
if (settings.icon) {
|
|
76
|
-
iconElement = document.createElement('span');
|
|
77
|
-
iconElement.className = 'mtrl-tab__icon';
|
|
78
|
-
iconElement.innerHTML = settings.icon;
|
|
79
|
-
|
|
80
|
-
if (config.iconSize) {
|
|
81
|
-
iconElement.style.width = config.iconSize;
|
|
82
|
-
iconElement.style.height = config.iconSize;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
contentContainer.appendChild(iconElement);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Create text element
|
|
89
|
-
const textElement = document.createElement('span');
|
|
90
|
-
textElement.className = 'mtrl-tab__text';
|
|
91
|
-
if (settings.text) {
|
|
92
|
-
textElement.textContent = settings.text;
|
|
93
|
-
}
|
|
94
|
-
contentContainer.appendChild(textElement);
|
|
95
|
-
|
|
96
|
-
// Add content container to tab
|
|
97
|
-
element.appendChild(contentContainer);
|
|
98
|
-
|
|
99
|
-
// Create ripple element if enabled
|
|
100
|
-
if (settings.ripple) {
|
|
101
|
-
const ripple = document.createElement('span');
|
|
102
|
-
ripple.className = 'mtrl-tab__ripple';
|
|
103
|
-
element.appendChild(ripple);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Badge component and elements
|
|
107
|
-
let badgeComponent: any = undefined;
|
|
108
|
-
let badgeElement: HTMLElement | null = null;
|
|
109
|
-
|
|
110
|
-
// Create badge if configured
|
|
111
|
-
if (settings.badge) {
|
|
112
|
-
badgeElement = document.createElement('span');
|
|
113
|
-
badgeElement.className = 'mtrl-badge';
|
|
114
|
-
badgeElement.textContent = settings.badge.toString();
|
|
115
|
-
element.appendChild(badgeElement);
|
|
116
|
-
|
|
117
|
-
// Simple mock badge component
|
|
118
|
-
badgeComponent = {
|
|
119
|
-
element: badgeElement,
|
|
120
|
-
setValue: (value: string | number) => {
|
|
121
|
-
badgeElement!.textContent = value.toString();
|
|
122
|
-
return badgeComponent;
|
|
123
|
-
},
|
|
124
|
-
getValue: () => badgeElement!.textContent || '',
|
|
125
|
-
show: () => {
|
|
126
|
-
badgeElement!.style.display = '';
|
|
127
|
-
return badgeComponent;
|
|
128
|
-
},
|
|
129
|
-
hide: () => {
|
|
130
|
-
badgeElement!.style.display = 'none';
|
|
131
|
-
return badgeComponent;
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Track event handlers
|
|
137
|
-
const eventHandlers: Record<string, Function[]> = {};
|
|
138
|
-
|
|
139
|
-
// Icon API
|
|
140
|
-
const iconAPI = {
|
|
141
|
-
setIcon: (html: string) => {
|
|
142
|
-
settings.icon = html;
|
|
143
|
-
|
|
144
|
-
if (html) {
|
|
145
|
-
if (!iconElement) {
|
|
146
|
-
iconElement = document.createElement('span');
|
|
147
|
-
iconElement.className = 'mtrl-tab__icon';
|
|
148
|
-
contentContainer.insertBefore(iconElement, contentContainer.firstChild);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
iconElement.innerHTML = html;
|
|
152
|
-
} else if (iconElement) {
|
|
153
|
-
iconElement.remove();
|
|
154
|
-
iconElement = null;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return iconAPI;
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
getIcon: () => settings.icon,
|
|
161
|
-
|
|
162
|
-
getElement: () => iconElement
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
// Text API
|
|
166
|
-
const textAPI = {
|
|
167
|
-
setText: (content: string) => {
|
|
168
|
-
settings.text = content;
|
|
169
|
-
textElement.textContent = content;
|
|
170
|
-
return textAPI;
|
|
171
|
-
},
|
|
172
|
-
|
|
173
|
-
getText: () => settings.text,
|
|
174
|
-
|
|
175
|
-
getElement: () => textElement
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// Create the tab component
|
|
179
|
-
const tab: TabComponent = {
|
|
180
|
-
element,
|
|
181
|
-
badge: badgeComponent,
|
|
182
|
-
|
|
183
|
-
getClass: (name: string) => {
|
|
184
|
-
const prefix = settings.prefix;
|
|
185
|
-
return name ? `${prefix}-${name}` : `${prefix}-tab`;
|
|
186
|
-
},
|
|
187
|
-
|
|
188
|
-
getValue: () => settings.value,
|
|
189
|
-
|
|
190
|
-
setValue: (value: string) => {
|
|
191
|
-
settings.value = value;
|
|
192
|
-
element.setAttribute('data-value', value);
|
|
193
|
-
return tab;
|
|
194
|
-
},
|
|
195
|
-
|
|
196
|
-
activate: () => {
|
|
197
|
-
// Remove current state class
|
|
198
|
-
element.classList.remove(`mtrl-tab--${settings.state}`);
|
|
199
|
-
|
|
200
|
-
// Set new state
|
|
201
|
-
settings.state = TAB_STATES.ACTIVE;
|
|
202
|
-
element.classList.add(`mtrl-tab--${settings.state}`);
|
|
203
|
-
|
|
204
|
-
return tab;
|
|
205
|
-
},
|
|
206
|
-
|
|
207
|
-
deactivate: () => {
|
|
208
|
-
// Remove current state class
|
|
209
|
-
element.classList.remove(`mtrl-tab--${settings.state}`);
|
|
210
|
-
|
|
211
|
-
// Set new state
|
|
212
|
-
settings.state = TAB_STATES.INACTIVE;
|
|
213
|
-
element.classList.add(`mtrl-tab--${settings.state}`);
|
|
214
|
-
|
|
215
|
-
return tab;
|
|
216
|
-
},
|
|
217
|
-
|
|
218
|
-
isActive: () => settings.state === TAB_STATES.ACTIVE,
|
|
219
|
-
|
|
220
|
-
enable: () => {
|
|
221
|
-
if (settings.disabled) {
|
|
222
|
-
settings.disabled = false;
|
|
223
|
-
element.disabled = false;
|
|
224
|
-
element.classList.remove('mtrl-tab--disabled');
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return tab;
|
|
228
|
-
},
|
|
229
|
-
|
|
230
|
-
disable: () => {
|
|
231
|
-
if (!settings.disabled) {
|
|
232
|
-
settings.disabled = true;
|
|
233
|
-
element.disabled = true;
|
|
234
|
-
element.classList.add('mtrl-tab--disabled');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return tab;
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
setText: (content: string) => {
|
|
241
|
-
textAPI.setText(content);
|
|
242
|
-
return tab;
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
getText: () => textAPI.getText(),
|
|
246
|
-
|
|
247
|
-
setIcon: (icon: string) => {
|
|
248
|
-
iconAPI.setIcon(icon);
|
|
249
|
-
return tab;
|
|
250
|
-
},
|
|
251
|
-
|
|
252
|
-
getIcon: () => iconAPI.getIcon(),
|
|
253
|
-
|
|
254
|
-
setBadge: (content: string | number) => {
|
|
255
|
-
settings.badge = content;
|
|
256
|
-
|
|
257
|
-
if (!badgeComponent) {
|
|
258
|
-
badgeElement = document.createElement('span');
|
|
259
|
-
badgeElement.className = 'mtrl-badge';
|
|
260
|
-
element.appendChild(badgeElement);
|
|
261
|
-
|
|
262
|
-
badgeComponent = {
|
|
263
|
-
element: badgeElement,
|
|
264
|
-
setValue: (value: string | number) => {
|
|
265
|
-
badgeElement!.textContent = value.toString();
|
|
266
|
-
return badgeComponent;
|
|
267
|
-
},
|
|
268
|
-
getValue: () => badgeElement!.textContent || '',
|
|
269
|
-
show: () => {
|
|
270
|
-
badgeElement!.style.display = '';
|
|
271
|
-
return badgeComponent;
|
|
272
|
-
},
|
|
273
|
-
hide: () => {
|
|
274
|
-
badgeElement!.style.display = 'none';
|
|
275
|
-
return badgeComponent;
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
tab.badge = badgeComponent;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
badgeComponent.setValue(content);
|
|
283
|
-
|
|
284
|
-
return tab;
|
|
285
|
-
},
|
|
286
|
-
|
|
287
|
-
getBadge: () => {
|
|
288
|
-
return badgeComponent ? badgeComponent.getValue() : '';
|
|
289
|
-
},
|
|
290
|
-
|
|
291
|
-
showBadge: () => {
|
|
292
|
-
if (badgeComponent) {
|
|
293
|
-
badgeComponent.show();
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return tab;
|
|
297
|
-
},
|
|
298
|
-
|
|
299
|
-
hideBadge: () => {
|
|
300
|
-
if (badgeComponent) {
|
|
301
|
-
badgeComponent.hide();
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return tab;
|
|
305
|
-
},
|
|
306
|
-
|
|
307
|
-
getBadgeComponent: () => badgeComponent,
|
|
308
|
-
|
|
309
|
-
updateLayoutStyle: () => {
|
|
310
|
-
// Adjust layout based on content
|
|
311
|
-
if (settings.icon && settings.text) {
|
|
312
|
-
element.classList.add('mtrl-tab--with-icon-and-text');
|
|
313
|
-
} else if (settings.icon) {
|
|
314
|
-
element.classList.add('mtrl-tab--icon-only');
|
|
315
|
-
} else if (settings.text) {
|
|
316
|
-
element.classList.add('mtrl-tab--text-only');
|
|
317
|
-
}
|
|
318
|
-
},
|
|
319
|
-
|
|
320
|
-
on: (event: string, handler: Function) => {
|
|
321
|
-
if (!eventHandlers[event]) {
|
|
322
|
-
eventHandlers[event] = [];
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
eventHandlers[event].push(handler);
|
|
326
|
-
|
|
327
|
-
element.addEventListener(event, handler as EventListener);
|
|
328
|
-
return tab;
|
|
329
|
-
},
|
|
330
|
-
|
|
331
|
-
off: (event: string, handler: Function) => {
|
|
332
|
-
if (eventHandlers[event]) {
|
|
333
|
-
eventHandlers[event] = eventHandlers[event].filter(h => h !== handler);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
element.removeEventListener(event, handler as EventListener);
|
|
337
|
-
return tab;
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
destroy: () => {
|
|
341
|
-
// Remove element from DOM if it has a parent
|
|
342
|
-
if (element.parentNode) {
|
|
343
|
-
element.parentNode.removeChild(element);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Remove event listeners
|
|
347
|
-
for (const event in eventHandlers) {
|
|
348
|
-
eventHandlers[event].forEach(handler => {
|
|
349
|
-
element.removeEventListener(event, handler as EventListener);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
eventHandlers[event] = [];
|
|
353
|
-
}
|
|
354
|
-
},
|
|
355
|
-
|
|
356
|
-
disabled: {
|
|
357
|
-
enable: () => {
|
|
358
|
-
if (settings.disabled) {
|
|
359
|
-
settings.disabled = false;
|
|
360
|
-
element.disabled = false;
|
|
361
|
-
element.classList.remove('mtrl-tab--disabled');
|
|
362
|
-
}
|
|
363
|
-
},
|
|
364
|
-
|
|
365
|
-
disable: () => {
|
|
366
|
-
if (!settings.disabled) {
|
|
367
|
-
settings.disabled = true;
|
|
368
|
-
element.disabled = true;
|
|
369
|
-
element.classList.add('mtrl-tab--disabled');
|
|
370
|
-
}
|
|
371
|
-
},
|
|
372
|
-
|
|
373
|
-
isDisabled: () => settings.disabled
|
|
374
|
-
},
|
|
375
|
-
|
|
376
|
-
lifecycle: {
|
|
377
|
-
destroy: () => {
|
|
378
|
-
tab.destroy();
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
// Update layout style based on content
|
|
384
|
-
tab.updateLayoutStyle();
|
|
385
|
-
|
|
386
|
-
return tab;
|
|
387
|
-
};
|
|
388
|
-
|
|
389
|
-
// Mock tabs implementation
|
|
390
|
-
const createMockTabs = (config: TabsConfig = {}): TabsComponent => {
|
|
391
|
-
// Create main container element
|
|
392
|
-
const element = document.createElement('div');
|
|
393
|
-
element.className = 'mtrl-tabs';
|
|
394
|
-
|
|
395
|
-
// Default settings
|
|
396
|
-
const settings = {
|
|
397
|
-
variant: config.variant || TABS_VARIANTS.PRIMARY,
|
|
398
|
-
showDivider: config.showDivider !== undefined ? config.showDivider : true,
|
|
399
|
-
scrollable: config.scrollable !== undefined ? config.scrollable : true,
|
|
400
|
-
prefix: config.prefix || 'mtrl',
|
|
401
|
-
tabs: []
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
// Apply variant class
|
|
405
|
-
element.classList.add(`mtrl-tabs--${settings.variant}`);
|
|
406
|
-
|
|
407
|
-
// Apply scrollable class if enabled
|
|
408
|
-
if (settings.scrollable) {
|
|
409
|
-
element.classList.add('mtrl-tabs--scrollable');
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Apply additional classes
|
|
413
|
-
if (config.class) {
|
|
414
|
-
const classes = config.class.split(' ');
|
|
415
|
-
classes.forEach(className => element.classList.add(className));
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Create tabs container
|
|
419
|
-
const tabsContainer = document.createElement('div');
|
|
420
|
-
tabsContainer.className = 'mtrl-tabs__container';
|
|
421
|
-
|
|
422
|
-
// Create scroll container if scrollable
|
|
423
|
-
let scrollContainer: HTMLElement | undefined;
|
|
424
|
-
if (settings.scrollable) {
|
|
425
|
-
scrollContainer = document.createElement('div');
|
|
426
|
-
scrollContainer.className = 'mtrl-tabs__scroll-container';
|
|
427
|
-
element.appendChild(scrollContainer);
|
|
428
|
-
scrollContainer.appendChild(tabsContainer);
|
|
429
|
-
} else {
|
|
430
|
-
element.appendChild(tabsContainer);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Create divider if enabled
|
|
434
|
-
if (settings.showDivider) {
|
|
435
|
-
const divider = document.createElement('div');
|
|
436
|
-
divider.className = 'mtrl-tabs__divider';
|
|
437
|
-
element.appendChild(divider);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Create indicator element
|
|
441
|
-
const indicator = document.createElement('div');
|
|
442
|
-
indicator.className = 'mtrl-tabs__indicator';
|
|
443
|
-
tabsContainer.appendChild(indicator);
|
|
444
|
-
|
|
445
|
-
// Simple mock indicator component
|
|
446
|
-
const indicatorComponent = {
|
|
447
|
-
element: indicator,
|
|
448
|
-
updatePosition: (targetTab: TabComponent) => {
|
|
449
|
-
// Set indicator position to match the active tab
|
|
450
|
-
if (targetTab.element.offsetLeft !== undefined) {
|
|
451
|
-
indicator.style.left = `${targetTab.element.offsetLeft}px`;
|
|
452
|
-
indicator.style.width = `${targetTab.element.offsetWidth}px`;
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
|
-
hide: () => {
|
|
456
|
-
indicator.style.opacity = '0';
|
|
457
|
-
},
|
|
458
|
-
show: () => {
|
|
459
|
-
indicator.style.opacity = '1';
|
|
460
|
-
}
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
// Track tabs and event handlers
|
|
464
|
-
const tabs: TabComponent[] = [];
|
|
465
|
-
const eventHandlers: Record<string, Function[]> = {};
|
|
466
|
-
|
|
467
|
-
// Emit an event
|
|
468
|
-
const emit = (event: string, data?: any) => {
|
|
469
|
-
if (eventHandlers[event]) {
|
|
470
|
-
eventHandlers[event].forEach(handler => handler(data));
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Call the on.change callback from config if available
|
|
474
|
-
if (event === 'change' && config.on?.change) {
|
|
475
|
-
config.on.change(data);
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
// Handle tab click
|
|
480
|
-
const handleTabClick = (event: Event, tab: TabComponent) => {
|
|
481
|
-
if (tab.disabled?.isDisabled()) {
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// Set as active tab
|
|
486
|
-
tabs.forEach(t => t.deactivate());
|
|
487
|
-
tab.activate();
|
|
488
|
-
|
|
489
|
-
// Update indicator position
|
|
490
|
-
indicatorComponent.updatePosition(tab);
|
|
491
|
-
|
|
492
|
-
// Emit change event
|
|
493
|
-
emit('change', { tab, value: tab.getValue() });
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
// Create the tabs component
|
|
497
|
-
const tabsComponent: TabsComponent = {
|
|
498
|
-
element,
|
|
499
|
-
scrollContainer,
|
|
500
|
-
|
|
501
|
-
addTab: (config: TabConfig) => {
|
|
502
|
-
// Set variant from tabs component
|
|
503
|
-
config.variant = settings.variant;
|
|
504
|
-
|
|
505
|
-
// Create the tab
|
|
506
|
-
const tab = createMockTab(config);
|
|
507
|
-
|
|
508
|
-
// Add tab to container
|
|
509
|
-
tabsContainer.appendChild(tab.element);
|
|
510
|
-
|
|
511
|
-
// Add click event handler
|
|
512
|
-
tab.element.addEventListener('click', (event) => {
|
|
513
|
-
tabsComponent.handleTabClick(event, tab);
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
// Add to tabs array
|
|
517
|
-
tabs.push(tab);
|
|
518
|
-
|
|
519
|
-
// If this is the first tab with active state, position the indicator
|
|
520
|
-
if (tab.isActive()) {
|
|
521
|
-
indicatorComponent.updatePosition(tab);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
return tab;
|
|
525
|
-
},
|
|
526
|
-
|
|
527
|
-
add: (tab: TabComponent) => {
|
|
528
|
-
// Add tab to container
|
|
529
|
-
tabsContainer.appendChild(tab.element);
|
|
530
|
-
|
|
531
|
-
// Add click event handler
|
|
532
|
-
tab.element.addEventListener('click', (event) => {
|
|
533
|
-
tabsComponent.handleTabClick(event, tab);
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
// Add to tabs array
|
|
537
|
-
tabs.push(tab);
|
|
538
|
-
|
|
539
|
-
// If this is an active tab, position the indicator
|
|
540
|
-
if (tab.isActive()) {
|
|
541
|
-
indicatorComponent.updatePosition(tab);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return tabsComponent;
|
|
545
|
-
},
|
|
546
|
-
|
|
547
|
-
getTabs: () => [...tabs],
|
|
548
|
-
|
|
549
|
-
getActiveTab: () => {
|
|
550
|
-
return tabs.find(tab => tab.isActive()) || null;
|
|
551
|
-
},
|
|
552
|
-
|
|
553
|
-
getIndicator: () => indicatorComponent,
|
|
554
|
-
|
|
555
|
-
setActiveTab: (tabOrValue: TabComponent | string) => {
|
|
556
|
-
let targetTab: TabComponent | undefined;
|
|
557
|
-
|
|
558
|
-
if (typeof tabOrValue === 'string') {
|
|
559
|
-
// Find tab by value
|
|
560
|
-
targetTab = tabs.find(tab => tab.getValue() === tabOrValue);
|
|
561
|
-
} else {
|
|
562
|
-
// Tab component provided directly
|
|
563
|
-
targetTab = tabOrValue;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
if (targetTab && !targetTab.disabled?.isDisabled()) {
|
|
567
|
-
// Deactivate all tabs
|
|
568
|
-
tabs.forEach(tab => tab.deactivate());
|
|
569
|
-
|
|
570
|
-
// Activate the target tab
|
|
571
|
-
targetTab.activate();
|
|
572
|
-
|
|
573
|
-
// Update indicator position
|
|
574
|
-
indicatorComponent.updatePosition(targetTab);
|
|
575
|
-
|
|
576
|
-
// Emit change event
|
|
577
|
-
emit('change', { tab: targetTab, value: targetTab.getValue() });
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
return tabsComponent;
|
|
581
|
-
},
|
|
582
|
-
|
|
583
|
-
removeTab: (tabOrValue: TabComponent | string) => {
|
|
584
|
-
let targetTabIndex = -1;
|
|
585
|
-
let targetTab: TabComponent | undefined;
|
|
586
|
-
|
|
587
|
-
if (typeof tabOrValue === 'string') {
|
|
588
|
-
// Find tab by value
|
|
589
|
-
targetTabIndex = tabs.findIndex(tab => tab.getValue() === tabOrValue);
|
|
590
|
-
if (targetTabIndex !== -1) {
|
|
591
|
-
targetTab = tabs[targetTabIndex];
|
|
592
|
-
}
|
|
593
|
-
} else {
|
|
594
|
-
// Tab component provided directly
|
|
595
|
-
targetTabIndex = tabs.indexOf(tabOrValue);
|
|
596
|
-
targetTab = tabOrValue;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
if (targetTabIndex !== -1 && targetTab) {
|
|
600
|
-
// Check if this is the active tab
|
|
601
|
-
const wasActive = targetTab.isActive();
|
|
602
|
-
|
|
603
|
-
// Remove tab from the DOM
|
|
604
|
-
targetTab.destroy();
|
|
605
|
-
|
|
606
|
-
// Remove from tabs array
|
|
607
|
-
tabs.splice(targetTabIndex, 1);
|
|
608
|
-
|
|
609
|
-
// If removed tab was active, activate the first remaining tab
|
|
610
|
-
if (wasActive && tabs.length > 0) {
|
|
611
|
-
tabsComponent.setActiveTab(tabs[0]);
|
|
612
|
-
} else if (tabs.length === 0) {
|
|
613
|
-
// No tabs left, hide the indicator
|
|
614
|
-
indicatorComponent.hide();
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
return tabsComponent;
|
|
619
|
-
},
|
|
620
|
-
|
|
621
|
-
on: (event: string, handler: Function) => {
|
|
622
|
-
if (!eventHandlers[event]) {
|
|
623
|
-
eventHandlers[event] = [];
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
eventHandlers[event].push(handler);
|
|
627
|
-
return tabsComponent;
|
|
628
|
-
},
|
|
629
|
-
|
|
630
|
-
off: (event: string, handler: Function) => {
|
|
631
|
-
if (eventHandlers[event]) {
|
|
632
|
-
eventHandlers[event] = eventHandlers[event].filter(h => h !== handler);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return tabsComponent;
|
|
636
|
-
},
|
|
637
|
-
|
|
638
|
-
emit: (event: string, data: any) => {
|
|
639
|
-
emit(event, data);
|
|
640
|
-
return tabsComponent;
|
|
641
|
-
},
|
|
642
|
-
|
|
643
|
-
destroy: () => {
|
|
644
|
-
// Clean up tabs
|
|
645
|
-
tabs.forEach(tab => tab.destroy());
|
|
646
|
-
tabs.length = 0;
|
|
647
|
-
|
|
648
|
-
// Remove element from DOM if it has a parent
|
|
649
|
-
if (element.parentNode) {
|
|
650
|
-
element.parentNode.removeChild(element);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// Clear event handlers
|
|
654
|
-
for (const event in eventHandlers) {
|
|
655
|
-
eventHandlers[event] = [];
|
|
656
|
-
}
|
|
657
|
-
},
|
|
658
|
-
|
|
659
|
-
handleTabClick
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
// Initialize tabs from config
|
|
663
|
-
if (config.tabs) {
|
|
664
|
-
config.tabs.forEach(tabConfig => {
|
|
665
|
-
// Set variant from tabs component
|
|
666
|
-
tabConfig.variant = settings.variant;
|
|
667
|
-
|
|
668
|
-
tabsComponent.addTab(tabConfig);
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
return tabsComponent;
|
|
673
|
-
};
|
|
674
|
-
|
|
675
|
-
describe('Tab Component', () => {
|
|
676
|
-
test('should create a tab element', () => {
|
|
677
|
-
const tab = createMockTab({
|
|
678
|
-
text: 'Home',
|
|
679
|
-
value: 'home'
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
expect(tab.element).toBeDefined();
|
|
683
|
-
expect(tab.element.tagName).toBe('BUTTON');
|
|
684
|
-
expect(tab.element.className).toContain('mtrl-tab');
|
|
685
|
-
|
|
686
|
-
const textElement = tab.element.querySelector('.mtrl-tab__text');
|
|
687
|
-
expect(textElement).toBeDefined();
|
|
688
|
-
expect(textElement?.textContent).toBe('Home');
|
|
689
|
-
|
|
690
|
-
expect(tab.element.getAttribute('data-value')).toBe('home');
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
test('should apply inactive state by default', () => {
|
|
694
|
-
const tab = createMockTab();
|
|
695
|
-
expect(tab.element.className).toContain('mtrl-tab--inactive');
|
|
696
|
-
expect(tab.isActive()).toBe(false);
|
|
697
|
-
});
|
|
698
|
-
|
|
699
|
-
test('should apply active state when configured', () => {
|
|
700
|
-
const tab = createMockTab({
|
|
701
|
-
state: TAB_STATES.ACTIVE
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
expect(tab.element.className).toContain('mtrl-tab--active');
|
|
705
|
-
expect(tab.isActive()).toBe(true);
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
test('should apply disabled state when configured', () => {
|
|
709
|
-
const tab = createMockTab({
|
|
710
|
-
disabled: true
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
expect(tab.element.disabled).toBe(true);
|
|
714
|
-
expect(tab.element.className).toContain('mtrl-tab--disabled');
|
|
715
|
-
expect(tab.disabled?.isDisabled()).toBe(true);
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
test('should render icon when provided', () => {
|
|
719
|
-
const iconHtml = '<svg>home</svg>';
|
|
720
|
-
const tab = createMockTab({
|
|
721
|
-
icon: iconHtml
|
|
722
|
-
});
|
|
723
|
-
|
|
724
|
-
const iconElement = tab.element.querySelector('.mtrl-tab__icon');
|
|
725
|
-
expect(iconElement).toBeDefined();
|
|
726
|
-
expect(iconElement?.innerHTML).toBe(iconHtml);
|
|
727
|
-
expect(tab.getIcon()).toBe(iconHtml);
|
|
728
|
-
});
|
|
729
|
-
|
|
730
|
-
test('should apply custom icon size', () => {
|
|
731
|
-
const tab = createMockTab({
|
|
732
|
-
icon: '<svg>home</svg>',
|
|
733
|
-
iconSize: '32px'
|
|
734
|
-
});
|
|
735
|
-
|
|
736
|
-
const iconElement = tab.element.querySelector('.mtrl-tab__icon') as HTMLElement;
|
|
737
|
-
expect(iconElement.style.width).toBe('32px');
|
|
738
|
-
expect(iconElement.style.height).toBe('32px');
|
|
739
|
-
});
|
|
740
|
-
|
|
741
|
-
test('should create badge when configured', () => {
|
|
742
|
-
const tab = createMockTab({
|
|
743
|
-
badge: '5'
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
expect(tab.badge).toBeDefined();
|
|
747
|
-
|
|
748
|
-
const badgeElement = tab.element.querySelector('.mtrl-badge');
|
|
749
|
-
expect(badgeElement).toBeDefined();
|
|
750
|
-
expect(badgeElement?.textContent).toBe('5');
|
|
751
|
-
expect(tab.getBadge()).toBe('5');
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
test('should create ripple by default', () => {
|
|
755
|
-
const tab = createMockTab();
|
|
756
|
-
|
|
757
|
-
const ripple = tab.element.querySelector('.mtrl-tab__ripple');
|
|
758
|
-
expect(ripple).toBeDefined();
|
|
759
|
-
});
|
|
760
|
-
|
|
761
|
-
test('should not create ripple when disabled', () => {
|
|
762
|
-
const tab = createMockTab({
|
|
763
|
-
ripple: false
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
const ripple = tab.element.querySelector('.mtrl-tab__ripple');
|
|
767
|
-
expect(ripple).toBeNull();
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
test('should activate and deactivate', () => {
|
|
771
|
-
const tab = createMockTab();
|
|
772
|
-
|
|
773
|
-
expect(tab.isActive()).toBe(false);
|
|
774
|
-
expect(tab.element.className).toContain('mtrl-tab--inactive');
|
|
775
|
-
|
|
776
|
-
tab.activate();
|
|
777
|
-
|
|
778
|
-
expect(tab.isActive()).toBe(true);
|
|
779
|
-
expect(tab.element.className).toContain('mtrl-tab--active');
|
|
780
|
-
expect(tab.element.className).not.toContain('mtrl-tab--inactive');
|
|
781
|
-
|
|
782
|
-
tab.deactivate();
|
|
783
|
-
|
|
784
|
-
expect(tab.isActive()).toBe(false);
|
|
785
|
-
expect(tab.element.className).toContain('mtrl-tab--inactive');
|
|
786
|
-
expect(tab.element.className).not.toContain('mtrl-tab--active');
|
|
787
|
-
});
|
|
788
|
-
|
|
789
|
-
test('should enable and disable', () => {
|
|
790
|
-
const tab = createMockTab({
|
|
791
|
-
disabled: true
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
expect(tab.disabled?.isDisabled()).toBe(true);
|
|
795
|
-
expect(tab.element.disabled).toBe(true);
|
|
796
|
-
|
|
797
|
-
tab.enable();
|
|
798
|
-
|
|
799
|
-
expect(tab.disabled?.isDisabled()).toBe(false);
|
|
800
|
-
expect(tab.element.disabled).toBe(false);
|
|
801
|
-
expect(tab.element.className).not.toContain('mtrl-tab--disabled');
|
|
802
|
-
|
|
803
|
-
tab.disable();
|
|
804
|
-
|
|
805
|
-
expect(tab.disabled?.isDisabled()).toBe(true);
|
|
806
|
-
expect(tab.element.disabled).toBe(true);
|
|
807
|
-
expect(tab.element.className).toContain('mtrl-tab--disabled');
|
|
808
|
-
});
|
|
809
|
-
|
|
810
|
-
test('should update text content', () => {
|
|
811
|
-
const tab = createMockTab({
|
|
812
|
-
text: 'Initial'
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
expect(tab.getText()).toBe('Initial');
|
|
816
|
-
|
|
817
|
-
tab.setText('Updated');
|
|
818
|
-
|
|
819
|
-
expect(tab.getText()).toBe('Updated');
|
|
820
|
-
|
|
821
|
-
const textElement = tab.element.querySelector('.mtrl-tab__text');
|
|
822
|
-
expect(textElement?.textContent).toBe('Updated');
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
test('should update icon', () => {
|
|
826
|
-
const tab = createMockTab();
|
|
827
|
-
|
|
828
|
-
expect(tab.getIcon()).toBe('');
|
|
829
|
-
|
|
830
|
-
const iconHtml = '<svg>home</svg>';
|
|
831
|
-
tab.setIcon(iconHtml);
|
|
832
|
-
|
|
833
|
-
expect(tab.getIcon()).toBe(iconHtml);
|
|
834
|
-
|
|
835
|
-
const iconElement = tab.element.querySelector('.mtrl-tab__icon');
|
|
836
|
-
expect(iconElement).toBeDefined();
|
|
837
|
-
expect(iconElement?.innerHTML).toBe(iconHtml);
|
|
838
|
-
});
|
|
839
|
-
|
|
840
|
-
test('should update badge', () => {
|
|
841
|
-
const tab = createMockTab();
|
|
842
|
-
|
|
843
|
-
expect(tab.getBadge()).toBe('');
|
|
844
|
-
expect(tab.badge).toBeUndefined();
|
|
845
|
-
|
|
846
|
-
tab.setBadge('5');
|
|
847
|
-
|
|
848
|
-
expect(tab.getBadge()).toBe('5');
|
|
849
|
-
expect(tab.badge).toBeDefined();
|
|
850
|
-
|
|
851
|
-
const badgeElement = tab.element.querySelector('.mtrl-badge');
|
|
852
|
-
expect(badgeElement).toBeDefined();
|
|
853
|
-
expect(badgeElement?.textContent).toBe('5');
|
|
854
|
-
|
|
855
|
-
// Update existing badge
|
|
856
|
-
tab.setBadge('10');
|
|
857
|
-
|
|
858
|
-
expect(tab.getBadge()).toBe('10');
|
|
859
|
-
expect(badgeElement?.textContent).toBe('10');
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
test('should show and hide badge', () => {
|
|
863
|
-
const tab = createMockTab({
|
|
864
|
-
badge: '5'
|
|
865
|
-
});
|
|
866
|
-
|
|
867
|
-
const badgeElement = tab.element.querySelector('.mtrl-badge') as HTMLElement;
|
|
868
|
-
expect(badgeElement.style.display).not.toBe('none');
|
|
869
|
-
|
|
870
|
-
tab.hideBadge();
|
|
871
|
-
|
|
872
|
-
expect(badgeElement.style.display).toBe('none');
|
|
873
|
-
|
|
874
|
-
tab.showBadge();
|
|
875
|
-
|
|
876
|
-
expect(badgeElement.style.display).not.toBe('none');
|
|
877
|
-
});
|
|
878
|
-
|
|
879
|
-
test('should get badge component', () => {
|
|
880
|
-
const tab = createMockTab({
|
|
881
|
-
badge: '5'
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
const badgeComponent = tab.getBadgeComponent();
|
|
885
|
-
expect(badgeComponent).toBeDefined();
|
|
886
|
-
expect(badgeComponent).toBe(tab.badge);
|
|
887
|
-
});
|
|
888
|
-
|
|
889
|
-
test('should apply layout styles based on content', () => {
|
|
890
|
-
// Tab with both icon and text
|
|
891
|
-
const tabWithBoth = createMockTab({
|
|
892
|
-
icon: '<svg>home</svg>',
|
|
893
|
-
text: 'Home'
|
|
894
|
-
});
|
|
895
|
-
expect(tabWithBoth.element.className).toContain('mtrl-tab--with-icon-and-text');
|
|
896
|
-
|
|
897
|
-
// Tab with only icon
|
|
898
|
-
const tabWithIcon = createMockTab({
|
|
899
|
-
icon: '<svg>home</svg>'
|
|
900
|
-
});
|
|
901
|
-
expect(tabWithIcon.element.className).toContain('mtrl-tab--icon-only');
|
|
902
|
-
|
|
903
|
-
// Tab with only text
|
|
904
|
-
const tabWithText = createMockTab({
|
|
905
|
-
text: 'Home'
|
|
906
|
-
});
|
|
907
|
-
expect(tabWithText.element.className).toContain('mtrl-tab--text-only');
|
|
908
|
-
});
|
|
909
|
-
|
|
910
|
-
test('should add event listeners', () => {
|
|
911
|
-
const tab = createMockTab();
|
|
912
|
-
let clicked = false;
|
|
913
|
-
|
|
914
|
-
tab.on('click', () => {
|
|
915
|
-
clicked = true;
|
|
916
|
-
});
|
|
917
|
-
|
|
918
|
-
// Simulate click
|
|
919
|
-
tab.element.dispatchEvent(new Event('click'));
|
|
920
|
-
|
|
921
|
-
expect(clicked).toBe(true);
|
|
922
|
-
});
|
|
923
|
-
|
|
924
|
-
test('should remove event listeners', () => {
|
|
925
|
-
const tab = createMockTab();
|
|
926
|
-
let count = 0;
|
|
927
|
-
|
|
928
|
-
const handler = () => {
|
|
929
|
-
count++;
|
|
930
|
-
};
|
|
931
|
-
|
|
932
|
-
tab.on('click', handler);
|
|
933
|
-
|
|
934
|
-
// First click
|
|
935
|
-
tab.element.dispatchEvent(new Event('click'));
|
|
936
|
-
expect(count).toBe(1);
|
|
937
|
-
|
|
938
|
-
// Remove listener
|
|
939
|
-
tab.off('click', handler);
|
|
940
|
-
|
|
941
|
-
// Second click
|
|
942
|
-
tab.element.dispatchEvent(new Event('click'));
|
|
943
|
-
expect(count).toBe(1); // Count should not increase
|
|
944
|
-
});
|
|
945
|
-
|
|
946
|
-
test('should be properly destroyed', () => {
|
|
947
|
-
const tab = createMockTab();
|
|
948
|
-
document.body.appendChild(tab.element);
|
|
949
|
-
|
|
950
|
-
expect(document.body.contains(tab.element)).toBe(true);
|
|
951
|
-
|
|
952
|
-
tab.destroy();
|
|
953
|
-
|
|
954
|
-
expect(document.body.contains(tab.element)).toBe(false);
|
|
955
|
-
});
|
|
956
|
-
});
|
|
957
|
-
|
|
958
|
-
describe('Tabs Component', () => {
|
|
959
|
-
test('should create a tabs container', () => {
|
|
960
|
-
const tabs = createMockTabs();
|
|
961
|
-
|
|
962
|
-
expect(tabs.element).toBeDefined();
|
|
963
|
-
expect(tabs.element.tagName).toBe('DIV');
|
|
964
|
-
expect(tabs.element.className).toContain('mtrl-tabs');
|
|
965
|
-
|
|
966
|
-
const container = tabs.element.querySelector('.mtrl-tabs__container');
|
|
967
|
-
expect(container).toBeDefined();
|
|
968
|
-
|
|
969
|
-
const indicator = tabs.element.querySelector('.mtrl-tabs__indicator');
|
|
970
|
-
expect(indicator).toBeDefined();
|
|
971
|
-
});
|
|
972
|
-
|
|
973
|
-
test('should apply primary variant by default', () => {
|
|
974
|
-
const tabs = createMockTabs();
|
|
975
|
-
expect(tabs.element.className).toContain('mtrl-tabs--primary');
|
|
976
|
-
});
|
|
977
|
-
|
|
978
|
-
test('should apply different variants', () => {
|
|
979
|
-
const variants: TabsVariant[] = [
|
|
980
|
-
TABS_VARIANTS.PRIMARY,
|
|
981
|
-
TABS_VARIANTS.SECONDARY
|
|
982
|
-
];
|
|
983
|
-
|
|
984
|
-
variants.forEach(variant => {
|
|
985
|
-
const tabs = createMockTabs({ variant });
|
|
986
|
-
expect(tabs.element.className).toContain(`mtrl-tabs--${variant}`);
|
|
987
|
-
});
|
|
988
|
-
});
|
|
989
|
-
|
|
990
|
-
test('should create divider by default', () => {
|
|
991
|
-
const tabs = createMockTabs();
|
|
992
|
-
|
|
993
|
-
const divider = tabs.element.querySelector('.mtrl-tabs__divider');
|
|
994
|
-
expect(divider).toBeDefined();
|
|
995
|
-
});
|
|
996
|
-
|
|
997
|
-
test('should not create divider when configured', () => {
|
|
998
|
-
const tabs = createMockTabs({
|
|
999
|
-
showDivider: false
|
|
1000
|
-
});
|
|
1001
|
-
|
|
1002
|
-
const divider = tabs.element.querySelector('.mtrl-tabs__divider');
|
|
1003
|
-
expect(divider).toBeNull();
|
|
1004
|
-
});
|
|
1005
|
-
|
|
1006
|
-
test('should be scrollable by default', () => {
|
|
1007
|
-
const tabs = createMockTabs();
|
|
1008
|
-
|
|
1009
|
-
expect(tabs.element.className).toContain('mtrl-tabs--scrollable');
|
|
1010
|
-
expect(tabs.scrollContainer).toBeDefined();
|
|
1011
|
-
});
|
|
1012
|
-
|
|
1013
|
-
test('should not be scrollable when configured', () => {
|
|
1014
|
-
const tabs = createMockTabs({
|
|
1015
|
-
scrollable: false
|
|
1016
|
-
});
|
|
1017
|
-
|
|
1018
|
-
expect(tabs.element.className).not.toContain('mtrl-tabs--scrollable');
|
|
1019
|
-
expect(tabs.scrollContainer).toBeUndefined();
|
|
1020
|
-
});
|
|
1021
|
-
|
|
1022
|
-
test('should create and add a tab', () => {
|
|
1023
|
-
const tabs = createMockTabs();
|
|
1024
|
-
|
|
1025
|
-
const tab = tabs.addTab({
|
|
1026
|
-
text: 'Home',
|
|
1027
|
-
value: 'home'
|
|
1028
|
-
});
|
|
1029
|
-
|
|
1030
|
-
expect(tab).toBeDefined();
|
|
1031
|
-
expect(tab.element.tagName).toBe('BUTTON');
|
|
1032
|
-
expect(tab.element.className).toContain('mtrl-tab');
|
|
1033
|
-
expect(tab.getText()).toBe('Home');
|
|
1034
|
-
|
|
1035
|
-
// Check that tab was added to the container
|
|
1036
|
-
const container = tabs.element.querySelector('.mtrl-tabs__container');
|
|
1037
|
-
expect(container?.contains(tab.element)).toBe(true);
|
|
1038
|
-
|
|
1039
|
-
// Check that tab was added to the internal tabs array
|
|
1040
|
-
expect(tabs.getTabs().length).toBe(1);
|
|
1041
|
-
expect(tabs.getTabs()[0]).toBe(tab);
|
|
1042
|
-
});
|
|
1043
|
-
|
|
1044
|
-
test('should add a pre-created tab', () => {
|
|
1045
|
-
const tabs = createMockTabs();
|
|
1046
|
-
const tab = createMockTab({
|
|
1047
|
-
text: 'Home',
|
|
1048
|
-
value: 'home'
|
|
1049
|
-
});
|
|
1050
|
-
|
|
1051
|
-
tabs.add(tab);
|
|
1052
|
-
|
|
1053
|
-
// Check that tab was added to the container
|
|
1054
|
-
const container = tabs.element.querySelector('.mtrl-tabs__container');
|
|
1055
|
-
expect(container?.contains(tab.element)).toBe(true);
|
|
1056
|
-
|
|
1057
|
-
// Check that tab was added to the internal tabs array
|
|
1058
|
-
expect(tabs.getTabs().length).toBe(1);
|
|
1059
|
-
expect(tabs.getTabs()[0]).toBe(tab);
|
|
1060
|
-
});
|
|
1061
|
-
|
|
1062
|
-
test('should create tabs from config', () => {
|
|
1063
|
-
const tabs = createMockTabs({
|
|
1064
|
-
tabs: [
|
|
1065
|
-
{ text: 'Home', value: 'home' },
|
|
1066
|
-
{ text: 'About', value: 'about' },
|
|
1067
|
-
{ text: 'Contact', value: 'contact' }
|
|
1068
|
-
]
|
|
1069
|
-
});
|
|
1070
|
-
|
|
1071
|
-
expect(tabs.getTabs().length).toBe(3);
|
|
1072
|
-
|
|
1073
|
-
const tabElements = tabs.element.querySelectorAll('.mtrl-tab');
|
|
1074
|
-
expect(tabElements.length).toBe(3);
|
|
1075
|
-
|
|
1076
|
-
expect(tabs.getTabs()[0].getText()).toBe('Home');
|
|
1077
|
-
expect(tabs.getTabs()[1].getText()).toBe('About');
|
|
1078
|
-
expect(tabs.getTabs()[2].getText()).toBe('Contact');
|
|
1079
|
-
});
|
|
1080
|
-
|
|
1081
|
-
test('should get active tab', () => {
|
|
1082
|
-
const tabs = createMockTabs({
|
|
1083
|
-
tabs: [
|
|
1084
|
-
{ text: 'Home', value: 'home', state: TAB_STATES.ACTIVE },
|
|
1085
|
-
{ text: 'About', value: 'about' },
|
|
1086
|
-
{ text: 'Contact', value: 'contact' }
|
|
1087
|
-
]
|
|
1088
|
-
});
|
|
1089
|
-
|
|
1090
|
-
const activeTab = tabs.getActiveTab();
|
|
1091
|
-
expect(activeTab).toBeDefined();
|
|
1092
|
-
expect(activeTab?.getText()).toBe('Home');
|
|
1093
|
-
expect(activeTab?.getValue()).toBe('home');
|
|
1094
|
-
});
|
|
1095
|
-
|
|
1096
|
-
test('should return null for getActiveTab when no tab is active', () => {
|
|
1097
|
-
const tabs = createMockTabs({
|
|
1098
|
-
tabs: [
|
|
1099
|
-
{ text: 'Home', value: 'home' },
|
|
1100
|
-
{ text: 'About', value: 'about' }
|
|
1101
|
-
]
|
|
1102
|
-
});
|
|
1103
|
-
|
|
1104
|
-
const activeTab = tabs.getActiveTab();
|
|
1105
|
-
expect(activeTab).toBeNull();
|
|
1106
|
-
});
|
|
1107
|
-
|
|
1108
|
-
test('should get indicator component', () => {
|
|
1109
|
-
const tabs = createMockTabs();
|
|
1110
|
-
|
|
1111
|
-
const indicator = tabs.getIndicator?.();
|
|
1112
|
-
expect(indicator).toBeDefined();
|
|
1113
|
-
expect(indicator?.element).toBeDefined();
|
|
1114
|
-
expect(indicator?.element.className).toContain('mtrl-tabs__indicator');
|
|
1115
|
-
});
|
|
1116
|
-
|
|
1117
|
-
test('should set active tab by tab reference', () => {
|
|
1118
|
-
const tabs = createMockTabs({
|
|
1119
|
-
tabs: [
|
|
1120
|
-
{ text: 'Home', value: 'home' },
|
|
1121
|
-
{ text: 'About', value: 'about' },
|
|
1122
|
-
{ text: 'Contact', value: 'contact' }
|
|
1123
|
-
]
|
|
1124
|
-
});
|
|
1125
|
-
|
|
1126
|
-
const tab = tabs.getTabs()[1]; // About tab
|
|
1127
|
-
tabs.setActiveTab(tab);
|
|
1128
|
-
|
|
1129
|
-
// Check active tab
|
|
1130
|
-
const activeTab = tabs.getActiveTab();
|
|
1131
|
-
expect(activeTab).toBe(tab);
|
|
1132
|
-
expect(activeTab?.getText()).toBe('About');
|
|
1133
|
-
|
|
1134
|
-
// Check that only one tab is active
|
|
1135
|
-
const activeTabs = tabs.getTabs().filter(t => t.isActive());
|
|
1136
|
-
expect(activeTabs.length).toBe(1);
|
|
1137
|
-
});
|
|
1138
|
-
|
|
1139
|
-
test('should set active tab by value', () => {
|
|
1140
|
-
const tabs = createMockTabs({
|
|
1141
|
-
tabs: [
|
|
1142
|
-
{ text: 'Home', value: 'home' },
|
|
1143
|
-
{ text: 'About', value: 'about' },
|
|
1144
|
-
{ text: 'Contact', value: 'contact' }
|
|
1145
|
-
]
|
|
1146
|
-
});
|
|
1147
|
-
|
|
1148
|
-
tabs.setActiveTab('contact');
|
|
1149
|
-
|
|
1150
|
-
// Check active tab
|
|
1151
|
-
const activeTab = tabs.getActiveTab();
|
|
1152
|
-
expect(activeTab).toBeDefined();
|
|
1153
|
-
expect(activeTab?.getText()).toBe('Contact');
|
|
1154
|
-
expect(activeTab?.getValue()).toBe('contact');
|
|
1155
|
-
});
|
|
1156
|
-
|
|
1157
|
-
test('should not activate disabled tabs', () => {
|
|
1158
|
-
const tabs = createMockTabs({
|
|
1159
|
-
tabs: [
|
|
1160
|
-
{ text: 'Home', value: 'home' },
|
|
1161
|
-
{ text: 'About', value: 'about', disabled: true }
|
|
1162
|
-
]
|
|
1163
|
-
});
|
|
1164
|
-
|
|
1165
|
-
tabs.setActiveTab('about');
|
|
1166
|
-
|
|
1167
|
-
// Check that no tab is active
|
|
1168
|
-
const activeTab = tabs.getActiveTab();
|
|
1169
|
-
expect(activeTab).toBeNull();
|
|
1170
|
-
});
|
|
1171
|
-
|
|
1172
|
-
test('should emit change event when setting active tab', () => {
|
|
1173
|
-
const tabs = createMockTabs({
|
|
1174
|
-
tabs: [
|
|
1175
|
-
{ text: 'Home', value: 'home' },
|
|
1176
|
-
{ text: 'About', value: 'about' }
|
|
1177
|
-
]
|
|
1178
|
-
});
|
|
1179
|
-
|
|
1180
|
-
let eventFired = false;
|
|
1181
|
-
let eventData: any = null;
|
|
1182
|
-
|
|
1183
|
-
tabs.on('change', (data) => {
|
|
1184
|
-
eventFired = true;
|
|
1185
|
-
eventData = data;
|
|
1186
|
-
});
|
|
1187
|
-
|
|
1188
|
-
tabs.setActiveTab('about');
|
|
1189
|
-
|
|
1190
|
-
expect(eventFired).toBe(true);
|
|
1191
|
-
expect(eventData).toBeDefined();
|
|
1192
|
-
expect(eventData.value).toBe('about');
|
|
1193
|
-
expect(eventData.tab).toBeDefined();
|
|
1194
|
-
expect(eventData.tab.getText()).toBe('About');
|
|
1195
|
-
});
|
|
1196
|
-
|
|
1197
|
-
test('should call change callback from config', () => {
|
|
1198
|
-
let callbackFired = false;
|
|
1199
|
-
let callbackData: any = null;
|
|
1200
|
-
|
|
1201
|
-
const tabs = createMockTabs({
|
|
1202
|
-
tabs: [
|
|
1203
|
-
{ text: 'Home', value: 'home' },
|
|
1204
|
-
{ text: 'About', value: 'about' }
|
|
1205
|
-
],
|
|
1206
|
-
on: {
|
|
1207
|
-
change: (data) => {
|
|
1208
|
-
callbackFired = true;
|
|
1209
|
-
callbackData = data;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
});
|
|
1213
|
-
|
|
1214
|
-
tabs.setActiveTab('about');
|
|
1215
|
-
|
|
1216
|
-
expect(callbackFired).toBe(true);
|
|
1217
|
-
expect(callbackData).toBeDefined();
|
|
1218
|
-
expect(callbackData.value).toBe('about');
|
|
1219
|
-
});
|
|
1220
|
-
|
|
1221
|
-
test('should remove tab by reference', () => {
|
|
1222
|
-
const tabs = createMockTabs({
|
|
1223
|
-
tabs: [
|
|
1224
|
-
{ text: 'Home', value: 'home' },
|
|
1225
|
-
{ text: 'About', value: 'about' },
|
|
1226
|
-
{ text: 'Contact', value: 'contact' }
|
|
1227
|
-
]
|
|
1228
|
-
});
|
|
1229
|
-
|
|
1230
|
-
expect(tabs.getTabs().length).toBe(3);
|
|
1231
|
-
|
|
1232
|
-
const tab = tabs.getTabs()[1]; // About tab
|
|
1233
|
-
tabs.removeTab(tab);
|
|
1234
|
-
|
|
1235
|
-
expect(tabs.getTabs().length).toBe(2);
|
|
1236
|
-
expect(tabs.getTabs()[0].getText()).toBe('Home');
|
|
1237
|
-
expect(tabs.getTabs()[1].getText()).toBe('Contact');
|
|
1238
|
-
|
|
1239
|
-
// The tab element should be removed from the DOM
|
|
1240
|
-
const container = tabs.element.querySelector('.mtrl-tabs__container');
|
|
1241
|
-
expect(container?.contains(tab.element)).toBe(false);
|
|
1242
|
-
});
|
|
1243
|
-
|
|
1244
|
-
test('should remove tab by value', () => {
|
|
1245
|
-
const tabs = createMockTabs({
|
|
1246
|
-
tabs: [
|
|
1247
|
-
{ text: 'Home', value: 'home' },
|
|
1248
|
-
{ text: 'About', value: 'about' },
|
|
1249
|
-
{ text: 'Contact', value: 'contact' }
|
|
1250
|
-
]
|
|
1251
|
-
});
|
|
1252
|
-
|
|
1253
|
-
expect(tabs.getTabs().length).toBe(3);
|
|
1254
|
-
|
|
1255
|
-
tabs.removeTab('about');
|
|
1256
|
-
|
|
1257
|
-
expect(tabs.getTabs().length).toBe(2);
|
|
1258
|
-
expect(tabs.getTabs()[0].getText()).toBe('Home');
|
|
1259
|
-
expect(tabs.getTabs()[1].getText()).toBe('Contact');
|
|
1260
|
-
});
|
|
1261
|
-
|
|
1262
|
-
test('should activate another tab when removing the active tab', () => {
|
|
1263
|
-
const tabs = createMockTabs({
|
|
1264
|
-
tabs: [
|
|
1265
|
-
{ text: 'Home', value: 'home', state: TAB_STATES.ACTIVE },
|
|
1266
|
-
{ text: 'About', value: 'about' },
|
|
1267
|
-
{ text: 'Contact', value: 'contact' }
|
|
1268
|
-
]
|
|
1269
|
-
});
|
|
1270
|
-
|
|
1271
|
-
// Remove the active tab
|
|
1272
|
-
tabs.removeTab('home');
|
|
1273
|
-
|
|
1274
|
-
// First remaining tab should become active
|
|
1275
|
-
const activeTab = tabs.getActiveTab();
|
|
1276
|
-
expect(activeTab).toBeDefined();
|
|
1277
|
-
expect(activeTab?.getText()).toBe('About');
|
|
1278
|
-
});
|
|
1279
|
-
|
|
1280
|
-
test('should handle tab click', () => {
|
|
1281
|
-
const tabs = createMockTabs({
|
|
1282
|
-
tabs: [
|
|
1283
|
-
{ text: 'Home', value: 'home' },
|
|
1284
|
-
{ text: 'About', value: 'about' }
|
|
1285
|
-
]
|
|
1286
|
-
});
|
|
1287
|
-
|
|
1288
|
-
let eventFired = false;
|
|
1289
|
-
|
|
1290
|
-
tabs.on('change', () => {
|
|
1291
|
-
eventFired = true;
|
|
1292
|
-
});
|
|
1293
|
-
|
|
1294
|
-
// Simulate click on the first tab
|
|
1295
|
-
const firstTab = tabs.getTabs()[0];
|
|
1296
|
-
firstTab.element.dispatchEvent(new Event('click'));
|
|
1297
|
-
|
|
1298
|
-
expect(eventFired).toBe(true);
|
|
1299
|
-
expect(firstTab.isActive()).toBe(true);
|
|
1300
|
-
});
|
|
1301
|
-
|
|
1302
|
-
test('should not respond to click on disabled tab', () => {
|
|
1303
|
-
const tabs = createMockTabs({
|
|
1304
|
-
tabs: [
|
|
1305
|
-
{ text: 'Home', value: 'home' },
|
|
1306
|
-
{ text: 'About', value: 'about', disabled: true }
|
|
1307
|
-
]
|
|
1308
|
-
});
|
|
1309
|
-
|
|
1310
|
-
let eventFired = false;
|
|
1311
|
-
|
|
1312
|
-
tabs.on('change', () => {
|
|
1313
|
-
eventFired = true;
|
|
1314
|
-
});
|
|
1315
|
-
|
|
1316
|
-
// Set first tab as active
|
|
1317
|
-
tabs.setActiveTab('home');
|
|
1318
|
-
eventFired = false; // Reset flag
|
|
1319
|
-
|
|
1320
|
-
// Simulate click on disabled tab
|
|
1321
|
-
const disabledTab = tabs.getTabs()[1];
|
|
1322
|
-
disabledTab.element.dispatchEvent(new Event('click'));
|
|
1323
|
-
|
|
1324
|
-
expect(eventFired).toBe(false);
|
|
1325
|
-
expect(disabledTab.isActive()).toBe(false);
|
|
1326
|
-
expect(tabs.getActiveTab()?.getValue()).toBe('home'); // Still active
|
|
1327
|
-
});
|
|
1328
|
-
|
|
1329
|
-
test('should add and remove event listeners', () => {
|
|
1330
|
-
const tabs = createMockTabs();
|
|
1331
|
-
let eventCount = 0;
|
|
1332
|
-
|
|
1333
|
-
const handler = () => {
|
|
1334
|
-
eventCount++;
|
|
1335
|
-
};
|
|
1336
|
-
|
|
1337
|
-
tabs.on('change', handler);
|
|
1338
|
-
|
|
1339
|
-
// Trigger event
|
|
1340
|
-
tabs.emit?.('change', {});
|
|
1341
|
-
expect(eventCount).toBe(1);
|
|
1342
|
-
|
|
1343
|
-
// Remove listener
|
|
1344
|
-
tabs.off('change', handler);
|
|
1345
|
-
|
|
1346
|
-
// Trigger event again
|
|
1347
|
-
tabs.emit?.('change', {});
|
|
1348
|
-
expect(eventCount).toBe(1); // Count should not increase
|
|
1349
|
-
});
|
|
1350
|
-
|
|
1351
|
-
test('should be properly destroyed', () => {
|
|
1352
|
-
const tabs = createMockTabs({
|
|
1353
|
-
tabs: [
|
|
1354
|
-
{ text: 'Home', value: 'home' },
|
|
1355
|
-
{ text: 'About', value: 'about' }
|
|
1356
|
-
]
|
|
1357
|
-
});
|
|
1358
|
-
|
|
1359
|
-
document.body.appendChild(tabs.element);
|
|
1360
|
-
|
|
1361
|
-
expect(document.body.contains(tabs.element)).toBe(true);
|
|
1362
|
-
expect(tabs.getTabs().length).toBe(2);
|
|
1363
|
-
|
|
1364
|
-
tabs.destroy();
|
|
1365
|
-
|
|
1366
|
-
expect(document.body.contains(tabs.element)).toBe(false);
|
|
1367
|
-
expect(tabs.getTabs().length).toBe(0);
|
|
1368
|
-
});
|
|
1369
|
-
});
|