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.
Files changed (159) hide show
  1. package/.env +15 -0
  2. package/CONTRIBUTING.md +8 -8
  3. package/DOCS.md +3 -3
  4. package/README.md +43 -20
  5. package/TESTING.md +128 -18
  6. package/dist/index.js +14865 -0
  7. package/git-user-stats.js +545 -0
  8. package/index.ts +9 -67
  9. package/package.json +8 -3
  10. package/src/components/badge/api.ts +15 -1
  11. package/src/components/badge/badge.ts +43 -4
  12. package/src/components/badge/config.ts +40 -8
  13. package/src/components/badge/index.ts +64 -3
  14. package/src/components/badge/types.ts +175 -33
  15. package/src/components/button/api.ts +63 -1
  16. package/src/components/button/button.ts +39 -3
  17. package/src/components/button/config.ts +21 -4
  18. package/src/components/button/index.ts +26 -1
  19. package/src/components/button/types.ts +7 -1
  20. package/src/components/card/api.ts +78 -9
  21. package/src/components/card/card.ts +58 -3
  22. package/src/components/card/config.ts +41 -11
  23. package/src/components/card/features.ts +39 -12
  24. package/src/components/card/index.ts +84 -19
  25. package/src/components/card/types.ts +218 -29
  26. package/src/components/carousel/carousel.ts +92 -28
  27. package/src/components/carousel/constants.ts +107 -21
  28. package/src/components/carousel/index.ts +31 -13
  29. package/src/components/checkbox/checkbox.ts +83 -16
  30. package/src/components/checkbox/index.ts +43 -1
  31. package/src/components/checkbox/types.ts +219 -32
  32. package/src/components/chips/api.ts +194 -0
  33. package/src/components/{chip → chips/chip}/api.ts +42 -2
  34. package/src/components/chips/chip/chip.ts +131 -0
  35. package/src/components/{chip → chips/chip}/config.ts +3 -3
  36. package/src/components/chips/chip/index.ts +3 -0
  37. package/src/components/chips/chips.md +481 -0
  38. package/src/components/chips/chips.ts +75 -0
  39. package/src/components/chips/config.ts +109 -0
  40. package/src/components/chips/constants.ts +61 -0
  41. package/src/components/chips/features/chip-items.ts +33 -0
  42. package/src/components/chips/features/container.ts +77 -0
  43. package/src/components/chips/features/controller.ts +448 -0
  44. package/src/components/chips/features/index.ts +5 -0
  45. package/src/components/chips/features/label.ts +108 -0
  46. package/src/components/chips/index.ts +11 -0
  47. package/src/components/chips/schema.ts +61 -0
  48. package/src/components/{chip → chips}/types.ts +203 -92
  49. package/src/components/dialog/dialog.ts +99 -16
  50. package/src/components/dialog/index.ts +97 -1
  51. package/src/components/dialog/types.ts +375 -69
  52. package/src/components/divider/config.ts +90 -6
  53. package/src/components/divider/divider.ts +32 -2
  54. package/src/components/divider/features.ts +26 -0
  55. package/src/components/divider/index.ts +30 -0
  56. package/src/components/divider/types.ts +86 -9
  57. package/src/components/extended-fab/api.ts +53 -1
  58. package/src/components/extended-fab/config.ts +29 -1
  59. package/src/components/extended-fab/extended-fab.ts +28 -0
  60. package/src/components/extended-fab/index.ts +36 -0
  61. package/src/components/extended-fab/types.ts +458 -13
  62. package/src/components/fab/api.ts +42 -2
  63. package/src/components/fab/config.ts +29 -1
  64. package/src/components/fab/fab.ts +16 -2
  65. package/src/components/fab/index.ts +35 -0
  66. package/src/components/fab/types.ts +374 -10
  67. package/src/components/list/api.ts +12 -2
  68. package/src/components/list/config.ts +21 -0
  69. package/src/components/list/features.ts +6 -0
  70. package/src/components/list/index.ts +56 -1
  71. package/src/components/list/list-item.ts +46 -2
  72. package/src/components/list/list.ts +73 -2
  73. package/src/components/list/types.ts +172 -0
  74. package/src/components/list/utils.ts +26 -2
  75. package/src/components/menu/api.ts +217 -20
  76. package/src/components/menu/config.ts +27 -0
  77. package/src/components/menu/features/visibility.ts +55 -6
  78. package/src/components/menu/index.ts +64 -0
  79. package/src/components/menu/menu-item.ts +46 -3
  80. package/src/components/menu/menu.ts +77 -1
  81. package/src/components/menu/types.ts +404 -39
  82. package/src/components/sheet/config.ts +1 -2
  83. package/src/components/sheet/features/gestures.ts +1 -1
  84. package/src/components/sheet/features/position.ts +1 -2
  85. package/src/components/sheet/features/state.ts +1 -1
  86. package/src/components/sheet/index.ts +10 -2
  87. package/src/components/sheet/sheet.ts +1 -2
  88. package/src/components/sheet/types.ts +29 -1
  89. package/src/components/slider/api.ts +1 -1
  90. package/src/components/slider/config.ts +1 -1
  91. package/src/components/slider/features/controller.ts +1 -1
  92. package/src/components/slider/features/handlers.ts +1 -1
  93. package/src/components/slider/features/states.ts +1 -1
  94. package/src/components/slider/index.ts +12 -5
  95. package/src/components/slider/schema.ts +1 -1
  96. package/src/components/slider/types.ts +31 -0
  97. package/src/components/tabs/tab-api.ts +1 -1
  98. package/src/components/tabs/types.ts +1 -1
  99. package/src/components/tooltip/api.ts +6 -2
  100. package/src/components/tooltip/config.ts +9 -28
  101. package/src/components/tooltip/index.ts +10 -1
  102. package/src/components/tooltip/types.ts +38 -3
  103. package/src/index.ts +129 -31
  104. package/src/styles/abstract/_mixins.scss +23 -9
  105. package/src/styles/abstract/_variables.scss +14 -4
  106. package/src/styles/components/_card.scss +1 -1
  107. package/src/styles/components/_chip.scss +323 -113
  108. package/src/styles/components/_tabs.scss +1 -1
  109. package/CLAUDE.md +0 -33
  110. package/src/components/checkbox/constants.ts +0 -37
  111. package/src/components/chip/chip-set.ts +0 -225
  112. package/src/components/chip/chip.ts +0 -118
  113. package/src/components/chip/constants.ts +0 -28
  114. package/src/components/chip/index.ts +0 -12
  115. package/src/components/list/constants.ts +0 -116
  116. package/src/components/sheet/constants.ts +0 -20
  117. package/src/components/slider/constants.ts +0 -32
  118. package/src/components/tooltip/constants.ts +0 -27
  119. package/test/components/badge.test.ts +0 -545
  120. package/test/components/bottom-app-bar.test.ts +0 -303
  121. package/test/components/button.test.ts +0 -233
  122. package/test/components/card.test.ts +0 -560
  123. package/test/components/carousel.test.ts +0 -951
  124. package/test/components/checkbox.test.ts +0 -462
  125. package/test/components/chip.test.ts +0 -692
  126. package/test/components/datepicker.test.ts +0 -1124
  127. package/test/components/dialog.test.ts +0 -990
  128. package/test/components/divider.test.ts +0 -412
  129. package/test/components/extended-fab.test.ts +0 -672
  130. package/test/components/fab.test.ts +0 -561
  131. package/test/components/list.test.ts +0 -365
  132. package/test/components/menu.test.ts +0 -718
  133. package/test/components/navigation.test.ts +0 -186
  134. package/test/components/progress.test.ts +0 -567
  135. package/test/components/radios.test.ts +0 -699
  136. package/test/components/search.test.ts +0 -1135
  137. package/test/components/segmented-button.test.ts +0 -732
  138. package/test/components/sheet.test.ts +0 -641
  139. package/test/components/slider.test.ts +0 -1220
  140. package/test/components/snackbar.test.ts +0 -461
  141. package/test/components/switch.test.ts +0 -452
  142. package/test/components/tabs.test.ts +0 -1369
  143. package/test/components/textfield.test.ts +0 -400
  144. package/test/components/timepicker.test.ts +0 -592
  145. package/test/components/tooltip.test.ts +0 -630
  146. package/test/components/top-app-bar.test.ts +0 -566
  147. package/test/core/dom.attributes.test.ts +0 -148
  148. package/test/core/dom.classes.test.ts +0 -152
  149. package/test/core/dom.events.test.ts +0 -243
  150. package/test/core/emitter.test.ts +0 -141
  151. package/test/core/ripple.test.ts +0 -99
  152. package/test/core/state.store.test.ts +0 -189
  153. package/test/core/utils.normalize.test.ts +0 -61
  154. package/test/core/utils.object.test.ts +0 -120
  155. package/test/setup.js +0 -371
  156. package/test/setup.ts +0 -451
  157. package/tsconfig.json +0 -22
  158. package/typedoc.json +0 -28
  159. 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
- });