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,732 +0,0 @@
|
|
|
1
|
-
// test/components/segmented-button.test.ts
|
|
2
|
-
import { describe, test, expect } from 'bun:test';
|
|
3
|
-
import {
|
|
4
|
-
type SegmentedButtonComponent,
|
|
5
|
-
type SegmentedButtonConfig,
|
|
6
|
-
type SegmentConfig,
|
|
7
|
-
type Segment,
|
|
8
|
-
SelectionMode
|
|
9
|
-
} from '../../src/components/segmented-button/types';
|
|
10
|
-
|
|
11
|
-
// Mock segmented-button implementation
|
|
12
|
-
const createMockSegmentedButton = (config: SegmentedButtonConfig = {}): SegmentedButtonComponent => {
|
|
13
|
-
// Create main container element
|
|
14
|
-
const element = document.createElement('div');
|
|
15
|
-
element.className = 'mtrl-segmented-button';
|
|
16
|
-
|
|
17
|
-
// Default settings
|
|
18
|
-
const settings = {
|
|
19
|
-
mode: config.mode || SelectionMode.SINGLE,
|
|
20
|
-
disabled: config.disabled || false,
|
|
21
|
-
ripple: config.ripple !== undefined ? config.ripple : true,
|
|
22
|
-
prefix: config.prefix || 'mtrl',
|
|
23
|
-
componentName: config.componentName || 'segmented-button'
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// Apply mode class
|
|
27
|
-
element.setAttribute('data-mode', settings.mode);
|
|
28
|
-
element.classList.add(`mtrl-segmented-button--${settings.mode}`);
|
|
29
|
-
|
|
30
|
-
// Apply disabled state
|
|
31
|
-
if (settings.disabled) {
|
|
32
|
-
element.classList.add('mtrl-segmented-button--disabled');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Apply additional classes
|
|
36
|
-
if (config.class) {
|
|
37
|
-
const classes = config.class.split(' ');
|
|
38
|
-
classes.forEach(className => element.classList.add(className));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Array to store segments
|
|
42
|
-
const segments: Segment[] = [];
|
|
43
|
-
|
|
44
|
-
// Track event handlers
|
|
45
|
-
const eventHandlers: Record<string, Function[]> = {};
|
|
46
|
-
|
|
47
|
-
// Emit an event
|
|
48
|
-
const emit = (event: string, originalEvent?: Event | null) => {
|
|
49
|
-
const selected = segmentedButton.getSelected();
|
|
50
|
-
const values = segmentedButton.getValue();
|
|
51
|
-
|
|
52
|
-
let defaultPrevented = false;
|
|
53
|
-
|
|
54
|
-
const eventData = {
|
|
55
|
-
segmentedButton,
|
|
56
|
-
selected,
|
|
57
|
-
values,
|
|
58
|
-
originalEvent: originalEvent || null,
|
|
59
|
-
preventDefault: () => {
|
|
60
|
-
defaultPrevented = true;
|
|
61
|
-
},
|
|
62
|
-
defaultPrevented
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// Call handlers from config.on
|
|
66
|
-
if (config.on && config.on[event]) {
|
|
67
|
-
config.on[event]!(eventData);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Call registered event handlers
|
|
71
|
-
if (eventHandlers[event]) {
|
|
72
|
-
eventHandlers[event].forEach(handler => handler(eventData));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return defaultPrevented;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// Create segments from config
|
|
79
|
-
if (config.segments) {
|
|
80
|
-
config.segments.forEach(segmentConfig => {
|
|
81
|
-
const segmentElement = document.createElement('button');
|
|
82
|
-
segmentElement.className = 'mtrl-segment';
|
|
83
|
-
segmentElement.type = 'button';
|
|
84
|
-
|
|
85
|
-
// Set value attribute
|
|
86
|
-
const value = segmentConfig.value || segmentConfig.text || '';
|
|
87
|
-
segmentElement.setAttribute('data-value', value);
|
|
88
|
-
|
|
89
|
-
// Apply selected state
|
|
90
|
-
if (segmentConfig.selected) {
|
|
91
|
-
segmentElement.classList.add('mtrl-segment--selected');
|
|
92
|
-
segmentElement.setAttribute('aria-pressed', 'true');
|
|
93
|
-
} else {
|
|
94
|
-
segmentElement.setAttribute('aria-pressed', 'false');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Apply disabled state
|
|
98
|
-
if (settings.disabled || segmentConfig.disabled) {
|
|
99
|
-
segmentElement.classList.add('mtrl-segment--disabled');
|
|
100
|
-
segmentElement.disabled = true;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Create icon if provided
|
|
104
|
-
if (segmentConfig.icon) {
|
|
105
|
-
const iconElement = document.createElement('span');
|
|
106
|
-
iconElement.className = 'mtrl-segment__icon';
|
|
107
|
-
iconElement.innerHTML = segmentConfig.icon;
|
|
108
|
-
segmentElement.appendChild(iconElement);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Create text if provided
|
|
112
|
-
if (segmentConfig.text) {
|
|
113
|
-
const textElement = document.createElement('span');
|
|
114
|
-
textElement.className = 'mtrl-segment__text';
|
|
115
|
-
textElement.textContent = segmentConfig.text;
|
|
116
|
-
segmentElement.appendChild(textElement);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Apply additional classes
|
|
120
|
-
if (segmentConfig.class) {
|
|
121
|
-
const classes = segmentConfig.class.split(' ');
|
|
122
|
-
classes.forEach(className => segmentElement.classList.add(className));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Add ripple effect if enabled
|
|
126
|
-
if (settings.ripple) {
|
|
127
|
-
const rippleElement = document.createElement('span');
|
|
128
|
-
rippleElement.className = 'mtrl-segment__ripple';
|
|
129
|
-
segmentElement.appendChild(rippleElement);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Create segment object
|
|
133
|
-
const segment: Segment = {
|
|
134
|
-
element: segmentElement,
|
|
135
|
-
value,
|
|
136
|
-
|
|
137
|
-
isSelected: () => segmentElement.classList.contains('mtrl-segment--selected'),
|
|
138
|
-
|
|
139
|
-
setSelected: (selected: boolean) => {
|
|
140
|
-
if (selected) {
|
|
141
|
-
segmentElement.classList.add('mtrl-segment--selected');
|
|
142
|
-
segmentElement.setAttribute('aria-pressed', 'true');
|
|
143
|
-
} else {
|
|
144
|
-
segmentElement.classList.remove('mtrl-segment--selected');
|
|
145
|
-
segmentElement.setAttribute('aria-pressed', 'false');
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
isDisabled: () => segmentElement.disabled,
|
|
150
|
-
|
|
151
|
-
setDisabled: (disabled: boolean) => {
|
|
152
|
-
segmentElement.disabled = disabled;
|
|
153
|
-
|
|
154
|
-
if (disabled) {
|
|
155
|
-
segmentElement.classList.add('mtrl-segment--disabled');
|
|
156
|
-
} else {
|
|
157
|
-
segmentElement.classList.remove('mtrl-segment--disabled');
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
destroy: () => {
|
|
162
|
-
if (segmentElement.parentNode) {
|
|
163
|
-
segmentElement.parentNode.removeChild(segmentElement);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
// Handle click event
|
|
169
|
-
segmentElement.addEventListener('click', (event) => {
|
|
170
|
-
if (!segment.isDisabled()) {
|
|
171
|
-
if (settings.mode === SelectionMode.SINGLE) {
|
|
172
|
-
// Deselect all segments
|
|
173
|
-
segments.forEach(s => s.setSelected(false));
|
|
174
|
-
// Select only this segment
|
|
175
|
-
segment.setSelected(true);
|
|
176
|
-
} else {
|
|
177
|
-
// Toggle this segment
|
|
178
|
-
segment.setSelected(!segment.isSelected());
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Emit change event
|
|
182
|
-
emit('change', event);
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
segments.push(segment);
|
|
187
|
-
element.appendChild(segmentElement);
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Create the segmented button component
|
|
192
|
-
const segmentedButton: SegmentedButtonComponent = {
|
|
193
|
-
element,
|
|
194
|
-
segments,
|
|
195
|
-
|
|
196
|
-
getSelected: () => segments.filter(segment => segment.isSelected()),
|
|
197
|
-
|
|
198
|
-
getValue: () => segments.filter(segment => segment.isSelected()).map(segment => segment.value),
|
|
199
|
-
|
|
200
|
-
select: (value: string) => {
|
|
201
|
-
const segment = segments.find(s => s.value === value);
|
|
202
|
-
|
|
203
|
-
if (segment && !segment.isDisabled()) {
|
|
204
|
-
if (settings.mode === SelectionMode.SINGLE) {
|
|
205
|
-
// Deselect all segments
|
|
206
|
-
segments.forEach(s => s.setSelected(false));
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Select the specified segment
|
|
210
|
-
segment.setSelected(true);
|
|
211
|
-
|
|
212
|
-
// Emit change event
|
|
213
|
-
emit('change');
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return segmentedButton;
|
|
217
|
-
},
|
|
218
|
-
|
|
219
|
-
deselect: (value: string) => {
|
|
220
|
-
const segment = segments.find(s => s.value === value);
|
|
221
|
-
|
|
222
|
-
if (segment && !segment.isDisabled() && segment.isSelected()) {
|
|
223
|
-
segment.setSelected(false);
|
|
224
|
-
|
|
225
|
-
// Emit change event
|
|
226
|
-
emit('change');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return segmentedButton;
|
|
230
|
-
},
|
|
231
|
-
|
|
232
|
-
enable: () => {
|
|
233
|
-
settings.disabled = false;
|
|
234
|
-
element.classList.remove('mtrl-segmented-button--disabled');
|
|
235
|
-
|
|
236
|
-
// Enable individual segments that weren't explicitly disabled
|
|
237
|
-
segments.forEach(segment => {
|
|
238
|
-
const configSegment = config.segments?.find(s => s.value === segment.value);
|
|
239
|
-
if (!configSegment?.disabled) {
|
|
240
|
-
segment.setDisabled(false);
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
return segmentedButton;
|
|
245
|
-
},
|
|
246
|
-
|
|
247
|
-
disable: () => {
|
|
248
|
-
settings.disabled = true;
|
|
249
|
-
element.classList.add('mtrl-segmented-button--disabled');
|
|
250
|
-
|
|
251
|
-
// Disable all segments
|
|
252
|
-
segments.forEach(segment => {
|
|
253
|
-
segment.setDisabled(true);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
return segmentedButton;
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
on: (event: string, handler: Function) => {
|
|
260
|
-
if (!eventHandlers[event]) {
|
|
261
|
-
eventHandlers[event] = [];
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
eventHandlers[event].push(handler);
|
|
265
|
-
return segmentedButton;
|
|
266
|
-
},
|
|
267
|
-
|
|
268
|
-
off: (event: string, handler: Function) => {
|
|
269
|
-
if (eventHandlers[event]) {
|
|
270
|
-
eventHandlers[event] = eventHandlers[event].filter(h => h !== handler);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return segmentedButton;
|
|
274
|
-
},
|
|
275
|
-
|
|
276
|
-
destroy: () => {
|
|
277
|
-
// Clean up segments
|
|
278
|
-
segments.forEach(segment => segment.destroy());
|
|
279
|
-
|
|
280
|
-
// Remove element from DOM if it has a parent
|
|
281
|
-
if (element.parentNode) {
|
|
282
|
-
element.parentNode.removeChild(element);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Clear event handlers
|
|
286
|
-
for (const event in eventHandlers) {
|
|
287
|
-
eventHandlers[event] = [];
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
return segmentedButton;
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
describe('Segmented Button Component', () => {
|
|
296
|
-
test('should create a segmented button component', () => {
|
|
297
|
-
const segmentedButton = createMockSegmentedButton({
|
|
298
|
-
segments: [
|
|
299
|
-
{ text: 'Day', value: 'day' },
|
|
300
|
-
{ text: 'Week', value: 'week' },
|
|
301
|
-
{ text: 'Month', value: 'month' }
|
|
302
|
-
]
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
expect(segmentedButton.element).toBeDefined();
|
|
306
|
-
expect(segmentedButton.element.tagName).toBe('DIV');
|
|
307
|
-
expect(segmentedButton.element.className).toContain('mtrl-segmented-button');
|
|
308
|
-
|
|
309
|
-
const segmentElements = segmentedButton.element.querySelectorAll('.mtrl-segment');
|
|
310
|
-
expect(segmentElements.length).toBe(3);
|
|
311
|
-
|
|
312
|
-
const textElements = segmentedButton.element.querySelectorAll('.mtrl-segment__text');
|
|
313
|
-
expect(textElements.length).toBe(3);
|
|
314
|
-
expect(textElements[0].textContent).toBe('Day');
|
|
315
|
-
expect(textElements[1].textContent).toBe('Week');
|
|
316
|
-
expect(textElements[2].textContent).toBe('Month');
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
test('should apply single selection mode by default', () => {
|
|
320
|
-
const segmentedButton = createMockSegmentedButton();
|
|
321
|
-
|
|
322
|
-
expect(segmentedButton.element.getAttribute('data-mode')).toBe(SelectionMode.SINGLE);
|
|
323
|
-
expect(segmentedButton.element.className).toContain(`mtrl-segmented-button--${SelectionMode.SINGLE}`);
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test('should apply multi selection mode when specified', () => {
|
|
327
|
-
const segmentedButton = createMockSegmentedButton({
|
|
328
|
-
mode: SelectionMode.MULTI
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
expect(segmentedButton.element.getAttribute('data-mode')).toBe(SelectionMode.MULTI);
|
|
332
|
-
expect(segmentedButton.element.className).toContain(`mtrl-segmented-button--${SelectionMode.MULTI}`);
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
test('should apply disabled state', () => {
|
|
336
|
-
const segmentedButton = createMockSegmentedButton({
|
|
337
|
-
disabled: true,
|
|
338
|
-
segments: [
|
|
339
|
-
{ text: 'Option 1', value: 'option1' },
|
|
340
|
-
{ text: 'Option 2', value: 'option2' }
|
|
341
|
-
]
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
expect(segmentedButton.element.className).toContain('mtrl-segmented-button--disabled');
|
|
345
|
-
|
|
346
|
-
const segmentElements = segmentedButton.element.querySelectorAll('.mtrl-segment');
|
|
347
|
-
segmentElements.forEach(segment => {
|
|
348
|
-
expect(segment.className).toContain('mtrl-segment--disabled');
|
|
349
|
-
expect((segment as HTMLButtonElement).disabled).toBe(true);
|
|
350
|
-
});
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
test('should disable individual segments', () => {
|
|
354
|
-
const segmentedButton = createMockSegmentedButton({
|
|
355
|
-
segments: [
|
|
356
|
-
{ text: 'Option 1', value: 'option1' },
|
|
357
|
-
{ text: 'Option 2', value: 'option2', disabled: true },
|
|
358
|
-
{ text: 'Option 3', value: 'option3' }
|
|
359
|
-
]
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
const segmentElements = segmentedButton.element.querySelectorAll('.mtrl-segment');
|
|
363
|
-
|
|
364
|
-
expect(segmentElements[0].className).not.toContain('mtrl-segment--disabled');
|
|
365
|
-
expect(segmentElements[1].className).toContain('mtrl-segment--disabled');
|
|
366
|
-
expect(segmentElements[2].className).not.toContain('mtrl-segment--disabled');
|
|
367
|
-
|
|
368
|
-
expect((segmentElements[0] as HTMLButtonElement).disabled).toBe(false);
|
|
369
|
-
expect((segmentElements[1] as HTMLButtonElement).disabled).toBe(true);
|
|
370
|
-
expect((segmentElements[2] as HTMLButtonElement).disabled).toBe(false);
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
test('should create ripple elements by default', () => {
|
|
374
|
-
const segmentedButton = createMockSegmentedButton({
|
|
375
|
-
segments: [
|
|
376
|
-
{ text: 'Option 1', value: 'option1' }
|
|
377
|
-
]
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
const ripples = segmentedButton.element.querySelectorAll('.mtrl-segment__ripple');
|
|
381
|
-
expect(ripples.length).toBe(1);
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
test('should not create ripple elements when disabled', () => {
|
|
385
|
-
const segmentedButton = createMockSegmentedButton({
|
|
386
|
-
ripple: false,
|
|
387
|
-
segments: [
|
|
388
|
-
{ text: 'Option 1', value: 'option1' }
|
|
389
|
-
]
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
const ripples = segmentedButton.element.querySelectorAll('.mtrl-segment__ripple');
|
|
393
|
-
expect(ripples.length).toBe(0);
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
test('should render icons when provided', () => {
|
|
397
|
-
const segmentedButton = createMockSegmentedButton({
|
|
398
|
-
segments: [
|
|
399
|
-
{ text: 'Day', value: 'day', icon: '<svg>calendar_day</svg>' },
|
|
400
|
-
{ text: 'Week', value: 'week', icon: '<svg>calendar_week</svg>' }
|
|
401
|
-
]
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
const icons = segmentedButton.element.querySelectorAll('.mtrl-segment__icon');
|
|
405
|
-
expect(icons.length).toBe(2);
|
|
406
|
-
expect(icons[0].innerHTML).toBe('<svg>calendar_day</svg>');
|
|
407
|
-
expect(icons[1].innerHTML).toBe('<svg>calendar_week</svg>');
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
test('should select initial segments', () => {
|
|
411
|
-
const segmentedButton = createMockSegmentedButton({
|
|
412
|
-
segments: [
|
|
413
|
-
{ text: 'Option 1', value: 'option1' },
|
|
414
|
-
{ text: 'Option 2', value: 'option2', selected: true },
|
|
415
|
-
{ text: 'Option 3', value: 'option3' }
|
|
416
|
-
]
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
const segmentElements = segmentedButton.element.querySelectorAll('.mtrl-segment');
|
|
420
|
-
|
|
421
|
-
expect(segmentElements[0].className).not.toContain('mtrl-segment--selected');
|
|
422
|
-
expect(segmentElements[1].className).toContain('mtrl-segment--selected');
|
|
423
|
-
expect(segmentElements[2].className).not.toContain('mtrl-segment--selected');
|
|
424
|
-
|
|
425
|
-
expect(segmentElements[0].getAttribute('aria-pressed')).toBe('false');
|
|
426
|
-
expect(segmentElements[1].getAttribute('aria-pressed')).toBe('true');
|
|
427
|
-
expect(segmentElements[2].getAttribute('aria-pressed')).toBe('false');
|
|
428
|
-
|
|
429
|
-
const selected = segmentedButton.getSelected();
|
|
430
|
-
expect(selected.length).toBe(1);
|
|
431
|
-
expect(selected[0].value).toBe('option2');
|
|
432
|
-
|
|
433
|
-
const values = segmentedButton.getValue();
|
|
434
|
-
expect(values).toEqual(['option2']);
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
test('should select multiple initial segments in multi mode', () => {
|
|
438
|
-
const segmentedButton = createMockSegmentedButton({
|
|
439
|
-
mode: SelectionMode.MULTI,
|
|
440
|
-
segments: [
|
|
441
|
-
{ text: 'Option 1', value: 'option1', selected: true },
|
|
442
|
-
{ text: 'Option 2', value: 'option2', selected: true },
|
|
443
|
-
{ text: 'Option 3', value: 'option3' }
|
|
444
|
-
]
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
const segmentElements = segmentedButton.element.querySelectorAll('.mtrl-segment');
|
|
448
|
-
|
|
449
|
-
expect(segmentElements[0].className).toContain('mtrl-segment--selected');
|
|
450
|
-
expect(segmentElements[1].className).toContain('mtrl-segment--selected');
|
|
451
|
-
expect(segmentElements[2].className).not.toContain('mtrl-segment--selected');
|
|
452
|
-
|
|
453
|
-
const selected = segmentedButton.getSelected();
|
|
454
|
-
expect(selected.length).toBe(2);
|
|
455
|
-
expect(selected[0].value).toBe('option1');
|
|
456
|
-
expect(selected[1].value).toBe('option2');
|
|
457
|
-
|
|
458
|
-
const values = segmentedButton.getValue();
|
|
459
|
-
expect(values).toEqual(['option1', 'option2']);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
test('should select a segment programmatically', () => {
|
|
463
|
-
const segmentedButton = createMockSegmentedButton({
|
|
464
|
-
segments: [
|
|
465
|
-
{ text: 'Option 1', value: 'option1' },
|
|
466
|
-
{ text: 'Option 2', value: 'option2' },
|
|
467
|
-
{ text: 'Option 3', value: 'option3' }
|
|
468
|
-
]
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
expect(segmentedButton.getValue().length).toBe(0);
|
|
472
|
-
|
|
473
|
-
segmentedButton.select('option2');
|
|
474
|
-
|
|
475
|
-
const segmentElements = segmentedButton.element.querySelectorAll('.mtrl-segment');
|
|
476
|
-
expect(segmentElements[1].className).toContain('mtrl-segment--selected');
|
|
477
|
-
|
|
478
|
-
const values = segmentedButton.getValue();
|
|
479
|
-
expect(values).toEqual(['option2']);
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
test('should deselect other segments in single mode when selecting one', () => {
|
|
483
|
-
const segmentedButton = createMockSegmentedButton({
|
|
484
|
-
segments: [
|
|
485
|
-
{ text: 'Option 1', value: 'option1', selected: true },
|
|
486
|
-
{ text: 'Option 2', value: 'option2' },
|
|
487
|
-
{ text: 'Option 3', value: 'option3' }
|
|
488
|
-
]
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
expect(segmentedButton.getValue()).toEqual(['option1']);
|
|
492
|
-
|
|
493
|
-
segmentedButton.select('option2');
|
|
494
|
-
|
|
495
|
-
const segmentElements = segmentedButton.element.querySelectorAll('.mtrl-segment');
|
|
496
|
-
expect(segmentElements[0].className).not.toContain('mtrl-segment--selected');
|
|
497
|
-
expect(segmentElements[1].className).toContain('mtrl-segment--selected');
|
|
498
|
-
|
|
499
|
-
const values = segmentedButton.getValue();
|
|
500
|
-
expect(values).toEqual(['option2']);
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
test('should allow multiple selections in multi mode', () => {
|
|
504
|
-
const segmentedButton = createMockSegmentedButton({
|
|
505
|
-
mode: SelectionMode.MULTI,
|
|
506
|
-
segments: [
|
|
507
|
-
{ text: 'Option 1', value: 'option1', selected: true },
|
|
508
|
-
{ text: 'Option 2', value: 'option2' },
|
|
509
|
-
{ text: 'Option 3', value: 'option3' }
|
|
510
|
-
]
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
expect(segmentedButton.getValue()).toEqual(['option1']);
|
|
514
|
-
|
|
515
|
-
segmentedButton.select('option2');
|
|
516
|
-
|
|
517
|
-
const segmentElements = segmentedButton.element.querySelectorAll('.mtrl-segment');
|
|
518
|
-
expect(segmentElements[0].className).toContain('mtrl-segment--selected');
|
|
519
|
-
expect(segmentElements[1].className).toContain('mtrl-segment--selected');
|
|
520
|
-
|
|
521
|
-
const values = segmentedButton.getValue();
|
|
522
|
-
expect(values).toEqual(['option1', 'option2']);
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
test('should deselect a segment programmatically', () => {
|
|
526
|
-
const segmentedButton = createMockSegmentedButton({
|
|
527
|
-
segments: [
|
|
528
|
-
{ text: 'Option 1', value: 'option1', selected: true },
|
|
529
|
-
{ text: 'Option 2', value: 'option2' }
|
|
530
|
-
]
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
expect(segmentedButton.getValue()).toEqual(['option1']);
|
|
534
|
-
|
|
535
|
-
segmentedButton.deselect('option1');
|
|
536
|
-
|
|
537
|
-
const values = segmentedButton.getValue();
|
|
538
|
-
expect(values).toEqual([]);
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
test('should not allow deselecting a segment in single mode if it\'s the only one selected', () => {
|
|
542
|
-
const segmentedButton = createMockSegmentedButton({
|
|
543
|
-
segments: [
|
|
544
|
-
{ text: 'Option 1', value: 'option1', selected: true },
|
|
545
|
-
{ text: 'Option 2', value: 'option2' }
|
|
546
|
-
]
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
// Attempt to deselect the only selected segment
|
|
550
|
-
segmentedButton.deselect('option1');
|
|
551
|
-
|
|
552
|
-
// It should remain selected (this is implementation-specific behavior)
|
|
553
|
-
const values = segmentedButton.getValue();
|
|
554
|
-
expect(values).toEqual([]);
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
test('should emit change events when segments are selected', () => {
|
|
558
|
-
const segmentedButton = createMockSegmentedButton({
|
|
559
|
-
segments: [
|
|
560
|
-
{ text: 'Option 1', value: 'option1' },
|
|
561
|
-
{ text: 'Option 2', value: 'option2' }
|
|
562
|
-
]
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
let changeEventFired = false;
|
|
566
|
-
let eventValues: string[] = [];
|
|
567
|
-
|
|
568
|
-
segmentedButton.on('change', (event) => {
|
|
569
|
-
changeEventFired = true;
|
|
570
|
-
eventValues = event.values;
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
segmentedButton.select('option1');
|
|
574
|
-
|
|
575
|
-
expect(changeEventFired).toBe(true);
|
|
576
|
-
expect(eventValues).toEqual(['option1']);
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
test('should handle click events on segments', () => {
|
|
580
|
-
const segmentedButton = createMockSegmentedButton({
|
|
581
|
-
segments: [
|
|
582
|
-
{ text: 'Option 1', value: 'option1' },
|
|
583
|
-
{ text: 'Option 2', value: 'option2' }
|
|
584
|
-
]
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
let changeEventFired = false;
|
|
588
|
-
|
|
589
|
-
segmentedButton.on('change', () => {
|
|
590
|
-
changeEventFired = true;
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
// Simulate click on first segment
|
|
594
|
-
const firstSegment = segmentedButton.element.querySelectorAll('.mtrl-segment')[0];
|
|
595
|
-
firstSegment.dispatchEvent(new Event('click'));
|
|
596
|
-
|
|
597
|
-
expect(changeEventFired).toBe(true);
|
|
598
|
-
expect(segmentedButton.getValue()).toEqual(['option1']);
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
test('should not select disabled segments', () => {
|
|
602
|
-
const segmentedButton = createMockSegmentedButton({
|
|
603
|
-
segments: [
|
|
604
|
-
{ text: 'Option 1', value: 'option1' },
|
|
605
|
-
{ text: 'Option 2', value: 'option2', disabled: true }
|
|
606
|
-
]
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
segmentedButton.select('option2');
|
|
610
|
-
|
|
611
|
-
// The disabled segment should not be selected
|
|
612
|
-
expect(segmentedButton.getValue()).toEqual([]);
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
test('should toggle segments in multi mode on click', () => {
|
|
616
|
-
const segmentedButton = createMockSegmentedButton({
|
|
617
|
-
mode: SelectionMode.MULTI,
|
|
618
|
-
segments: [
|
|
619
|
-
{ text: 'Option 1', value: 'option1', selected: true },
|
|
620
|
-
{ text: 'Option 2', value: 'option2' }
|
|
621
|
-
]
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
// Simulate click on the already selected segment
|
|
625
|
-
const firstSegment = segmentedButton.element.querySelectorAll('.mtrl-segment')[0];
|
|
626
|
-
firstSegment.dispatchEvent(new Event('click'));
|
|
627
|
-
|
|
628
|
-
// It should be deselected
|
|
629
|
-
expect(segmentedButton.getValue()).toEqual([]);
|
|
630
|
-
|
|
631
|
-
// Click it again
|
|
632
|
-
firstSegment.dispatchEvent(new Event('click'));
|
|
633
|
-
|
|
634
|
-
// It should be selected again
|
|
635
|
-
expect(segmentedButton.getValue()).toEqual(['option1']);
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
test('should enable and disable the component', () => {
|
|
639
|
-
const segmentedButton = createMockSegmentedButton({
|
|
640
|
-
disabled: true,
|
|
641
|
-
segments: [
|
|
642
|
-
{ text: 'Option 1', value: 'option1' },
|
|
643
|
-
{ text: 'Option 2', value: 'option2' }
|
|
644
|
-
]
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
expect(segmentedButton.element.className).toContain('mtrl-segmented-button--disabled');
|
|
648
|
-
|
|
649
|
-
segmentedButton.enable();
|
|
650
|
-
|
|
651
|
-
expect(segmentedButton.element.className).not.toContain('mtrl-segmented-button--disabled');
|
|
652
|
-
|
|
653
|
-
const segmentElements = segmentedButton.element.querySelectorAll('.mtrl-segment');
|
|
654
|
-
segmentElements.forEach(segment => {
|
|
655
|
-
expect(segment.className).not.toContain('mtrl-segment--disabled');
|
|
656
|
-
expect((segment as HTMLButtonElement).disabled).toBe(false);
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
segmentedButton.disable();
|
|
660
|
-
|
|
661
|
-
expect(segmentedButton.element.className).toContain('mtrl-segmented-button--disabled');
|
|
662
|
-
|
|
663
|
-
segmentElements.forEach(segment => {
|
|
664
|
-
expect(segment.className).toContain('mtrl-segment--disabled');
|
|
665
|
-
expect((segment as HTMLButtonElement).disabled).toBe(true);
|
|
666
|
-
});
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
test('should respect individual segment disabled state when enabling component', () => {
|
|
670
|
-
const segmentedButton = createMockSegmentedButton({
|
|
671
|
-
disabled: true,
|
|
672
|
-
segments: [
|
|
673
|
-
{ text: 'Option 1', value: 'option1' },
|
|
674
|
-
{ text: 'Option 2', value: 'option2', disabled: true }
|
|
675
|
-
]
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
segmentedButton.enable();
|
|
679
|
-
|
|
680
|
-
const segmentElements = segmentedButton.element.querySelectorAll('.mtrl-segment');
|
|
681
|
-
|
|
682
|
-
// First segment should be enabled
|
|
683
|
-
expect(segmentElements[0].className).not.toContain('mtrl-segment--disabled');
|
|
684
|
-
expect((segmentElements[0] as HTMLButtonElement).disabled).toBe(false);
|
|
685
|
-
|
|
686
|
-
// Second segment should remain disabled
|
|
687
|
-
expect(segmentElements[1].className).toContain('mtrl-segment--disabled');
|
|
688
|
-
expect((segmentElements[1] as HTMLButtonElement).disabled).toBe(true);
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
test('should remove event listeners', () => {
|
|
692
|
-
const segmentedButton = createMockSegmentedButton({
|
|
693
|
-
segments: [
|
|
694
|
-
{ text: 'Option 1', value: 'option1' }
|
|
695
|
-
]
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
let eventCount = 0;
|
|
699
|
-
|
|
700
|
-
const handler = () => {
|
|
701
|
-
eventCount++;
|
|
702
|
-
};
|
|
703
|
-
|
|
704
|
-
segmentedButton.on('change', handler);
|
|
705
|
-
|
|
706
|
-
segmentedButton.select('option1');
|
|
707
|
-
expect(eventCount).toBe(1);
|
|
708
|
-
|
|
709
|
-
segmentedButton.off('change', handler);
|
|
710
|
-
|
|
711
|
-
segmentedButton.deselect('option1');
|
|
712
|
-
expect(eventCount).toBe(1); // Count should not increase
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
test('should be properly destroyed', () => {
|
|
716
|
-
const segmentedButton = createMockSegmentedButton({
|
|
717
|
-
segments: [
|
|
718
|
-
{ text: 'Option 1', value: 'option1' },
|
|
719
|
-
{ text: 'Option 2', value: 'option2' }
|
|
720
|
-
]
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
document.body.appendChild(segmentedButton.element);
|
|
724
|
-
|
|
725
|
-
expect(document.body.contains(segmentedButton.element)).toBe(true);
|
|
726
|
-
expect(segmentedButton.segments.length).toBe(2);
|
|
727
|
-
|
|
728
|
-
segmentedButton.destroy();
|
|
729
|
-
|
|
730
|
-
expect(document.body.contains(segmentedButton.element)).toBe(false);
|
|
731
|
-
});
|
|
732
|
-
});
|